From d0a755c32bb54413141b183404a1d18ec11aab4b Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Tue, 17 Dec 2024 22:13:43 +0900 Subject: [PATCH 001/156] =?UTF-8?q?[FEAT]=20NotFoundParentCommentException?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/error/NotFoundParentCommentException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/error/NotFoundParentCommentException.java diff --git a/src/main/java/ject/componote/domain/comment/error/NotFoundParentCommentException.java b/src/main/java/ject/componote/domain/comment/error/NotFoundParentCommentException.java new file mode 100644 index 0000000..dcace58 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/error/NotFoundParentCommentException.java @@ -0,0 +1,9 @@ +package ject.componote.domain.comment.error; + +import org.springframework.http.HttpStatus; + +public class NotFoundParentCommentException extends CommentException { + public NotFoundParentCommentException(final Long parentId) { + super("일치하는 부모 댓글을 찾을 수 없습니다. 댓글 ID: " + parentId, HttpStatus.NOT_FOUND); + } +} From 55e445501bae1b099a933cad0b3ea8b32176b985 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Tue, 17 Dec 2024 22:13:52 +0900 Subject: [PATCH 002/156] =?UTF-8?q?[FEAT]=20InvalidCommentCreateStrategyEx?= =?UTF-8?q?ception=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../error/InvalidCommentCreateStrategyException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/error/InvalidCommentCreateStrategyException.java diff --git a/src/main/java/ject/componote/domain/comment/error/InvalidCommentCreateStrategyException.java b/src/main/java/ject/componote/domain/comment/error/InvalidCommentCreateStrategyException.java new file mode 100644 index 0000000..9ec22e0 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/error/InvalidCommentCreateStrategyException.java @@ -0,0 +1,9 @@ +package ject.componote.domain.comment.error; + +import org.springframework.http.HttpStatus; + +public class InvalidCommentCreateStrategyException extends CommentException { + public InvalidCommentCreateStrategyException() { + super("댓글 생성에 실패하였습니다. 요청 Body 확인해주세요.", HttpStatus.BAD_REQUEST); + } +} From 5657ee3604f293e53633f241985c3aae1515b899 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Tue, 17 Dec 2024 22:14:02 +0900 Subject: [PATCH 003/156] =?UTF-8?q?[FEAT]=20CommentException=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../componote/domain/comment/error/CommentException.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/ject/componote/domain/comment/error/CommentException.java b/src/main/java/ject/componote/domain/comment/error/CommentException.java index 431e1d8..1b74df8 100644 --- a/src/main/java/ject/componote/domain/comment/error/CommentException.java +++ b/src/main/java/ject/componote/domain/comment/error/CommentException.java @@ -1,4 +1,10 @@ package ject.componote.domain.comment.error; -public class CommentException { +import ject.componote.global.error.ComponoteException; +import org.springframework.http.HttpStatus; + +public class CommentException extends ComponoteException { + public CommentException(final String message, final HttpStatus status) { + super(message, status); + } } From a22c585452f8d002d4b53977eefd29fc54853f0b Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Tue, 17 Dec 2024 22:14:21 +0900 Subject: [PATCH 004/156] =?UTF-8?q?[FEAT]=20CommentCreationStrategy=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../create/CommentCreationStrategy.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/application/create/CommentCreationStrategy.java diff --git a/src/main/java/ject/componote/domain/comment/application/create/CommentCreationStrategy.java b/src/main/java/ject/componote/domain/comment/application/create/CommentCreationStrategy.java new file mode 100644 index 0000000..af143cb --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/application/create/CommentCreationStrategy.java @@ -0,0 +1,47 @@ +package ject.componote.domain.comment.application.create; + +import ject.componote.domain.comment.domain.Comment; +import ject.componote.domain.comment.dto.create.request.CommentCreateRequest; +import ject.componote.domain.comment.error.InvalidCommentCreateStrategyException; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.function.Predicate; + +@RequiredArgsConstructor +public enum CommentCreationStrategy { + GENERAL_WITHOUT_IMAGE( + request -> request.parentId() == null && request.imageTempKey() == null, + (request, memberId, permanentObjectKey) -> + Comment.createWithoutImage(request.componentId(), memberId, request.content()) + ), + GENERAL_WITH_IMAGE( + request -> request.parentId() == null && request.imageTempKey() != null, + (request, memberId, permanentObjectKey) -> + Comment.createWithImage(request.componentId(), memberId, request.content(), permanentObjectKey) + ), + REPLY_WITHOUT_IMAGE( + request -> request.parentId() != null && request.imageTempKey() == null, + (request, memberId, permanentObjectKey) -> + Comment.createReplyWithoutImage(request.componentId(), memberId, request.parentId(), request.content()) + ), + REPLY_WITH_IMAGE( + request -> request.parentId() != null && request.imageTempKey() != null, + (request, memberId, permanentObjectKey) -> + Comment.createReplyWithImage(request.componentId(), memberId, request.parentId(), request.content(), permanentObjectKey) + ); + + private final Predicate condition; + private final CommentCreationFunction creationFunction; + + public static CommentCreationStrategy findByRequest(final CommentCreateRequest request) { + return Arrays.stream(values()) + .filter(type -> type.condition.test(request)) + .findFirst() + .orElseThrow(InvalidCommentCreateStrategyException::new); + } + + public Comment createComment(final CommentCreateRequest request, final Long memberId, final String permanentObjectKey) { + return creationFunction.create(request, memberId, permanentObjectKey); + } +} From 35937e24f38ca40c0e9a25ba810498d9b00627af Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:41:49 +0900 Subject: [PATCH 005/156] =?UTF-8?q?[FEAT]=20AlreadyLikedException=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/error/AlreadyLikedException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/error/AlreadyLikedException.java diff --git a/src/main/java/ject/componote/domain/comment/error/AlreadyLikedException.java b/src/main/java/ject/componote/domain/comment/error/AlreadyLikedException.java new file mode 100644 index 0000000..e4fc4dc --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/error/AlreadyLikedException.java @@ -0,0 +1,9 @@ +package ject.componote.domain.comment.error; + +import org.springframework.http.HttpStatus; + +public class AlreadyLikedException extends CommentException { + public AlreadyLikedException(final Long commentId, final Long memberId) { + super("이미 좋아요를 누른 댓글입니다. 댓글 ID " + commentId + ", 회원 ID: " + memberId, HttpStatus.BAD_REQUEST); + } +} From 73c1be99ca1ea5dae08914069f4a7176b739a9f2 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:42:13 +0900 Subject: [PATCH 006/156] =?UTF-8?q?[FEAT]=20Spring=20EventHandler=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9D=84=20=EC=9C=84=ED=95=9C=20AsyncConfig?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ject/componote/global/config/AsyncConfig.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/global/config/AsyncConfig.java diff --git a/src/main/java/ject/componote/global/config/AsyncConfig.java b/src/main/java/ject/componote/global/config/AsyncConfig.java new file mode 100644 index 0000000..3211ed5 --- /dev/null +++ b/src/main/java/ject/componote/global/config/AsyncConfig.java @@ -0,0 +1,9 @@ +package ject.componote.global.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; + +@Configuration +@EnableAsync +public class AsyncConfig { +} From 892d62ec733e9d47b2b82d713c2852d67d65a230 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:42:20 +0900 Subject: [PATCH 007/156] =?UTF-8?q?[FEAT]=20BlankCommentException=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/error/BlankCommentException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/error/BlankCommentException.java diff --git a/src/main/java/ject/componote/domain/comment/error/BlankCommentException.java b/src/main/java/ject/componote/domain/comment/error/BlankCommentException.java new file mode 100644 index 0000000..aaceadc --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/error/BlankCommentException.java @@ -0,0 +1,9 @@ +package ject.componote.domain.comment.error; + +import org.springframework.http.HttpStatus; + +public class BlankCommentException extends CommentException { + public BlankCommentException() { + super("댓글 내용이 없습니다.", HttpStatus.BAD_REQUEST); + } +} From 7a30c0606345f2161fc7a2d66632810e04676b8c Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:42:41 +0900 Subject: [PATCH 008/156] =?UTF-8?q?[REFACTOR]=20Comment=EC=9D=98=20parentI?= =?UTF-8?q?d=20nullable=20=EC=98=B5=EC=85=98=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/ject/componote/domain/comment/domain/Comment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ject/componote/domain/comment/domain/Comment.java b/src/main/java/ject/componote/domain/comment/domain/Comment.java index f54df2d..4b82c25 100644 --- a/src/main/java/ject/componote/domain/comment/domain/Comment.java +++ b/src/main/java/ject/componote/domain/comment/domain/Comment.java @@ -49,7 +49,7 @@ public class Comment extends BaseEntity { @Column(name = "member_id", nullable = false) private Long memberId; - @Column(name = "parent_id", nullable = false) + @Column(name = "parent_id", nullable = true) private Long parentId; private Comment(final Long componentId, final Long memberId, final Long parentId, final String content, final Image image) { From e78745d471674bd31f46b21872f304a1738a4624 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:43:33 +0900 Subject: [PATCH 009/156] =?UTF-8?q?[REFACTOR]=20Comment=EC=9D=98=20Image?= =?UTF-8?q?=20=ED=95=84=EB=93=9C=EB=A5=BC=20CommentImage=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/domain/Comment.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/ject/componote/domain/comment/domain/Comment.java b/src/main/java/ject/componote/domain/comment/domain/Comment.java index 4b82c25..4b39928 100644 --- a/src/main/java/ject/componote/domain/comment/domain/Comment.java +++ b/src/main/java/ject/componote/domain/comment/domain/Comment.java @@ -7,12 +7,12 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import ject.componote.domain.comment.model.CommentContent; +import ject.componote.domain.comment.model.CommentImage; import ject.componote.domain.comment.model.converter.CommentContentConverter; +import ject.componote.domain.comment.model.converter.CommentImageConverter; import ject.componote.domain.common.domain.BaseEntity; import ject.componote.domain.common.model.Count; -import ject.componote.domain.common.model.Image; import ject.componote.domain.common.model.converter.CountConverter; -import ject.componote.domain.common.model.converter.ImageConverter; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -32,8 +32,8 @@ public class Comment extends BaseEntity { private CommentContent content; @Column(name = "image", nullable = true) - @Convert(converter = ImageConverter.class) - private Image image; + @Convert(converter = CommentImageConverter.class) + private CommentImage image; @Column(name = "report_count", nullable = false) @Convert(converter = CountConverter.class) @@ -52,7 +52,7 @@ public class Comment extends BaseEntity { @Column(name = "parent_id", nullable = true) private Long parentId; - private Comment(final Long componentId, final Long memberId, final Long parentId, final String content, final Image image) { + private Comment(final Long componentId, final Long memberId, final Long parentId, final String content, final CommentImage image) { this.componentId = componentId; this.memberId = memberId; this.parentId = parentId; @@ -62,19 +62,19 @@ private Comment(final Long componentId, final Long memberId, final Long parentId this.reportCount = Count.create(); } - public static Comment createWithImage(final Long componentId, final Long memberId, final String content, final Image image) { + public static Comment createWithImage(final Long componentId, final Long memberId, final String content, final CommentImage image) { return new Comment(componentId, memberId, null, content, image); } - public static Comment createWithoutImage(final Long componentId, final Long memberId, final String content) { + public static Comment createWithoutImage(final Long componentId, final Long memberId, final String content) { return new Comment(componentId, memberId, null, content, null); } - public static Comment createReplyWithoutImage(final Long componentId, final Long memberId, final Comment parentComment, final String content) { - return new Comment(componentId, memberId, parentComment.getParentId(), content, null); + public static Comment createReplyWithoutImage(final Long componentId, final Long memberId, final Long parentId, final String content) { + return new Comment(componentId, memberId, parentId, content, null); } - public static Comment createReplyWithImage(final Long componentId, final Long memberId, final Comment parentComment, final String content, final Image image) { - return new Comment(componentId, memberId, parentComment.getParentId(), content, image); + public static Comment createReplyWithImage(final Long componentId, final Long memberId, final Long parentId, final String content, final CommentImage image) { + return new Comment(componentId, memberId, parentId, content, image); } } From 42d21f676b23af0bb81d0e6cc25e8d35c556e923 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:44:08 +0900 Subject: [PATCH 010/156] =?UTF-8?q?[FEAT]=20CommentImage=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/model/CommentImage.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/model/CommentImage.java diff --git a/src/main/java/ject/componote/domain/comment/model/CommentImage.java b/src/main/java/ject/componote/domain/comment/model/CommentImage.java new file mode 100644 index 0000000..6b46ca6 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/model/CommentImage.java @@ -0,0 +1,37 @@ +package ject.componote.domain.comment.model; + +import ject.componote.domain.comment.error.InvalidCommentImageExtensionException; +import ject.componote.domain.common.model.BaseImage; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import org.springframework.util.StringUtils; + +import java.util.Arrays; +import java.util.List; + +@EqualsAndHashCode +@Getter +@ToString +public class CommentImage { + private static final List ALLOWED_IMAGE_EXTENSIONS = Arrays.asList("jpg", "jpeg", "png", "gif"); + + private final BaseImage image; + + private CommentImage(final BaseImage image) { + this.image = image; + } + + public static CommentImage from(final String objectKey) { + if (objectKey == null) { + return new CommentImage(BaseImage.getEmptyInstance()); + } + + final String extension = StringUtils.getFilenameExtension(objectKey); + if (!ALLOWED_IMAGE_EXTENSIONS.contains(extension)) { + throw new InvalidCommentImageExtensionException(extension); + } + + return new CommentImage(BaseImage.from(objectKey)); + } +} From cbdb7e568e3740721963d375dbc5059b81a5a51e Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:44:41 +0900 Subject: [PATCH 011/156] =?UTF-8?q?[FEAT]=20CommentContent=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/model/CommentContent.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/ject/componote/domain/comment/model/CommentContent.java b/src/main/java/ject/componote/domain/comment/model/CommentContent.java index d68e680..7dd4eb1 100644 --- a/src/main/java/ject/componote/domain/comment/model/CommentContent.java +++ b/src/main/java/ject/componote/domain/comment/model/CommentContent.java @@ -1,5 +1,9 @@ package ject.componote.domain.comment.model; +import ject.componote.domain.auth.domain.BadWordFilteringSingleton; +import ject.componote.domain.comment.error.BlankCommentException; +import ject.componote.domain.comment.error.ExceededCommentLengthException; +import ject.componote.domain.comment.error.OffensiveCommentException; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.ToString; @@ -8,6 +12,8 @@ @EqualsAndHashCode @ToString public class CommentContent { + private static final int MAX_LENGTH = 1_000; + private final String value; private CommentContent(final String value) { @@ -20,9 +26,16 @@ public static CommentContent from(final String value) { } private void validateContent(final String value) { - // 추가 제약조건 고려 (e.g. 글자수, 비속어 등) if (value == null || value.isBlank()) { - throw new IllegalArgumentException("Comment content cannot be null or blank"); + throw new BlankCommentException(); + } + + if (value.length() > MAX_LENGTH) { + throw new ExceededCommentLengthException(value.length()); + } + + if (BadWordFilteringSingleton.containsBadWord(value)) { + throw new OffensiveCommentException(); } } } From 468a6ff5e9c61dfaab183cb7aea12ee4784d7e35 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:44:56 +0900 Subject: [PATCH 012/156] =?UTF-8?q?[REFACTOR]=20CommentCreateRequest=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/dto/create/request/CommentCreateRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ject/componote/domain/comment/dto/create/request/CommentCreateRequest.java b/src/main/java/ject/componote/domain/comment/dto/create/request/CommentCreateRequest.java index 191bd3c..fb4c687 100644 --- a/src/main/java/ject/componote/domain/comment/dto/create/request/CommentCreateRequest.java +++ b/src/main/java/ject/componote/domain/comment/dto/create/request/CommentCreateRequest.java @@ -5,7 +5,7 @@ import jakarta.validation.constraints.NotNull; public record CommentCreateRequest( - @Nullable String image, + @Nullable String imageObjectKey, @NotBlank String content, @NotNull Long componentId, @Nullable Long parentId From 536da0fe22952ed81d03214326e4248590f005f9 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:45:17 +0900 Subject: [PATCH 013/156] =?UTF-8?q?[FEAT]=20CommentCreateResponse=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EB=B0=8F=20=EC=A0=95=EC=A0=81=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?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/dto/create/response/CommentCreateResponse.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/ject/componote/domain/comment/dto/create/response/CommentCreateResponse.java b/src/main/java/ject/componote/domain/comment/dto/create/response/CommentCreateResponse.java index c568aad..c9df5dc 100644 --- a/src/main/java/ject/componote/domain/comment/dto/create/response/CommentCreateResponse.java +++ b/src/main/java/ject/componote/domain/comment/dto/create/response/CommentCreateResponse.java @@ -1,4 +1,9 @@ package ject.componote.domain.comment.dto.create.response; -public record CommentCreateResponse() { +import ject.componote.domain.comment.domain.Comment; + +public record CommentCreateResponse(Long id) { + public static CommentCreateResponse from(final Comment comment) { + return new CommentCreateResponse(comment.getId()); + } } From 5f35279d4f925dc8c8de367634bd4c35bb118aeb Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:45:29 +0900 Subject: [PATCH 014/156] =?UTF-8?q?[FEAT]=20CommentCreationStrategy=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/CommentCreationStrategy.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/application/CommentCreationStrategy.java diff --git a/src/main/java/ject/componote/domain/comment/application/CommentCreationStrategy.java b/src/main/java/ject/componote/domain/comment/application/CommentCreationStrategy.java new file mode 100644 index 0000000..cecb5fc --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/application/CommentCreationStrategy.java @@ -0,0 +1,53 @@ +package ject.componote.domain.comment.application; + +import ject.componote.domain.comment.domain.Comment; +import ject.componote.domain.comment.dto.create.request.CommentCreateRequest; +import ject.componote.domain.comment.error.InvalidCommentCreateStrategyException; +import ject.componote.domain.comment.model.CommentImage; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.function.Predicate; + +@RequiredArgsConstructor +public enum CommentCreationStrategy { + GENERAL_WITHOUT_IMAGE( + request -> request.parentId() == null && request.imageObjectKey() == null, + (request, memberId, image) -> + Comment.createWithoutImage(request.componentId(), memberId, request.content()) + ), + GENERAL_WITH_IMAGE( + request -> request.parentId() == null && request.imageObjectKey() != null, + (request, memberId, image) -> + Comment.createWithImage(request.componentId(), memberId, request.content(), image) + ), + REPLY_WITHOUT_IMAGE( + request -> request.parentId() != null && request.imageObjectKey() == null, + (request, memberId, image) -> + Comment.createReplyWithoutImage(request.componentId(), memberId, request.parentId(), request.content()) + ), + REPLY_WITH_IMAGE( + request -> request.parentId() != null && request.imageObjectKey() != null, + (request, memberId, image) -> + Comment.createReplyWithImage(request.componentId(), memberId, request.parentId(), request.content(), image) + ); + + private final Predicate condition; + private final CommentCreationFunction creationFunction; + + public static CommentCreationStrategy findByRequest(final CommentCreateRequest request) { + return Arrays.stream(values()) + .filter(type -> type.condition.test(request)) + .findFirst() + .orElseThrow(InvalidCommentCreateStrategyException::new); + } + + public Comment createComment(final CommentCreateRequest request, final Long memberId, final CommentImage image) { + return creationFunction.create(request, memberId, image); + } + + @FunctionalInterface + private interface CommentCreationFunction { + Comment create(final CommentCreateRequest request, final Long memberId, final CommentImage image); + } +} From d96e59786afa80c924e7f02138c31d0306c5d7bf Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:45:45 +0900 Subject: [PATCH 015/156] =?UTF-8?q?[FEAT]=20CommenterValidation=20?= =?UTF-8?q?=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/validation/CommenterValidation.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/validation/CommenterValidation.java diff --git a/src/main/java/ject/componote/domain/comment/validation/CommenterValidation.java b/src/main/java/ject/componote/domain/comment/validation/CommenterValidation.java new file mode 100644 index 0000000..54a6a76 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/validation/CommenterValidation.java @@ -0,0 +1,11 @@ +package ject.componote.domain.comment.validation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface CommenterValidation { +} From 9211df326abbd3514100687b16c583c0cf9bcc99 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:46:03 +0900 Subject: [PATCH 016/156] =?UTF-8?q?[FEAT]=20AOP=EB=A5=BC=20=ED=86=B5?= =?UTF-8?q?=ED=95=9C=20=EB=B3=B8=EC=9D=B8=EC=9D=B4=20=EC=9E=91=EC=84=B1?= =?UTF-8?q?=ED=95=9C=20=EB=8C=93=EA=B8=80=EC=9D=B8=EC=A7=80=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=ED=95=98=EB=8A=94=20=EA=B2=80=EC=A6=9D=EA=B8=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../validation/CommenterValidationAspect.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/validation/CommenterValidationAspect.java diff --git a/src/main/java/ject/componote/domain/comment/validation/CommenterValidationAspect.java b/src/main/java/ject/componote/domain/comment/validation/CommenterValidationAspect.java new file mode 100644 index 0000000..e3d87e9 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/validation/CommenterValidationAspect.java @@ -0,0 +1,28 @@ +package ject.componote.domain.comment.validation; + +import ject.componote.domain.auth.model.AuthPrincipal; +import ject.componote.domain.comment.dao.CommentRepository; +import ject.componote.domain.comment.error.NotFoundCommentException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Aspect +@Component +@RequiredArgsConstructor +@Slf4j +@Transactional(readOnly = true) +public class CommenterValidationAspect { + private final CommentRepository commentRepository; + + @Before(value = "@annotation(ject.componote.domain.comment.validation.CommenterValidation) && args(authPrincipal, commentId, ..)", argNames = "authPrincipal,commentId") + public void validate(final AuthPrincipal authPrincipal, final Long commentId) { + final Long memberId = authPrincipal.id(); + if (!commentRepository.existsByIdAndMemberId(commentId, memberId)) { + throw new NotFoundCommentException(commentId, memberId); + } + } +} From 57a472bd6faefa375ce87a603b5ba273174c1f4f Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:46:16 +0900 Subject: [PATCH 017/156] =?UTF-8?q?[FEAT]=20CommentFindByComponentDao=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/dao/CommentFindByComponentDao.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/dao/CommentFindByComponentDao.java diff --git a/src/main/java/ject/componote/domain/comment/dao/CommentFindByComponentDao.java b/src/main/java/ject/componote/domain/comment/dao/CommentFindByComponentDao.java new file mode 100644 index 0000000..a4b87e0 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/dao/CommentFindByComponentDao.java @@ -0,0 +1,12 @@ +package ject.componote.domain.comment.dao; + +import ject.componote.domain.auth.domain.Job; +import ject.componote.domain.auth.model.Nickname; +import ject.componote.domain.comment.model.CommentContent; +import ject.componote.domain.common.model.BaseImage; +import ject.componote.domain.common.model.Count; + +import java.time.LocalDateTime; + +public record CommentFindByComponentDao(Long memberId, Nickname nickname, BaseImage profileImage, Job job, Long commentId, Long parentId, BaseImage image, CommentContent content, LocalDateTime createdAt, Count likeCount, boolean isLiked, boolean isReply) { +} From 19695a1d5bcd0fc786ab2f77038cba3df042bb1b Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:46:29 +0900 Subject: [PATCH 018/156] =?UTF-8?q?[FEAT]=20CommentFindByComponentResponse?= =?UTF-8?q?=20=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommentFindByComponentResponse.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByComponentResponse.java diff --git a/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByComponentResponse.java b/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByComponentResponse.java new file mode 100644 index 0000000..e9e89f0 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByComponentResponse.java @@ -0,0 +1,29 @@ +package ject.componote.domain.comment.dto.find.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import ject.componote.domain.comment.dao.CommentFindByComponentDao; + +import java.time.LocalDateTime; + +public record CommentFindByComponentResponse( + CommentProfileResponse profile, + Long id, + @JsonInclude(JsonInclude.Include.NON_NULL) Long parentId, + @JsonInclude(JsonInclude.Include.NON_NULL) String imageUrl, + String content, + LocalDateTime createdAt, + Long likeCount, + boolean isReply) { + public static CommentFindByComponentResponse from(final CommentFindByComponentDao dto) { + return new CommentFindByComponentResponse( + new CommentProfileResponse(dto.memberId(), dto.nickname().getValue(), dto.profileImage().getObjectKey(), dto.job().name()), + dto.commentId(), + dto.parentId(), + dto.image().toUrl(), + dto.content().getValue(), + dto.createdAt(), + dto.likeCount().getValue(), + dto.isReply() + ); + } +} From 8874db0469ea1a47fabc53a230046bacd2575dc6 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:46:37 +0900 Subject: [PATCH 019/156] =?UTF-8?q?[FEAT]=20CommentFindByMemberDao=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/dao/CommentFindByMemberDao.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/dao/CommentFindByMemberDao.java diff --git a/src/main/java/ject/componote/domain/comment/dao/CommentFindByMemberDao.java b/src/main/java/ject/componote/domain/comment/dao/CommentFindByMemberDao.java new file mode 100644 index 0000000..649f91f --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/dao/CommentFindByMemberDao.java @@ -0,0 +1,8 @@ +package ject.componote.domain.comment.dao; + +import ject.componote.domain.comment.model.CommentContent; + +import java.time.LocalDateTime; + +public record CommentFindByMemberDao(Long id, Long parentId, String componentTitle, CommentContent parentContent, CommentContent content, LocalDateTime createdAt, boolean isReply) { +} From 1e9d900386bec80b5af54232dfd906f4e842f59f Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:46:44 +0900 Subject: [PATCH 020/156] =?UTF-8?q?[FEAT]=20CommentFindByMemberResponse=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/CommentFindByMemberResponse.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByMemberResponse.java diff --git a/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByMemberResponse.java b/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByMemberResponse.java new file mode 100644 index 0000000..37c82bb --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByMemberResponse.java @@ -0,0 +1,20 @@ +package ject.componote.domain.comment.dto.find.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import ject.componote.domain.comment.dao.CommentFindByMemberDao; + +import java.time.LocalDateTime; + +public record CommentFindByMemberResponse(Long id, @JsonInclude(JsonInclude.Include.NON_NULL) Long parentId, String componentTitle, @JsonInclude(JsonInclude.Include.NON_NULL) String parentContent, String content, LocalDateTime createdAt, boolean isReply) { + public static CommentFindByMemberResponse from(final CommentFindByMemberDao commentFindByMemberDao) { + return new CommentFindByMemberResponse( + commentFindByMemberDao.id(), + commentFindByMemberDao.parentId(), + commentFindByMemberDao.componentTitle(), + commentFindByMemberDao.parentContent().getValue(), + commentFindByMemberDao.content().getValue(), + commentFindByMemberDao.createdAt(), + commentFindByMemberDao.isReply() + ); + } +} From 166f47ffc23b88cec260743610244dd260b0cdbe Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:46:50 +0900 Subject: [PATCH 021/156] =?UTF-8?q?[FEAT]=20CommentImageConverter=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/CommentImageConverter.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/model/converter/CommentImageConverter.java diff --git a/src/main/java/ject/componote/domain/comment/model/converter/CommentImageConverter.java b/src/main/java/ject/componote/domain/comment/model/converter/CommentImageConverter.java new file mode 100644 index 0000000..c2189ed --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/model/converter/CommentImageConverter.java @@ -0,0 +1,19 @@ +package ject.componote.domain.comment.model.converter; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +import ject.componote.domain.comment.model.CommentImage; + +@Converter +public class CommentImageConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(final CommentImage attribute) { + return attribute.getImage() + .getObjectKey(); + } + + @Override + public CommentImage convertToEntityAttribute(final String dbData) { + return CommentImage.from(dbData); + } +} From 74becb2b32dfab457a4b2c7dd86009b91242ef2b Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:46:56 +0900 Subject: [PATCH 022/156] =?UTF-8?q?[FEAT]=20CommentLike=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/domain/CommentLike.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/domain/CommentLike.java diff --git a/src/main/java/ject/componote/domain/comment/domain/CommentLike.java b/src/main/java/ject/componote/domain/comment/domain/CommentLike.java new file mode 100644 index 0000000..261cd69 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/domain/CommentLike.java @@ -0,0 +1,36 @@ +package ject.componote.domain.comment.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@ToString +public class CommentLike { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "comment_id", nullable = false) + private Long commentId; + + @Column(name = "member_id", nullable = false) + private Long memberId; + + private CommentLike(final Long commentId, final Long memberId) { + this.commentId = commentId; + this.memberId = memberId; + } + + public static CommentLike of(final Long commentId, final Long memberId) { + return new CommentLike(commentId, memberId); + } +} From 3ce4014257d3ee4f10f43cde3d947871755ed74d Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:47:02 +0900 Subject: [PATCH 023/156] =?UTF-8?q?[FEAT]=20CommentLikeEvent=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/dto/like/event/CommentLikeEvent.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/dto/like/event/CommentLikeEvent.java diff --git a/src/main/java/ject/componote/domain/comment/dto/like/event/CommentLikeEvent.java b/src/main/java/ject/componote/domain/comment/dto/like/event/CommentLikeEvent.java new file mode 100644 index 0000000..12b96f3 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/dto/like/event/CommentLikeEvent.java @@ -0,0 +1,9 @@ +package ject.componote.domain.comment.dto.like.event; + +import ject.componote.domain.auth.model.AuthPrincipal; + +public record CommentLikeEvent(Long commentId, Long memberId) { + public static CommentLikeEvent of(final AuthPrincipal authPrincipal, final Long commentId) { + return new CommentLikeEvent(commentId, authPrincipal.id()); + } +} From b70c4c38a50e3bece693befb8661dd964f34f228 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:47:12 +0900 Subject: [PATCH 024/156] =?UTF-8?q?[FEAT]=20CommentLikeEventListener=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/CommentLikeEventListener.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/application/CommentLikeEventListener.java diff --git a/src/main/java/ject/componote/domain/comment/application/CommentLikeEventListener.java b/src/main/java/ject/componote/domain/comment/application/CommentLikeEventListener.java new file mode 100644 index 0000000..4a776b5 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/application/CommentLikeEventListener.java @@ -0,0 +1,53 @@ +package ject.componote.domain.comment.application; + +import ject.componote.domain.comment.domain.Comment; +import ject.componote.domain.comment.domain.CommentLike; +import ject.componote.domain.comment.dao.CommentLikeRepository; +import ject.componote.domain.comment.dao.CommentRepository; +import ject.componote.domain.comment.dto.like.event.CommentLikeEvent; +import ject.componote.domain.comment.dto.like.event.CommentUnlikeEvent; +import ject.componote.domain.comment.error.NotFoundCommentException; +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class CommentLikeEventListener { + private final CommentRepository commentRepository; + private final CommentLikeRepository commentLikeRepository; + + @Async + @EventListener + @Transactional + public void handleCommentLikeEvent(final CommentLikeEvent event) { + final Long commentId = event.commentId(); + final Comment comment = findCommentById(commentId); // 내 댓글에 좋아요를 달 수 있는지? + comment.increaseLikeCount(); + + final Long memberId = event.memberId(); + commentLikeRepository.save(CommentLike.of(commentId, memberId)); + commentRepository.save(comment); + } + + @Async + @EventListener + @Transactional + public void handleCommentUnlikeEvent(final CommentUnlikeEvent event) { + final Long commentId = event.commentId(); + final Comment comment = findCommentById(commentId); // 내 댓글에 좋아요를 달 수 있는지? + comment.decreaseLikeCount(); + + final Long memberId = event.memberId(); + commentLikeRepository.deleteByCommentIdAndMemberId(commentId, memberId); + commentRepository.save(comment); + } + + private Comment findCommentById(final Long commentId) { + return commentRepository.findById(commentId) + .orElseThrow(() -> new NotFoundCommentException(commentId)); + } +} From d9d91be087cd9bac3f062ba4e883b84b9c6e4e74 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:48:45 +0900 Subject: [PATCH 025/156] =?UTF-8?q?[FEAT]=20CommentLikeRepository=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/dao/CommentLikeRepository.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/dao/CommentLikeRepository.java diff --git a/src/main/java/ject/componote/domain/comment/dao/CommentLikeRepository.java b/src/main/java/ject/componote/domain/comment/dao/CommentLikeRepository.java new file mode 100644 index 0000000..ab2a53e --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/dao/CommentLikeRepository.java @@ -0,0 +1,9 @@ +package ject.componote.domain.comment.dao; + +import ject.componote.domain.comment.domain.CommentLike; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CommentLikeRepository extends JpaRepository { + boolean existsByCommentIdAndMemberId(final Long commentId, final Long memberId); + void deleteByCommentIdAndMemberId(final Long commentId, final Long memberId); +} From 5a4b230d9512e71ed9caa89ecca80c35dc8cd6ff Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:48:55 +0900 Subject: [PATCH 026/156] =?UTF-8?q?[FEAT]=20CommentProfileDao=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../componote/domain/comment/dao/CommentProfileDao.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/dao/CommentProfileDao.java diff --git a/src/main/java/ject/componote/domain/comment/dao/CommentProfileDao.java b/src/main/java/ject/componote/domain/comment/dao/CommentProfileDao.java new file mode 100644 index 0000000..53b63ce --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/dao/CommentProfileDao.java @@ -0,0 +1,8 @@ +package ject.componote.domain.comment.dao; + +import ject.componote.domain.auth.domain.Job; +import ject.componote.domain.auth.model.Nickname; +import ject.componote.domain.common.model.BaseImage; + +public record CommentProfileDao(Long memberId, Nickname nickname, BaseImage profileImage, Job job) { +} From 58190ee80c542aa04e1632bec3c3352aa4e260a7 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:49:02 +0900 Subject: [PATCH 027/156] =?UTF-8?q?[FEAT]=20CommentProfileResponse=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/find/response/CommentProfileResponse.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/dto/find/response/CommentProfileResponse.java diff --git a/src/main/java/ject/componote/domain/comment/dto/find/response/CommentProfileResponse.java b/src/main/java/ject/componote/domain/comment/dto/find/response/CommentProfileResponse.java new file mode 100644 index 0000000..458597e --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/dto/find/response/CommentProfileResponse.java @@ -0,0 +1,14 @@ +package ject.componote.domain.comment.dto.find.response; + +import ject.componote.domain.comment.dao.CommentProfileDao; + +public record CommentProfileResponse(Long memberId, String nickname, String profileImageUrl, String job) { + public static CommentProfileResponse from(final CommentProfileDao profileDto) { + return new CommentProfileResponse( + profileDto.memberId(), + profileDto.nickname().getValue(), + profileDto.profileImage().getObjectKey(), + profileDto.job().name() + ); + } +} From b5c88fad7cdd87702dc09a8c2c49dfdb8de72c23 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:49:10 +0900 Subject: [PATCH 028/156] =?UTF-8?q?[FEAT]=20CommentQueryDsl=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../componote/domain/comment/dao/CommentQueryDsl.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/dao/CommentQueryDsl.java diff --git a/src/main/java/ject/componote/domain/comment/dao/CommentQueryDsl.java b/src/main/java/ject/componote/domain/comment/dao/CommentQueryDsl.java new file mode 100644 index 0000000..e824bcb --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/dao/CommentQueryDsl.java @@ -0,0 +1,10 @@ +package ject.componote.domain.comment.dao; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface CommentQueryDsl { + Page findAllByComponentIdWithPagination(final Long componentId, final Pageable pageable); + Page findAllByComponentIdWithLikeStatusAndPagination(final Long componentId, final Long memberId, final Pageable pageable); + Page findAllByMemberIdWithPagination(final Long memberId, final Pageable pageable); +} From 05e8ad984095bbeeb5044ab976a0dca3ad9ea18d Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:49:15 +0900 Subject: [PATCH 029/156] =?UTF-8?q?[FEAT]=20CommentQueryDslImpl=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/dao/CommentQueryDslImpl.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/dao/CommentQueryDslImpl.java diff --git a/src/main/java/ject/componote/domain/comment/dao/CommentQueryDslImpl.java b/src/main/java/ject/componote/domain/comment/dao/CommentQueryDslImpl.java new file mode 100644 index 0000000..4502bc6 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/dao/CommentQueryDslImpl.java @@ -0,0 +1,65 @@ +package ject.componote.domain.comment.dao; + +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import ject.componote.domain.comment.domain.QComment; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import static ject.componote.domain.auth.domain.QMember.member; +import static ject.componote.domain.comment.domain.QComment.comment; +import static ject.componote.domain.comment.domain.QCommentLike.commentLike; +import static ject.componote.domain.component.domain.QComponent.component; +import static ject.componote.global.util.RepositoryUtils.eqExpression; +import static ject.componote.global.util.RepositoryUtils.toPage; + +@RequiredArgsConstructor +public class CommentQueryDslImpl implements CommentQueryDsl { + private static final QComment PARENT = new QComment("parent"); + + private final JPAQueryFactory queryFactory; + private final QCommentDtoFactory qCommentDtoFactory; + + @Override + public Page findAllByComponentIdWithPagination(final Long componentId, final Pageable pageable) { + final BooleanExpression predicate = eqExpression(comment.componentId, componentId); + final JPAQuery countQuery = createCountQuery(predicate); + final JPAQuery baseQuery = queryFactory.select(qCommentDtoFactory.createForComponent()) + .from(comment) + .innerJoin(member).on(eqExpression(member.id, comment.memberId)) + .where(predicate); + return toPage(baseQuery, countQuery, comment, pageable); + } + + @Override + public Page findAllByComponentIdWithLikeStatusAndPagination(final Long componentId, final Long memberId, final Pageable pageable) { + final BooleanExpression predicate = eqExpression(comment.componentId, componentId); + final JPAQuery countQuery = createCountQuery(predicate); + final JPAQuery baseQuery = queryFactory.select(qCommentDtoFactory.createForComponentWithLikeStatus()) + .from(comment) + .innerJoin(member).on(eqExpression(member.id, comment.memberId)) + .innerJoin(commentLike).on(eqExpression(commentLike.commentId, component.id).and(eqExpression(commentLike.memberId, memberId))) + .where(predicate); + return toPage(baseQuery, countQuery, comment, pageable); + } + + @Override + public Page findAllByMemberIdWithPagination(final Long memberId, final Pageable pageable) { + final BooleanExpression predicate = eqExpression(comment.memberId, memberId); + final JPAQuery countQuery = createCountQuery(predicate); + final JPAQuery baseQuery = queryFactory.select(qCommentDtoFactory.createForMember(PARENT)) + .from(comment) + .innerJoin(PARENT).on(eqExpression(PARENT.id, comment.parentId)) + .innerJoin(component).on(eqExpression(component.id, comment.componentId)) + .where(predicate); + return toPage(baseQuery, countQuery, comment, pageable); + } + + private JPAQuery createCountQuery(final BooleanExpression predicate) { + return queryFactory.select(comment.count()) + .from(comment) + .where(predicate); + } +} From 9c81d09208f866788e33d91df442d29f71e73ce4 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:49:53 +0900 Subject: [PATCH 030/156] =?UTF-8?q?[FEAT]=20CommentRepository=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/dao/CommentRepository.java | 12 ++++++++++++ .../domain/comment/domain/CommentRepository.java | 6 ------ 2 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 src/main/java/ject/componote/domain/comment/dao/CommentRepository.java delete mode 100644 src/main/java/ject/componote/domain/comment/domain/CommentRepository.java diff --git a/src/main/java/ject/componote/domain/comment/dao/CommentRepository.java b/src/main/java/ject/componote/domain/comment/dao/CommentRepository.java new file mode 100644 index 0000000..bb04504 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/dao/CommentRepository.java @@ -0,0 +1,12 @@ +package ject.componote.domain.comment.dao; + +import ject.componote.domain.comment.domain.Comment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface CommentRepository extends JpaRepository, CommentQueryDsl { + Optional findByIdAndMemberId(final Long id, final Long memberId); + boolean existsByIdAndMemberId(final Long id, final Long memberId); + void deleteByIdAndMemberId(final Long commentId, final Long memberId); +} diff --git a/src/main/java/ject/componote/domain/comment/domain/CommentRepository.java b/src/main/java/ject/componote/domain/comment/domain/CommentRepository.java deleted file mode 100644 index ff52d3b..0000000 --- a/src/main/java/ject/componote/domain/comment/domain/CommentRepository.java +++ /dev/null @@ -1,6 +0,0 @@ -package ject.componote.domain.comment.domain; - -import org.springframework.data.jpa.repository.JpaRepository; - -public interface CommentRepository extends JpaRepository { -} From 645684562f27fdcafb9c86e94ae6fade639177fe Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:50:16 +0900 Subject: [PATCH 031/156] =?UTF-8?q?[FEAT]=20Comment=EC=97=90=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=EC=A6=9D=EA=B0=80/=EA=B0=90=EC=86=8C,=20u?= =?UTF-8?q?pdate()=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/domain/Comment.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/main/java/ject/componote/domain/comment/domain/Comment.java b/src/main/java/ject/componote/domain/comment/domain/Comment.java index 4b39928..5ea0002 100644 --- a/src/main/java/ject/componote/domain/comment/domain/Comment.java +++ b/src/main/java/ject/componote/domain/comment/domain/Comment.java @@ -77,4 +77,29 @@ public static Comment createReplyWithoutImage(final Long componentId, final Long public static Comment createReplyWithImage(final Long componentId, final Long memberId, final Long parentId, final String content, final CommentImage image) { return new Comment(componentId, memberId, parentId, content, image); } + + public void increaseLikeCount() { + this.likeCount.increase(); + } + + public void decreaseLikeCount() { + this.likeCount.decrease(); + } + + public boolean equalsImage(final CommentImage image) { + return this.image.equals(image); + } + + public void update(final CommentContent content, final CommentImage image) { + updateContent(content); + updateImage(image); + } + + private void updateContent(final CommentContent content) { + this.content = content; + } + + private void updateImage(final CommentImage image) { + this.image = image; + } } From 5751615f4b8d1bc3ebb6185d8bcacf30b64ae7f4 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:50:28 +0900 Subject: [PATCH 032/156] =?UTF-8?q?[FEAT]=20CommentUnlikeEvent=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/dto/like/event/CommentUnlikeEvent.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/dto/like/event/CommentUnlikeEvent.java diff --git a/src/main/java/ject/componote/domain/comment/dto/like/event/CommentUnlikeEvent.java b/src/main/java/ject/componote/domain/comment/dto/like/event/CommentUnlikeEvent.java new file mode 100644 index 0000000..2237ef9 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/dto/like/event/CommentUnlikeEvent.java @@ -0,0 +1,9 @@ +package ject.componote.domain.comment.dto.like.event; + +import ject.componote.domain.auth.model.AuthPrincipal; + +public record CommentUnlikeEvent(Long commentId, Long memberId) { + public static CommentUnlikeEvent of(final AuthPrincipal authPrincipal, final Long commentId) { + return new CommentUnlikeEvent(commentId, authPrincipal.id()); + } +} From 8b0cd9dc5dfbe20110f6ab31c5c7a1440941674a Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:50:36 +0900 Subject: [PATCH 033/156] =?UTF-8?q?[FEAT]=20CommentUpdateRequest=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/dto/update/request/CommentUpdateRequest.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/dto/update/request/CommentUpdateRequest.java diff --git a/src/main/java/ject/componote/domain/comment/dto/update/request/CommentUpdateRequest.java b/src/main/java/ject/componote/domain/comment/dto/update/request/CommentUpdateRequest.java new file mode 100644 index 0000000..207c2ba --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/dto/update/request/CommentUpdateRequest.java @@ -0,0 +1,8 @@ +package ject.componote.domain.comment.dto.update.request; + +import jakarta.annotation.Nullable; +import jakarta.validation.constraints.NotBlank; + +public record CommentUpdateRequest(@Nullable String imageObjectKey, + @NotBlank String content) { +} From bb3925614f72adf6f72c464125a3663ae61bc3c4 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:51:29 +0900 Subject: [PATCH 034/156] =?UTF-8?q?[FEAT]=20ExceededCommentLengthException?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/error/ExceedCommentLengthException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/error/ExceedCommentLengthException.java diff --git a/src/main/java/ject/componote/domain/comment/error/ExceedCommentLengthException.java b/src/main/java/ject/componote/domain/comment/error/ExceedCommentLengthException.java new file mode 100644 index 0000000..e476270 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/error/ExceedCommentLengthException.java @@ -0,0 +1,9 @@ +package ject.componote.domain.comment.error; + +import org.springframework.http.HttpStatus; + +public class ExceedCommentLengthException extends CommentException{ + public ExceedCommentLengthException(final int length) { + super("댓글 길이가 너무 깁니다. 현재 길이: " + length, HttpStatus.BAD_REQUEST); + } +} From 5f8202123ad98b702858ce63a4850888ce8f20e9 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:51:43 +0900 Subject: [PATCH 035/156] =?UTF-8?q?[FEAT]=20CommentContent=20import=20?= =?UTF-8?q?=EB=AC=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ject/componote/domain/comment/model/CommentContent.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ject/componote/domain/comment/model/CommentContent.java b/src/main/java/ject/componote/domain/comment/model/CommentContent.java index 7dd4eb1..b3a0e55 100644 --- a/src/main/java/ject/componote/domain/comment/model/CommentContent.java +++ b/src/main/java/ject/componote/domain/comment/model/CommentContent.java @@ -2,7 +2,7 @@ import ject.componote.domain.auth.domain.BadWordFilteringSingleton; import ject.componote.domain.comment.error.BlankCommentException; -import ject.componote.domain.comment.error.ExceededCommentLengthException; +import ject.componote.domain.comment.error.ExceedCommentLengthException; import ject.componote.domain.comment.error.OffensiveCommentException; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -31,7 +31,7 @@ private void validateContent(final String value) { } if (value.length() > MAX_LENGTH) { - throw new ExceededCommentLengthException(value.length()); + throw new ExceedCommentLengthException(value.length()); } if (BadWordFilteringSingleton.containsBadWord(value)) { From a88613af13088a58bf3d4303bb1031dd00b58036 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:51:52 +0900 Subject: [PATCH 036/156] =?UTF-8?q?[FEAT]=20RepositoryUtils=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/util/RepositoryUtils.java | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/main/java/ject/componote/global/util/RepositoryUtils.java diff --git a/src/main/java/ject/componote/global/util/RepositoryUtils.java b/src/main/java/ject/componote/global/util/RepositoryUtils.java new file mode 100644 index 0000000..c6bd084 --- /dev/null +++ b/src/main/java/ject/componote/global/util/RepositoryUtils.java @@ -0,0 +1,82 @@ +package ject.componote.global.util; + +import com.querydsl.core.types.Order; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.ComparableExpression; +import com.querydsl.core.types.dsl.EntityPathBase; +import com.querydsl.core.types.dsl.NumberExpression; +import com.querydsl.core.types.dsl.PathBuilder; +import com.querydsl.core.types.dsl.SimpleExpression; +import com.querydsl.jpa.impl.JPAQuery; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.support.PageableExecutionUtils; + +public final class RepositoryUtils { + private RepositoryUtils() { + + } + + public static Page toPage(final JPAQuery baseQuery, + final JPAQuery countQuery, + final EntityPathBase qClass, + final Pageable pageable) { + if (countQuery.fetchFirst() == null) { + return Page.empty(); + } + + final JPAQuery contentQuery = createContentQuery(baseQuery, qClass, pageable); + return PageableExecutionUtils.getPage(contentQuery.fetch(), pageable, countQuery::fetchOne); + } + + public static > BooleanExpression eqExpression(final SimpleExpression simpleExpression, final T target) { + if (target == null) { + return null; + } + + return simpleExpression.eq(target); + } + + public static > BooleanExpression eqExpression(final NumberExpression numberExpression, final T target) { + if (target == null) { + return null; + } + + return numberExpression.eq(target); + } + + public static > BooleanExpression eqExpression(final SimpleExpression simpleExpression, final SimpleExpression target) { + return simpleExpression.eq(target); + } + + private static JPAQuery createContentQuery(final JPAQuery query, + final EntityPathBase qClass, + final Pageable pageable) { + return query.limit(pageable.getPageSize()) + .offset(pageable.getOffset()) + .orderBy(createOrderSpecifiers(qClass, pageable)); + } + + private static OrderSpecifier[] createOrderSpecifiers(final EntityPathBase qClass, final Pageable pageable) { + return pageable.getSort() + .stream() + .map(sort -> toOrderSpecifier(qClass, sort)) + .toArray(OrderSpecifier[]::new); + } + + private static OrderSpecifier toOrderSpecifier(final EntityPathBase qClass, final Sort.Order sortOrder) { + final Order orderMethod = toOrder(sortOrder); + final PathBuilder pathBuilder = new PathBuilder<>(qClass.getType(), qClass.getMetadata()); + return new OrderSpecifier(orderMethod, pathBuilder.get(sortOrder.getProperty())); + } + + private static Order toOrder(final Sort.Order sortOrder) { + if (sortOrder.isAscending()) { + return Order.ASC; + } + + return Order.DESC; + } +} From 5867a9d8544f7bd9a1a79f23cfe096112dd0f421 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:52:00 +0900 Subject: [PATCH 037/156] =?UTF-8?q?[FEAT]=20QueryDslConfig=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/QueryDslConfig.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/main/java/ject/componote/global/config/QueryDslConfig.java diff --git a/src/main/java/ject/componote/global/config/QueryDslConfig.java b/src/main/java/ject/componote/global/config/QueryDslConfig.java new file mode 100644 index 0000000..af0989e --- /dev/null +++ b/src/main/java/ject/componote/global/config/QueryDslConfig.java @@ -0,0 +1,19 @@ +package ject.componote.global.config; + +import com.querydsl.jpa.JPQLTemplates; +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class QueryDslConfig { + @PersistenceContext + private EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(JPQLTemplates.DEFAULT, entityManager); + } +} From 97301c07f067028a68485e3f8922cff993358ab4 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:52:51 +0900 Subject: [PATCH 038/156] =?UTF-8?q?[FEAT]=20QCommentDaoFactory=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/dao/QCommentDaoFactory.java | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/dao/QCommentDaoFactory.java diff --git a/src/main/java/ject/componote/domain/comment/dao/QCommentDaoFactory.java b/src/main/java/ject/componote/domain/comment/dao/QCommentDaoFactory.java new file mode 100644 index 0000000..ac4f012 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/dao/QCommentDaoFactory.java @@ -0,0 +1,63 @@ +package ject.componote.domain.comment.dao; + +import com.querydsl.core.types.ConstructorExpression; +import com.querydsl.core.types.Projections; +import ject.componote.domain.comment.domain.QComment; +import org.springframework.stereotype.Component; + +import static ject.componote.domain.auth.domain.QMember.member; +import static ject.componote.domain.comment.domain.QComment.comment; +import static ject.componote.domain.comment.domain.QCommentLike.commentLike; +import static ject.componote.domain.component.domain.QComponent.component; + +@Component +public class QCommentDaoFactory { + public ConstructorExpression createForComponent() { + return Projections.constructor( + CommentFindByComponentDao.class, + member.id, + member.nickname, + member.profileImage, + member.job, + comment.id, + comment.parentId, + comment.image, + comment.content, + comment.createdAt, + comment.likeCount, + null, + comment.parentId.isNotNull() + ); + } + + public ConstructorExpression createForComponentWithLikeStatus() { + return Projections.constructor( + CommentFindByComponentDao.class, + member.id, + member.nickname, + member.profileImage, + member.job, + comment.id, + comment.parentId, + comment.image, + comment.content, + comment.createdAt, + comment.likeCount, + commentLike.isNotNull(), + comment.parentId.isNotNull() + ); + } + + public ConstructorExpression createForMember(final QComment parent) { + return Projections.constructor( + CommentFindByMemberDao.class, + comment.id, + comment.parentId, + component.summary.title, + parent.content, + comment.content, + comment.createdAt, + comment.parentId.isNotNull() + ); + } +} From ed039bb3d46e40e5d11bb1062801fbc01371ff17 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:52:59 +0900 Subject: [PATCH 039/156] =?UTF-8?q?[FEAT]=20OffensiveCommentException=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/error/OffensiveCommentException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/error/OffensiveCommentException.java diff --git a/src/main/java/ject/componote/domain/comment/error/OffensiveCommentException.java b/src/main/java/ject/componote/domain/comment/error/OffensiveCommentException.java new file mode 100644 index 0000000..973f72e --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/error/OffensiveCommentException.java @@ -0,0 +1,9 @@ +package ject.componote.domain.comment.error; + +import org.springframework.http.HttpStatus; + +public class OffensiveCommentException extends CommentException { + public OffensiveCommentException() { + super("적절하지 않은 댓글 내용입니다.", HttpStatus.BAD_REQUEST); + } +} From f0c4089435631f0f6ac161b73d12bf39f4261568 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:53:04 +0900 Subject: [PATCH 040/156] =?UTF-8?q?[FEAT]=20NotFoundCommentException=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/error/NotFoundCommentException.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/error/NotFoundCommentException.java diff --git a/src/main/java/ject/componote/domain/comment/error/NotFoundCommentException.java b/src/main/java/ject/componote/domain/comment/error/NotFoundCommentException.java new file mode 100644 index 0000000..49e8e68 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/error/NotFoundCommentException.java @@ -0,0 +1,13 @@ +package ject.componote.domain.comment.error; + +import org.springframework.http.HttpStatus; + +public class NotFoundCommentException extends CommentException { + public NotFoundCommentException(final Long commentId) { + super("댓글을 찾을 수 없습니다. 댓글 ID: " + commentId, HttpStatus.NOT_FOUND); + } + + public NotFoundCommentException(final Long commentId, final Long memberId) { + super("댓글을 찾을 수 없습니다. 댓글 ID: " + commentId + ", 회원 ID: " + memberId, HttpStatus.NOT_FOUND); + } +} From 62b1a938f24081980150863b72634977c1a9cea9 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:53:13 +0900 Subject: [PATCH 041/156] =?UTF-8?q?[FEAT]=20NoLikedException=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../componote/domain/comment/error/NoLikedException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/error/NoLikedException.java diff --git a/src/main/java/ject/componote/domain/comment/error/NoLikedException.java b/src/main/java/ject/componote/domain/comment/error/NoLikedException.java new file mode 100644 index 0000000..d281b1d --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/error/NoLikedException.java @@ -0,0 +1,9 @@ +package ject.componote.domain.comment.error; + +import org.springframework.http.HttpStatus; + +public class NoLikedException extends CommentException { + public NoLikedException(final Long commentId, final Long memberId) { + super("해당 댓글에 좋아요를 누르지 않았습니다. 댓글 ID:" + commentId + ", 회원 ID: " + memberId, HttpStatus.BAD_REQUEST); + } +} From e52b9f19ad2f1c2af5a834b5f30eb0b2e3e96e5d Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:53:23 +0900 Subject: [PATCH 042/156] =?UTF-8?q?[FEAT]=20InvalidCommentContentException?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../error/InvalidCommentImageExtensionException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/error/InvalidCommentImageExtensionException.java diff --git a/src/main/java/ject/componote/domain/comment/error/InvalidCommentImageExtensionException.java b/src/main/java/ject/componote/domain/comment/error/InvalidCommentImageExtensionException.java new file mode 100644 index 0000000..9953416 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/error/InvalidCommentImageExtensionException.java @@ -0,0 +1,9 @@ +package ject.componote.domain.comment.error; + +import org.springframework.http.HttpStatus; + +public class InvalidCommentImageExtensionException extends CommentException{ + public InvalidCommentImageExtensionException(final String extension) { + super("확장자가 올바르지 않습니다. 입력된 확장자: " + extension, HttpStatus.BAD_REQUEST); + } +} From 374eb1b7d532e16a1cc22aeb73649dd3fefa8e18 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:53:27 +0900 Subject: [PATCH 043/156] =?UTF-8?q?[FEAT]=20InvalidCommentContentException?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/error/InvalidCommentContentException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/error/InvalidCommentContentException.java diff --git a/src/main/java/ject/componote/domain/comment/error/InvalidCommentContentException.java b/src/main/java/ject/componote/domain/comment/error/InvalidCommentContentException.java new file mode 100644 index 0000000..49050d5 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/error/InvalidCommentContentException.java @@ -0,0 +1,9 @@ +package ject.componote.domain.comment.error; + +import org.springframework.http.HttpStatus; + +public class InvalidCommentContentException extends CommentException { + public InvalidCommentContentException() { + super("댓글 내용이 잘못되었습니다.", HttpStatus.BAD_REQUEST); + } +} From d81751410c23e5c938474b5008209017e65b83d1 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:53:48 +0900 Subject: [PATCH 044/156] =?UTF-8?q?[REFACTOR]=20QCommentDaoFactory=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=EB=AA=85=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../componote/domain/comment/dao/CommentQueryDslImpl.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/ject/componote/domain/comment/dao/CommentQueryDslImpl.java b/src/main/java/ject/componote/domain/comment/dao/CommentQueryDslImpl.java index 4502bc6..9b48d02 100644 --- a/src/main/java/ject/componote/domain/comment/dao/CommentQueryDslImpl.java +++ b/src/main/java/ject/componote/domain/comment/dao/CommentQueryDslImpl.java @@ -20,13 +20,13 @@ public class CommentQueryDslImpl implements CommentQueryDsl { private static final QComment PARENT = new QComment("parent"); private final JPAQueryFactory queryFactory; - private final QCommentDtoFactory qCommentDtoFactory; + private final QCommentDaoFactory qCommentDaoFactory; @Override public Page findAllByComponentIdWithPagination(final Long componentId, final Pageable pageable) { final BooleanExpression predicate = eqExpression(comment.componentId, componentId); final JPAQuery countQuery = createCountQuery(predicate); - final JPAQuery baseQuery = queryFactory.select(qCommentDtoFactory.createForComponent()) + final JPAQuery baseQuery = queryFactory.select(qCommentDaoFactory.createForComponent()) .from(comment) .innerJoin(member).on(eqExpression(member.id, comment.memberId)) .where(predicate); @@ -37,7 +37,7 @@ public Page findAllByComponentIdWithPagination(final public Page findAllByComponentIdWithLikeStatusAndPagination(final Long componentId, final Long memberId, final Pageable pageable) { final BooleanExpression predicate = eqExpression(comment.componentId, componentId); final JPAQuery countQuery = createCountQuery(predicate); - final JPAQuery baseQuery = queryFactory.select(qCommentDtoFactory.createForComponentWithLikeStatus()) + final JPAQuery baseQuery = queryFactory.select(qCommentDaoFactory.createForComponentWithLikeStatus()) .from(comment) .innerJoin(member).on(eqExpression(member.id, comment.memberId)) .innerJoin(commentLike).on(eqExpression(commentLike.commentId, component.id).and(eqExpression(commentLike.memberId, memberId))) @@ -49,7 +49,7 @@ public Page findAllByComponentIdWithLikeStatusAndPagi public Page findAllByMemberIdWithPagination(final Long memberId, final Pageable pageable) { final BooleanExpression predicate = eqExpression(comment.memberId, memberId); final JPAQuery countQuery = createCountQuery(predicate); - final JPAQuery baseQuery = queryFactory.select(qCommentDtoFactory.createForMember(PARENT)) + final JPAQuery baseQuery = queryFactory.select(qCommentDaoFactory.createForMember(PARENT)) .from(comment) .innerJoin(PARENT).on(eqExpression(PARENT.id, comment.parentId)) .innerJoin(component).on(eqExpression(component.id, comment.componentId)) From 82985f374798bcbec95766cf432c8c0a0048bdd6 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:53:58 +0900 Subject: [PATCH 045/156] =?UTF-8?q?[REFACTOR]=20CommentLikeEventListener?= =?UTF-8?q?=20=EC=A3=BC=EC=84=9D=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/application/CommentLikeEventListener.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ject/componote/domain/comment/application/CommentLikeEventListener.java b/src/main/java/ject/componote/domain/comment/application/CommentLikeEventListener.java index 4a776b5..b4fec93 100644 --- a/src/main/java/ject/componote/domain/comment/application/CommentLikeEventListener.java +++ b/src/main/java/ject/componote/domain/comment/application/CommentLikeEventListener.java @@ -25,7 +25,7 @@ public class CommentLikeEventListener { @Transactional public void handleCommentLikeEvent(final CommentLikeEvent event) { final Long commentId = event.commentId(); - final Comment comment = findCommentById(commentId); // 내 댓글에 좋아요를 달 수 있는지? + final Comment comment = findCommentById(commentId); comment.increaseLikeCount(); final Long memberId = event.memberId(); @@ -38,7 +38,7 @@ public void handleCommentLikeEvent(final CommentLikeEvent event) { @Transactional public void handleCommentUnlikeEvent(final CommentUnlikeEvent event) { final Long commentId = event.commentId(); - final Comment comment = findCommentById(commentId); // 내 댓글에 좋아요를 달 수 있는지? + final Comment comment = findCommentById(commentId); comment.decreaseLikeCount(); final Long memberId = event.memberId(); From a72d85ae2a7f03ce0d7a469e6015d0d138fcc962 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:54:14 +0900 Subject: [PATCH 046/156] =?UTF-8?q?[REFACTOR]=20PageResponse=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../componote/domain/common/dto/response/PageResponse.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/ject/componote/domain/common/dto/response/PageResponse.java b/src/main/java/ject/componote/domain/common/dto/response/PageResponse.java index 13edacf..3bd6d72 100644 --- a/src/main/java/ject/componote/domain/common/dto/response/PageResponse.java +++ b/src/main/java/ject/componote/domain/common/dto/response/PageResponse.java @@ -12,12 +12,14 @@ @Getter @ToString public class PageResponse { - private final int totalCount; + private final boolean hasNext; + private final long totalElements; + private final int totalPages; private final List content; private final int pageNumber; private final int pageSize; public static PageResponse from(final Page page) { - return new PageResponse<>(page.getTotalPages(), page.getContent(), page.getNumber(), page.getSize()); + return new PageResponse<>(page.hasNext(), page.getTotalElements(), page.getTotalPages(), page.getContent(), page.getNumber(), page.getSize()); } } From c94c7c383f2de4eed5e6be4adc46cfb9311d9805 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:54:25 +0900 Subject: [PATCH 047/156] =?UTF-8?q?[FEAT]=20CommentService=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/application/CommentService.java | 123 +++++++++++++++++- 1 file changed, 118 insertions(+), 5 deletions(-) diff --git a/src/main/java/ject/componote/domain/comment/application/CommentService.java b/src/main/java/ject/componote/domain/comment/application/CommentService.java index 9cd5bdf..1c3474c 100644 --- a/src/main/java/ject/componote/domain/comment/application/CommentService.java +++ b/src/main/java/ject/componote/domain/comment/application/CommentService.java @@ -1,20 +1,133 @@ package ject.componote.domain.comment.application; import ject.componote.domain.auth.model.AuthPrincipal; -import ject.componote.domain.comment.domain.CommentRepository; +import ject.componote.domain.comment.dao.CommentFindByComponentDao; +import ject.componote.domain.comment.dao.CommentLikeRepository; +import ject.componote.domain.comment.dao.CommentRepository; +import ject.componote.domain.comment.domain.Comment; import ject.componote.domain.comment.dto.create.request.CommentCreateRequest; import ject.componote.domain.comment.dto.create.response.CommentCreateResponse; +import ject.componote.domain.comment.dto.find.response.CommentFindByComponentResponse; +import ject.componote.domain.comment.dto.find.response.CommentFindByMemberResponse; +import ject.componote.domain.comment.dto.like.event.CommentLikeEvent; +import ject.componote.domain.comment.dto.like.event.CommentUnlikeEvent; +import ject.componote.domain.comment.dto.update.request.CommentUpdateRequest; +import ject.componote.domain.comment.error.AlreadyLikedException; +import ject.componote.domain.comment.error.NoLikedException; +import ject.componote.domain.comment.error.NotFoundCommentException; +import ject.componote.domain.comment.error.NotFoundParentCommentException; +import ject.componote.domain.comment.model.CommentContent; +import ject.componote.domain.comment.model.CommentImage; +import ject.componote.domain.comment.validation.CommenterValidation; +import ject.componote.domain.common.dto.response.PageResponse; +import ject.componote.infra.file.application.FileService; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @RequiredArgsConstructor @Service public class CommentService { + private final ApplicationEventPublisher eventPublisher; private final CommentRepository commentRepository; + private final CommentLikeRepository commentLikeRepository; + private final FileService fileService; - // 비동기로? - public CommentCreateResponse create(final AuthPrincipal authPrincipal, - final CommentCreateRequest request) { - return null; + public CommentCreateResponse create(final AuthPrincipal authPrincipal, final CommentCreateRequest request) { + if (isReply(request)) { + validateParentId(request.parentId()); + } + + final CommentImage commentImage = CommentImage.from(request.imageObjectKey()); + final CommentCreationStrategy creationStrategy = CommentCreationStrategy.findByRequest(request); + final Comment comment = commentRepository.save(creationStrategy.createComment(request, authPrincipal.id(), commentImage)); + + fileService.moveImage(commentImage.getImage()); + return CommentCreateResponse.from(comment); + } + + public PageResponse getCommentsByMemberId(final AuthPrincipal authPrincipal, + final Pageable pageable) { + final Page page = commentRepository.findAllByMemberIdWithPagination(authPrincipal.id(), pageable) + .map(CommentFindByMemberResponse::from); + return PageResponse.from(page); + } + + public PageResponse getCommentsByComponentId(final AuthPrincipal authPrincipal, + final Long componentId, + final Pageable pageable) { + final Page page = findCommentsByComponentId(authPrincipal, componentId, pageable) + .map(CommentFindByComponentResponse::from); + return PageResponse.from(page); + } + + @CommenterValidation + public void update(final AuthPrincipal authPrincipal, final Long commentId, final CommentUpdateRequest commentUpdateRequest) { + final Comment comment = findCommentByIdAndMemberId(commentId, authPrincipal.id()); + + final CommentImage image = CommentImage.from(commentUpdateRequest.imageObjectKey()); + if (!comment.equalsImage(image)) { + fileService.moveImage(image.getImage()); + } + + final CommentContent content = CommentContent.from(commentUpdateRequest.content()); // 가비지가 발생하지 않을까? + comment.update(content, image); + + commentRepository.save(comment); + } + + @CommenterValidation + public void delete(final AuthPrincipal authPrincipal, final Long commentId) { + commentRepository.deleteByIdAndMemberId(commentId, authPrincipal.id()); + } + + public void likeComment(final AuthPrincipal authPrincipal, final Long commentId) { + final Long memberId = authPrincipal.id(); + if (isAlreadyLiked(commentId, memberId)) { + throw new AlreadyLikedException(commentId, memberId); + } + + eventPublisher.publishEvent(CommentLikeEvent.of(authPrincipal, commentId)); + } + + public void unlikeComment(final AuthPrincipal authPrincipal, final Long commentId) { + final Long memberId = authPrincipal.id(); + if (!isAlreadyLiked(commentId, memberId)) { + throw new NoLikedException(commentId, memberId); + } + + eventPublisher.publishEvent(CommentUnlikeEvent.of(authPrincipal, commentId)); + } + + private Comment findCommentByIdAndMemberId(final Long commentId, final Long memberId) { + return commentRepository.findByIdAndMemberId(commentId, memberId) + .orElseThrow(() -> new NotFoundCommentException(commentId, memberId)); + } + + private Page findCommentsByComponentId(final AuthPrincipal authPrincipal, + final Long componentId, + final Pageable pageable) { + if (authPrincipal == null) { + return commentRepository.findAllByComponentIdWithPagination(componentId, pageable); + } + + final Long memberId = authPrincipal.id(); + return commentRepository.findAllByComponentIdWithLikeStatusAndPagination(componentId, memberId, pageable); + } + + private boolean isReply(final CommentCreateRequest request) { + return request.parentId() != null; + } + + private void validateParentId(final Long parentId) { + if (!commentRepository.existsById(parentId)) { + throw new NotFoundParentCommentException(parentId); + } + } + + private boolean isAlreadyLiked(final Long commentId, final Long memberId) { + return commentLikeRepository.existsByCommentIdAndMemberId(commentId, memberId); } } From f38bb29de034569059aa316a1b4d71b84be1591c Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:54:33 +0900 Subject: [PATCH 048/156] =?UTF-8?q?[FEAT]=20CommentController=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/api/CommentController.java | 92 ++++++++++++++++++- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/src/main/java/ject/componote/domain/comment/api/CommentController.java b/src/main/java/ject/componote/domain/comment/api/CommentController.java index b4713ed..c630090 100644 --- a/src/main/java/ject/componote/domain/comment/api/CommentController.java +++ b/src/main/java/ject/componote/domain/comment/api/CommentController.java @@ -1,12 +1,100 @@ package ject.componote.domain.comment.api; +import jakarta.validation.Valid; +import ject.componote.domain.auth.model.AuthPrincipal; +import ject.componote.domain.auth.model.Authenticated; +import ject.componote.domain.auth.model.User; +import ject.componote.domain.comment.application.CommentService; +import ject.componote.domain.comment.dto.create.request.CommentCreateRequest; +import ject.componote.domain.comment.dto.create.response.CommentCreateResponse; +import ject.componote.domain.comment.dto.find.response.CommentFindByComponentResponse; +import ject.componote.domain.comment.dto.find.response.CommentFindByMemberResponse; +import ject.componote.domain.comment.dto.update.request.CommentUpdateRequest; +import ject.componote.domain.common.dto.response.PageResponse; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; -@RequestMapping("/comments") @RequiredArgsConstructor @RestController public class CommentController { + private static final int DEFAULT_MEMBER_COMMENT_PAGE_SIZE = 8; + private static final int DEFAULT_COMPONENT_COMMENT_PAGE_SIZE = 5; + private final CommentService commentService; + + @PostMapping("/comments") + @User + public ResponseEntity create( + @Authenticated final AuthPrincipal authPrincipal, + @RequestBody @Valid final CommentCreateRequest commentCreateRequest + ) { + final CommentCreateResponse commentCreateResponse = commentService.create(authPrincipal, commentCreateRequest); + return ResponseEntity.ok(commentCreateResponse); + } + + @GetMapping("/members/comments") + @User + public ResponseEntity> getCommentsByMemberId( + @Authenticated final AuthPrincipal authPrincipal, + @PageableDefault(size = DEFAULT_MEMBER_COMMENT_PAGE_SIZE) final Pageable pageable + ) { + final PageResponse pageResponse = commentService.getCommentsByMemberId(authPrincipal, pageable); + return ResponseEntity.ok(pageResponse); + } + + @GetMapping("/components/{componentId}/comments") + public ResponseEntity> getCommentsByComponentId( + @Authenticated final AuthPrincipal authPrincipal, + @PathVariable("componentId") final Long componentId, + @PageableDefault(size = DEFAULT_COMPONENT_COMMENT_PAGE_SIZE) final Pageable pageable + ) { + final PageResponse pageResponse = commentService.getCommentsByComponentId(authPrincipal, componentId, pageable); + return ResponseEntity.ok(pageResponse); + } + + @PutMapping("/comments/{commentId}") + @User + public ResponseEntity update(@Authenticated final AuthPrincipal authPrincipal, + @PathVariable("commentId") final Long commentId, + @RequestBody @Valid final CommentUpdateRequest commentUpdateRequest) { + commentService.update(authPrincipal, commentId, commentUpdateRequest); + return ResponseEntity.noContent() + .build(); + } + + @PostMapping("/comments/{commentId}/likes") + @User + public ResponseEntity likeComment(@Authenticated final AuthPrincipal authPrincipal, + @PathVariable("commentId") final Long commentId) { + commentService.likeComment(authPrincipal, commentId); + return ResponseEntity.noContent() + .build(); + } + + @DeleteMapping("/comments/{commentId}/likes") + @User + public ResponseEntity unlikeComment(@Authenticated final AuthPrincipal authPrincipal, + @PathVariable("commentId") final Long commentId) { + commentService.unlikeComment(authPrincipal, commentId); + return ResponseEntity.noContent() + .build(); + } + + @DeleteMapping("/comments/{commentId}") + @User + public ResponseEntity delete(@Authenticated final AuthPrincipal authPrincipal, + @PathVariable("commentId") final Long commentId) { + commentService.delete(authPrincipal, commentId); + return ResponseEntity.noContent() + .build(); + } } From eef19acf36788072d5c203aaee28b592e16008b2 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 12:55:55 +0900 Subject: [PATCH 049/156] =?UTF-8?q?[REFACTOR]=20CommentCreationStrategy=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../create/CommentCreationStrategy.java | 47 ------------------- 1 file changed, 47 deletions(-) delete mode 100644 src/main/java/ject/componote/domain/comment/application/create/CommentCreationStrategy.java diff --git a/src/main/java/ject/componote/domain/comment/application/create/CommentCreationStrategy.java b/src/main/java/ject/componote/domain/comment/application/create/CommentCreationStrategy.java deleted file mode 100644 index af143cb..0000000 --- a/src/main/java/ject/componote/domain/comment/application/create/CommentCreationStrategy.java +++ /dev/null @@ -1,47 +0,0 @@ -package ject.componote.domain.comment.application.create; - -import ject.componote.domain.comment.domain.Comment; -import ject.componote.domain.comment.dto.create.request.CommentCreateRequest; -import ject.componote.domain.comment.error.InvalidCommentCreateStrategyException; -import lombok.RequiredArgsConstructor; - -import java.util.Arrays; -import java.util.function.Predicate; - -@RequiredArgsConstructor -public enum CommentCreationStrategy { - GENERAL_WITHOUT_IMAGE( - request -> request.parentId() == null && request.imageTempKey() == null, - (request, memberId, permanentObjectKey) -> - Comment.createWithoutImage(request.componentId(), memberId, request.content()) - ), - GENERAL_WITH_IMAGE( - request -> request.parentId() == null && request.imageTempKey() != null, - (request, memberId, permanentObjectKey) -> - Comment.createWithImage(request.componentId(), memberId, request.content(), permanentObjectKey) - ), - REPLY_WITHOUT_IMAGE( - request -> request.parentId() != null && request.imageTempKey() == null, - (request, memberId, permanentObjectKey) -> - Comment.createReplyWithoutImage(request.componentId(), memberId, request.parentId(), request.content()) - ), - REPLY_WITH_IMAGE( - request -> request.parentId() != null && request.imageTempKey() != null, - (request, memberId, permanentObjectKey) -> - Comment.createReplyWithImage(request.componentId(), memberId, request.parentId(), request.content(), permanentObjectKey) - ); - - private final Predicate condition; - private final CommentCreationFunction creationFunction; - - public static CommentCreationStrategy findByRequest(final CommentCreateRequest request) { - return Arrays.stream(values()) - .filter(type -> type.condition.test(request)) - .findFirst() - .orElseThrow(InvalidCommentCreateStrategyException::new); - } - - public Comment createComment(final CommentCreateRequest request, final Long memberId, final String permanentObjectKey) { - return creationFunction.create(request, memberId, permanentObjectKey); - } -} From a2c897753aa54bc41ebf448f2cf6c6615d360e02 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 17:42:19 +0900 Subject: [PATCH 050/156] =?UTF-8?q?[FEAT]=20Count=EC=97=90=20Compable=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ject/componote/domain/common/model/Count.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/ject/componote/domain/common/model/Count.java b/src/main/java/ject/componote/domain/common/model/Count.java index 227bf71..4128522 100644 --- a/src/main/java/ject/componote/domain/common/model/Count.java +++ b/src/main/java/ject/componote/domain/common/model/Count.java @@ -7,7 +7,7 @@ @Getter @EqualsAndHashCode @ToString -public class Count { +public class Count implements Comparable { private Long value; private Count(final Long value) { @@ -36,4 +36,9 @@ private void validateCount(final Long value) { throw new IllegalArgumentException("Value must be greater than zero"); } } + + @Override + public int compareTo(final Count other) { + return Long.compare(this.value, other.value); + } } From d793dacd55dc3b6e78d9263f3632a71fe8088636 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 17:42:42 +0900 Subject: [PATCH 051/156] =?UTF-8?q?[REFACTOR]=20Comment=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=A0=84=EB=9E=B5=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/CommentCreationStrategy.java | 24 ++++++++----------- .../comment/application/CommentService.java | 9 ++++--- .../domain/comment/domain/Comment.java | 12 +++++----- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/main/java/ject/componote/domain/comment/application/CommentCreationStrategy.java b/src/main/java/ject/componote/domain/comment/application/CommentCreationStrategy.java index cecb5fc..4d7c2c1 100644 --- a/src/main/java/ject/componote/domain/comment/application/CommentCreationStrategy.java +++ b/src/main/java/ject/componote/domain/comment/application/CommentCreationStrategy.java @@ -3,7 +3,6 @@ import ject.componote.domain.comment.domain.Comment; import ject.componote.domain.comment.dto.create.request.CommentCreateRequest; import ject.componote.domain.comment.error.InvalidCommentCreateStrategyException; -import ject.componote.domain.comment.model.CommentImage; import lombok.RequiredArgsConstructor; import java.util.Arrays; @@ -13,41 +12,38 @@ public enum CommentCreationStrategy { GENERAL_WITHOUT_IMAGE( request -> request.parentId() == null && request.imageObjectKey() == null, - (request, memberId, image) -> + (request, memberId) -> Comment.createWithoutImage(request.componentId(), memberId, request.content()) ), GENERAL_WITH_IMAGE( request -> request.parentId() == null && request.imageObjectKey() != null, - (request, memberId, image) -> - Comment.createWithImage(request.componentId(), memberId, request.content(), image) + (request, memberId) -> + Comment.createWithImage(request.componentId(), memberId, request.content(), request.imageObjectKey()) ), REPLY_WITHOUT_IMAGE( request -> request.parentId() != null && request.imageObjectKey() == null, - (request, memberId, image) -> + (request, memberId) -> Comment.createReplyWithoutImage(request.componentId(), memberId, request.parentId(), request.content()) ), REPLY_WITH_IMAGE( request -> request.parentId() != null && request.imageObjectKey() != null, - (request, memberId, image) -> - Comment.createReplyWithImage(request.componentId(), memberId, request.parentId(), request.content(), image) + (request, memberId) -> + Comment.createReplyWithImage(request.componentId(), memberId, request.parentId(), request.content(), request.imageObjectKey()) ); private final Predicate condition; private final CommentCreationFunction creationFunction; - public static CommentCreationStrategy findByRequest(final CommentCreateRequest request) { + public static Comment createBy(final CommentCreateRequest request, final Long memberId) { return Arrays.stream(values()) .filter(type -> type.condition.test(request)) .findFirst() - .orElseThrow(InvalidCommentCreateStrategyException::new); - } - - public Comment createComment(final CommentCreateRequest request, final Long memberId, final CommentImage image) { - return creationFunction.create(request, memberId, image); + .orElseThrow(InvalidCommentCreateStrategyException::new) + .creationFunction.create(request, memberId); } @FunctionalInterface private interface CommentCreationFunction { - Comment create(final CommentCreateRequest request, final Long memberId, final CommentImage image); + Comment create(final CommentCreateRequest request, final Long memberId); } } diff --git a/src/main/java/ject/componote/domain/comment/application/CommentService.java b/src/main/java/ject/componote/domain/comment/application/CommentService.java index 1c3474c..9b0a79f 100644 --- a/src/main/java/ject/componote/domain/comment/application/CommentService.java +++ b/src/main/java/ject/componote/domain/comment/application/CommentService.java @@ -40,11 +40,10 @@ public CommentCreateResponse create(final AuthPrincipal authPrincipal, final Com validateParentId(request.parentId()); } - final CommentImage commentImage = CommentImage.from(request.imageObjectKey()); - final CommentCreationStrategy creationStrategy = CommentCreationStrategy.findByRequest(request); - final Comment comment = commentRepository.save(creationStrategy.createComment(request, authPrincipal.id(), commentImage)); - - fileService.moveImage(commentImage.getImage()); + final Comment comment = commentRepository.save( + CommentCreationStrategy.createBy(request, authPrincipal.id()) + ); + fileService.moveImage(comment.getImage().getImage()); return CommentCreateResponse.from(comment); } diff --git a/src/main/java/ject/componote/domain/comment/domain/Comment.java b/src/main/java/ject/componote/domain/comment/domain/Comment.java index 5ea0002..71c992a 100644 --- a/src/main/java/ject/componote/domain/comment/domain/Comment.java +++ b/src/main/java/ject/componote/domain/comment/domain/Comment.java @@ -52,18 +52,18 @@ public class Comment extends BaseEntity { @Column(name = "parent_id", nullable = true) private Long parentId; - private Comment(final Long componentId, final Long memberId, final Long parentId, final String content, final CommentImage image) { + private Comment(final Long componentId, final Long memberId, final Long parentId, final String content, final String objectKey) { this.componentId = componentId; this.memberId = memberId; this.parentId = parentId; this.content = CommentContent.from(content); - this.image = image; + this.image = CommentImage.from(objectKey); this.likeCount = Count.create(); this.reportCount = Count.create(); } - public static Comment createWithImage(final Long componentId, final Long memberId, final String content, final CommentImage image) { - return new Comment(componentId, memberId, null, content, image); + public static Comment createWithImage(final Long componentId, final Long memberId, final String content, final String objectKey) { + return new Comment(componentId, memberId, null, content, objectKey); } public static Comment createWithoutImage(final Long componentId, final Long memberId, final String content) { @@ -74,8 +74,8 @@ public static Comment createReplyWithoutImage(final Long componentId, final Long return new Comment(componentId, memberId, parentId, content, null); } - public static Comment createReplyWithImage(final Long componentId, final Long memberId, final Long parentId, final String content, final CommentImage image) { - return new Comment(componentId, memberId, parentId, content, image); + public static Comment createReplyWithImage(final Long componentId, final Long memberId, final Long parentId, final String content, final String objectKey) { + return new Comment(componentId, memberId, parentId, content, objectKey); } public void increaseLikeCount() { From daac2d30d6e2b84bdb4a2ebf5ec04a2639c723b7 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 17:43:03 +0900 Subject: [PATCH 052/156] =?UTF-8?q?[FEAT]=20CommentImage=20from()=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B2=80=EC=A6=9D=20=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ject/componote/domain/comment/model/CommentImage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ject/componote/domain/comment/model/CommentImage.java b/src/main/java/ject/componote/domain/comment/model/CommentImage.java index 6b46ca6..327aa36 100644 --- a/src/main/java/ject/componote/domain/comment/model/CommentImage.java +++ b/src/main/java/ject/componote/domain/comment/model/CommentImage.java @@ -23,7 +23,7 @@ private CommentImage(final BaseImage image) { } public static CommentImage from(final String objectKey) { - if (objectKey == null) { + if (objectKey == null || objectKey.isEmpty()) { return new CommentImage(BaseImage.getEmptyInstance()); } From 28e3365eb538b19b34de333e81bba1977d9f5f5c Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 17:43:33 +0900 Subject: [PATCH 053/156] =?UTF-8?q?[FEAT]=20PageResponse=EC=97=90=20@Equal?= =?UTF-8?q?sAndHashCode=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ject/componote/domain/common/dto/response/PageResponse.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/ject/componote/domain/common/dto/response/PageResponse.java b/src/main/java/ject/componote/domain/common/dto/response/PageResponse.java index 3bd6d72..a293d91 100644 --- a/src/main/java/ject/componote/domain/common/dto/response/PageResponse.java +++ b/src/main/java/ject/componote/domain/common/dto/response/PageResponse.java @@ -2,6 +2,7 @@ import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.ToString; import org.springframework.data.domain.Page; @@ -9,6 +10,7 @@ import java.util.List; @AllArgsConstructor(access = AccessLevel.PRIVATE) +@EqualsAndHashCode @Getter @ToString public class PageResponse { From c073e245ae50632fcab390b83c1095fb8ba21a51 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 17:44:06 +0900 Subject: [PATCH 054/156] =?UTF-8?q?[REFACTOR]=20CommentFindByComponentResp?= =?UTF-8?q?onse=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/find/response/CommentFindByMemberResponse.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByMemberResponse.java b/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByMemberResponse.java index 37c82bb..6895e4e 100644 --- a/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByMemberResponse.java +++ b/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByMemberResponse.java @@ -5,13 +5,16 @@ import java.time.LocalDateTime; -public record CommentFindByMemberResponse(Long id, @JsonInclude(JsonInclude.Include.NON_NULL) Long parentId, String componentTitle, @JsonInclude(JsonInclude.Include.NON_NULL) String parentContent, String content, LocalDateTime createdAt, boolean isReply) { +public record CommentFindByMemberResponse(Long id, @JsonInclude(JsonInclude.Include.NON_NULL) Long parentId, + String componentTitle, + @JsonInclude(JsonInclude.Include.NON_NULL) String parentContent, + String content, LocalDateTime createdAt, boolean isReply) { public static CommentFindByMemberResponse from(final CommentFindByMemberDao commentFindByMemberDao) { return new CommentFindByMemberResponse( commentFindByMemberDao.id(), - commentFindByMemberDao.parentId(), + commentFindByMemberDao.isReply() ? commentFindByMemberDao.parentId() : null, commentFindByMemberDao.componentTitle(), - commentFindByMemberDao.parentContent().getValue(), + commentFindByMemberDao.isReply() ? commentFindByMemberDao.parentContent().getValue() : null, commentFindByMemberDao.content().getValue(), commentFindByMemberDao.createdAt(), commentFindByMemberDao.isReply() From abb2964e0ee8cad5796832c7bd6a7f89a9d6caf6 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 17:44:15 +0900 Subject: [PATCH 055/156] =?UTF-8?q?[REFACTOR]=20CommentFindByComponentResp?= =?UTF-8?q?onse=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/CommentFindByComponentResponse.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByComponentResponse.java b/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByComponentResponse.java index e9e89f0..249b2cd 100644 --- a/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByComponentResponse.java +++ b/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByComponentResponse.java @@ -16,14 +16,23 @@ public record CommentFindByComponentResponse( boolean isReply) { public static CommentFindByComponentResponse from(final CommentFindByComponentDao dto) { return new CommentFindByComponentResponse( - new CommentProfileResponse(dto.memberId(), dto.nickname().getValue(), dto.profileImage().getObjectKey(), dto.job().name()), + createProfileResponse(dto), dto.commentId(), dto.parentId(), - dto.image().toUrl(), + dto.commentImage().getImage().toUrl(), dto.content().getValue(), dto.createdAt(), dto.likeCount().getValue(), dto.isReply() ); } + + private static CommentProfileResponse createProfileResponse(final CommentFindByComponentDao dto) { + return new CommentProfileResponse( + dto.memberId(), + dto.nickname().getValue(), + dto.profileImage().getImage().toUrl(), + dto.job().name() + ); + } } From 7eda119e5f05608aa4a4a913931f4606b000e60c Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 17:44:21 +0900 Subject: [PATCH 056/156] =?UTF-8?q?[REFACTOR]=20CommentFindByComponentDao?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/dao/CommentFindByComponentDao.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/ject/componote/domain/comment/dao/CommentFindByComponentDao.java b/src/main/java/ject/componote/domain/comment/dao/CommentFindByComponentDao.java index a4b87e0..884d1ca 100644 --- a/src/main/java/ject/componote/domain/comment/dao/CommentFindByComponentDao.java +++ b/src/main/java/ject/componote/domain/comment/dao/CommentFindByComponentDao.java @@ -2,11 +2,12 @@ import ject.componote.domain.auth.domain.Job; import ject.componote.domain.auth.model.Nickname; +import ject.componote.domain.auth.model.ProfileImage; import ject.componote.domain.comment.model.CommentContent; -import ject.componote.domain.common.model.BaseImage; +import ject.componote.domain.comment.model.CommentImage; import ject.componote.domain.common.model.Count; import java.time.LocalDateTime; -public record CommentFindByComponentDao(Long memberId, Nickname nickname, BaseImage profileImage, Job job, Long commentId, Long parentId, BaseImage image, CommentContent content, LocalDateTime createdAt, Count likeCount, boolean isLiked, boolean isReply) { +public record CommentFindByComponentDao(Long memberId, Nickname nickname, ProfileImage profileImage, Job job, Long commentId, Long parentId, CommentImage commentImage, CommentContent content, LocalDateTime createdAt, Count likeCount, boolean isLiked, boolean isReply) { } From abd78159f93452c162b1133bde371521d119f3bf Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 17:44:51 +0900 Subject: [PATCH 057/156] =?UTF-8?q?[TEST]=20CommentContent=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/model/CommentContentTest.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/test/java/ject/componote/domain/comment/model/CommentContentTest.java diff --git a/src/test/java/ject/componote/domain/comment/model/CommentContentTest.java b/src/test/java/ject/componote/domain/comment/model/CommentContentTest.java new file mode 100644 index 0000000..a0712c2 --- /dev/null +++ b/src/test/java/ject/componote/domain/comment/model/CommentContentTest.java @@ -0,0 +1,37 @@ +package ject.componote.domain.comment.model; + +import ject.componote.domain.comment.error.BlankCommentException; +import ject.componote.domain.comment.error.ExceedCommentLengthException; +import ject.componote.domain.comment.error.OffensiveCommentException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class CommentContentTest { + @Test + @DisplayName("댓글 내용이 너무 긴 경우 예외 발생") + public void exceedLength() throws Exception { + final String value = "H".repeat(1_500); + assertThatThrownBy(() -> CommentContent.from(value)) + .isInstanceOf(ExceedCommentLengthException.class); + } + + @Test + @DisplayName("댓글 내용이 없는 경우 예외 발생") + public void isNullOrEmpty() throws Exception { + final String value = ""; + assertThatThrownBy(() -> CommentContent.from(value)) + .isInstanceOf(BlankCommentException.class); + } + + @ParameterizedTest + @DisplayName("비속어 필터링") + @ValueSource(strings = {"씨발", "개새끼"}) + public void badWordFiltering(final String value) throws Exception { + assertThatThrownBy(() -> CommentContent.from(value)) + .isInstanceOf(OffensiveCommentException.class); + } +} \ No newline at end of file From aa02ec6f65ee115d91e6728f870369e3ebca9755 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 17:44:59 +0900 Subject: [PATCH 058/156] =?UTF-8?q?[TEST]=20CommentCreationStrategy=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommentCreationStrategyTest.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/test/java/ject/componote/domain/comment/application/CommentCreationStrategyTest.java diff --git a/src/test/java/ject/componote/domain/comment/application/CommentCreationStrategyTest.java b/src/test/java/ject/componote/domain/comment/application/CommentCreationStrategyTest.java new file mode 100644 index 0000000..0289dc8 --- /dev/null +++ b/src/test/java/ject/componote/domain/comment/application/CommentCreationStrategyTest.java @@ -0,0 +1,32 @@ +package ject.componote.domain.comment.application; + +import ject.componote.domain.comment.domain.Comment; +import ject.componote.domain.comment.dto.create.request.CommentCreateRequest; +import ject.componote.fixture.CommentFixture; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import static org.assertj.core.api.Assertions.assertThat; + +class CommentCreationStrategyTest { + @ParameterizedTest + @DisplayName("요청값에 알맞는 댓글 엔티티 생성") + @EnumSource(CommentFixture.class) + public void createBy(final CommentFixture fixture) throws Exception { + // given + final Comment comment = fixture.생성(); + final CommentCreateRequest createRequest = fixture.toCreateRequest(); + final Long memberId = comment.getMemberId(); + + // when + final Comment createdComment = CommentCreationStrategy.createBy(createRequest, memberId); + + // then + assertThat(createdComment.getContent().getValue()).isEqualTo(createRequest.content()); + assertThat(createdComment.getImage().getImage().getObjectKey()).isEqualTo(createRequest.imageObjectKey()); + assertThat(createdComment.getMemberId()).isEqualTo(memberId); + assertThat(createdComment.getComponentId()).isEqualTo(createRequest.componentId()); + assertThat(createdComment.getParentId()).isEqualTo(createRequest.parentId()); + } +} \ No newline at end of file From ec1ee6f50a3a0672d9c420e9e9d87e5ba9c327c8 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 17:45:05 +0900 Subject: [PATCH 059/156] =?UTF-8?q?[TEST]=20CommenterValidationAspect=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommenterValidationAspectTest.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 src/test/java/ject/componote/domain/comment/validation/CommenterValidationAspectTest.java diff --git a/src/test/java/ject/componote/domain/comment/validation/CommenterValidationAspectTest.java b/src/test/java/ject/componote/domain/comment/validation/CommenterValidationAspectTest.java new file mode 100644 index 0000000..e248428 --- /dev/null +++ b/src/test/java/ject/componote/domain/comment/validation/CommenterValidationAspectTest.java @@ -0,0 +1,69 @@ +package ject.componote.domain.comment.validation; + +import ject.componote.domain.auth.domain.Member; +import ject.componote.domain.auth.model.AuthPrincipal; +import ject.componote.domain.comment.dao.CommentRepository; +import ject.componote.domain.comment.domain.Comment; +import ject.componote.domain.comment.error.NotFoundCommentException; +import ject.componote.fixture.CommentFixture; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static ject.componote.fixture.MemberFixture.KIM; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.doReturn; + +@ExtendWith(MockitoExtension.class) +class CommenterValidationAspectTest { + @Mock + CommentRepository commentRepository; + + @InjectMocks + CommenterValidationAspect commenterValidationAspect; + + final Member member = KIM.생성(1L); + final AuthPrincipal authPrincipal = AuthPrincipal.from(member); + + @ParameterizedTest + @DisplayName("댓글 작성자 검증") + @EnumSource(CommentFixture.class) + public void validate(final CommentFixture fixture) throws Exception { + // given + final Long memberId = member.getId(); + final Comment comment = fixture.생성(memberId); + final Long commentId = comment.getId(); + + // when + doReturn(true).when(commentRepository) + .existsByIdAndMemberId(commentId, memberId); + + // then + assertDoesNotThrow( + () -> commenterValidationAspect.validate(authPrincipal, commentId) + ); + } + + @ParameterizedTest + @DisplayName("댓글 작성자가 아닌 경우 예외 발생") + @EnumSource(CommentFixture.class) + public void validateWhenInvalidMemberId(final CommentFixture fixture) throws Exception { + // given + final Long memberId = member.getId(); + final Comment comment = fixture.생성(); + final Long commentId = comment.getId(); + + // when + doReturn(false).when(commentRepository) + .existsByIdAndMemberId(commentId, memberId); + + // then + assertThatThrownBy(() -> commenterValidationAspect.validate(authPrincipal, commentId)) + .isInstanceOf(NotFoundCommentException.class); + } +} \ No newline at end of file From 687bdfc23de6a6d4cf509ab239c5973d8554b1f1 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 17:45:12 +0900 Subject: [PATCH 060/156] =?UTF-8?q?[TEST]=20CommentFixture=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../componote/fixture/CommentFixture.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/test/java/ject/componote/fixture/CommentFixture.java diff --git a/src/test/java/ject/componote/fixture/CommentFixture.java b/src/test/java/ject/componote/fixture/CommentFixture.java new file mode 100644 index 0000000..ea08881 --- /dev/null +++ b/src/test/java/ject/componote/fixture/CommentFixture.java @@ -0,0 +1,38 @@ +package ject.componote.fixture; + +import ject.componote.domain.comment.application.CommentCreationStrategy; +import ject.componote.domain.comment.domain.Comment; +import ject.componote.domain.comment.dto.create.request.CommentCreateRequest; + +public enum CommentFixture { + 댓글_이미지X(1L, 1L, null, "일반 댓글입니다.", null), + 댓글_이미지O(1L, 1L, null, "일반 댓글입니다.", "comments/image1.jpg"), + 답글_이미지X(1L, 1L, 100L, "답글입니다.", null), + 답글_이미지O(1L, 1L, 100L, "답글입니다.", "comments/image2.jpg"); + + private final Long componentId; + private final Long memberId; + private final Long parentId; + private final String content; + private final String imageObjectKey; + + CommentFixture(Long componentId, Long memberId, Long parentId, String content, String imageObjectKey) { + this.componentId = componentId; + this.memberId = memberId; + this.parentId = parentId; + this.content = content; + this.imageObjectKey = imageObjectKey; + } + + public Comment 생성(final Long memberId) { + return CommentCreationStrategy.createBy(toCreateRequest(), memberId); + } + + public Comment 생성() { + return CommentCreationStrategy.createBy(toCreateRequest(), memberId); + } + + public CommentCreateRequest toCreateRequest() { + return new CommentCreateRequest(imageObjectKey, content, componentId, parentId); + } +} From 92e0ef76e4312f919371092aef799926426bc8ec Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 17:45:19 +0900 Subject: [PATCH 061/156] =?UTF-8?q?[TEST]=20CommentImage=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/model/CommentImageTest.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/test/java/ject/componote/domain/comment/model/CommentImageTest.java diff --git a/src/test/java/ject/componote/domain/comment/model/CommentImageTest.java b/src/test/java/ject/componote/domain/comment/model/CommentImageTest.java new file mode 100644 index 0000000..24b46ac --- /dev/null +++ b/src/test/java/ject/componote/domain/comment/model/CommentImageTest.java @@ -0,0 +1,30 @@ +package ject.componote.domain.comment.model; + +import ject.componote.domain.comment.error.InvalidCommentImageExtensionException; +import ject.componote.domain.common.model.BaseImage; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class CommentImageTest { + @Test + @DisplayName("이미지 ObjectKey가 없는 경우 null 저장") + public void isNullOrEmpty() throws Exception { + final String objectKey = ""; + final CommentImage commentImage = CommentImage.from(objectKey); + assertThat(commentImage).isNotNull(); + assertThat(commentImage.getImage()).isEqualTo(BaseImage.getEmptyInstance()); + } + + @ParameterizedTest + @DisplayName("이미지 ObjectKey 확장자가 잘못된 경우 예외 발생") + @ValueSource(strings = {"hello.jp", "hello.gf"}) + public void invalidExtension(final String objectKey) throws Exception { + assertThatThrownBy(() -> CommentImage.from(objectKey)) + .isInstanceOf(InvalidCommentImageExtensionException.class); + } +} \ No newline at end of file From 1ca122752a6b9e528b0223cf27d1b550a9b836e9 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 17:45:26 +0900 Subject: [PATCH 062/156] =?UTF-8?q?[TEST]=20CommentLikeEventListener=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommentLikeEventListenerTest.java | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/test/java/ject/componote/domain/comment/application/CommentLikeEventListenerTest.java diff --git a/src/test/java/ject/componote/domain/comment/application/CommentLikeEventListenerTest.java b/src/test/java/ject/componote/domain/comment/application/CommentLikeEventListenerTest.java new file mode 100644 index 0000000..e070a04 --- /dev/null +++ b/src/test/java/ject/componote/domain/comment/application/CommentLikeEventListenerTest.java @@ -0,0 +1,119 @@ +package ject.componote.domain.comment.application; + +import ject.componote.domain.auth.domain.Member; +import ject.componote.domain.auth.model.AuthPrincipal; +import ject.componote.domain.comment.dao.CommentLikeRepository; +import ject.componote.domain.comment.dao.CommentRepository; +import ject.componote.domain.comment.domain.Comment; +import ject.componote.domain.comment.dto.like.event.CommentLikeEvent; +import ject.componote.domain.comment.dto.like.event.CommentUnlikeEvent; +import ject.componote.domain.comment.error.NotFoundCommentException; +import ject.componote.domain.common.model.Count; +import ject.componote.fixture.CommentFixture; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static ject.componote.fixture.MemberFixture.KIM; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.doReturn; + +@ExtendWith(MockitoExtension.class) +class CommentLikeEventListenerTest { + @Mock + CommentRepository commentRepository; + + @Mock + CommentLikeRepository commentLikeRepository; + + @InjectMocks + CommentLikeEventListener commentLikeEventListener; + + final Member member = KIM.생성(1L); + final AuthPrincipal authPrincipal = AuthPrincipal.from(member); + + @ParameterizedTest + @DisplayName("댓글 좋아요 이벤트 처리") + @EnumSource(CommentFixture.class) + public void handleCommentLikeEvent(final CommentFixture fixture) throws Exception { + // given + final Comment comment = fixture.생성(); + final Count previousLikeCount = comment.getLikeCount(); + final Long commentId = comment.getId(); + final CommentLikeEvent event = CommentLikeEvent.of(authPrincipal, commentId); + + // when + doReturn(Optional.of(comment)).when(commentRepository) + .findById(commentId); + commentLikeEventListener.handleCommentLikeEvent(event); + + // then + final Count newLikeCount = comment.getLikeCount(); + previousLikeCount.increase(); + assertThat(previousLikeCount).isEqualTo(newLikeCount); + } + + @ParameterizedTest + @DisplayName("댓글 좋아요 이벤트 처리시 댓글 ID가 잘못된 경우 예외 발생") + @EnumSource(CommentFixture.class) + public void handleCommentLikeEventWhenInvalidCommentId(final CommentFixture fixture) throws Exception { + // given + final Comment comment = fixture.생성(); + final Long commentId = comment.getId(); + final CommentLikeEvent event = CommentLikeEvent.of(authPrincipal, commentId); + + // when + doReturn(Optional.empty()).when(commentRepository) + .findById(commentId); + + // then + assertThatThrownBy(() -> commentLikeEventListener.handleCommentLikeEvent(event)) + .isInstanceOf(NotFoundCommentException.class); + } + + @ParameterizedTest + @DisplayName("댓글 좋아요 취소 이벤트 처리") + @EnumSource(CommentFixture.class) + public void handleCommentUnLikeEvent(final CommentFixture fixture) throws Exception { + // given + final Comment comment = fixture.생성(); + final Count previousLikeCount = comment.getLikeCount(); + final Long commentId = comment.getId(); + final CommentUnlikeEvent event = CommentUnlikeEvent.of(authPrincipal, commentId); + + // when + doReturn(Optional.of(comment)).when(commentRepository) + .findById(commentId); + commentLikeEventListener.handleCommentUnlikeEvent(event); + + // then + final Count newLikeCount = comment.getLikeCount(); + previousLikeCount.decrease(); + assertThat(previousLikeCount).isEqualTo(newLikeCount); + } + + @ParameterizedTest + @DisplayName("댓글 좋아요 취소 이벤트 처리시 댓글 ID가 잘못된 경우 예외 발생") + @EnumSource(CommentFixture.class) + public void handleCommentUnlikeEventWhenInvalidCommentId(final CommentFixture fixture) throws Exception { + // given + final Comment comment = fixture.생성(); + final Long commentId = comment.getId(); + final CommentUnlikeEvent event = CommentUnlikeEvent.of(authPrincipal, commentId); + + // when + doReturn(Optional.empty()).when(commentRepository) + .findById(commentId); + + // then + assertThatThrownBy(() -> commentLikeEventListener.handleCommentUnlikeEvent(event)) + .isInstanceOf(NotFoundCommentException.class); + } +} \ No newline at end of file From f8cb41dcafe488ef8fc3be21ca68ce768f5d65c3 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 17:45:59 +0900 Subject: [PATCH 063/156] =?UTF-8?q?[TEST]=20CommentService=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/CommentServiceTest.java | 356 ++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 src/test/java/ject/componote/domain/comment/application/CommentServiceTest.java diff --git a/src/test/java/ject/componote/domain/comment/application/CommentServiceTest.java b/src/test/java/ject/componote/domain/comment/application/CommentServiceTest.java new file mode 100644 index 0000000..bea9bf3 --- /dev/null +++ b/src/test/java/ject/componote/domain/comment/application/CommentServiceTest.java @@ -0,0 +1,356 @@ +package ject.componote.domain.comment.application; + +import ject.componote.domain.auth.domain.Job; +import ject.componote.domain.auth.domain.Member; +import ject.componote.domain.auth.model.AuthPrincipal; +import ject.componote.domain.auth.model.Nickname; +import ject.componote.domain.auth.model.ProfileImage; +import ject.componote.domain.comment.dao.CommentFindByComponentDao; +import ject.componote.domain.comment.dao.CommentFindByMemberDao; +import ject.componote.domain.comment.dao.CommentLikeRepository; +import ject.componote.domain.comment.dao.CommentRepository; +import ject.componote.domain.comment.domain.Comment; +import ject.componote.domain.comment.dto.create.request.CommentCreateRequest; +import ject.componote.domain.comment.dto.create.response.CommentCreateResponse; +import ject.componote.domain.comment.dto.find.response.CommentFindByComponentResponse; +import ject.componote.domain.comment.dto.find.response.CommentFindByMemberResponse; +import ject.componote.domain.comment.dto.like.event.CommentLikeEvent; +import ject.componote.domain.comment.dto.like.event.CommentUnlikeEvent; +import ject.componote.domain.comment.dto.update.request.CommentUpdateRequest; +import ject.componote.domain.comment.error.AlreadyLikedException; +import ject.componote.domain.comment.error.NoLikedException; +import ject.componote.domain.comment.error.NotFoundParentCommentException; +import ject.componote.domain.comment.model.CommentContent; +import ject.componote.domain.comment.model.CommentImage; +import ject.componote.domain.common.dto.response.PageResponse; +import ject.componote.domain.common.model.Count; +import ject.componote.fixture.CommentFixture; +import ject.componote.infra.file.application.FileService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static ject.componote.fixture.CommentFixture.답글_이미지X; +import static ject.componote.fixture.MemberFixture.KIM; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; + +@ExtendWith(MockitoExtension.class) +class CommentServiceTest { + @Mock + ApplicationEventPublisher eventPublisher; + + @Mock + CommentRepository commentRepository; + + @Mock + CommentLikeRepository commentLikeRepository; + + @Mock + FileService fileService; + + @InjectMocks + CommentService commentService; + + final Member member = KIM.생성(1L); + final AuthPrincipal authPrincipal = AuthPrincipal.from(member); + final Pageable pageable = PageRequest.of(0, 10); + + @ParameterizedTest + @DisplayName("댓글 생성") + @EnumSource(value = CommentFixture.class) + public void create(final CommentFixture fixture) throws Exception { + // given + final CommentCreateRequest createRequest = fixture.toCreateRequest(); + final Comment comment = fixture.생성(authPrincipal.id()); + final CommentCreateResponse expect = CommentCreateResponse.from(comment); + + // when + final Long parentId = comment.getParentId(); + if (parentId != null) { + doReturn(true).when(commentRepository) + .existsById(parentId); + } + + doReturn(comment).when(commentRepository) + .save(any()); + doNothing().when(fileService) + .moveImage(comment.getImage().getImage()); + final CommentCreateResponse actual = commentService.create(authPrincipal, createRequest); + + // then + assertThat(actual).isEqualTo(expect); + } + + @Test + @DisplayName("댓글 생성시 잘못된 parentId가 입력된 경우 예외 발생") + public void createWhenInvalidParentId() { + // given + final CommentCreateRequest createRequest = 답글_이미지X.toCreateRequest(); + final Long parentId = createRequest.parentId(); + + // when + doReturn(false).when(commentRepository) + .existsById(parentId); + // then + assertThatThrownBy(() -> commentService.create(authPrincipal, createRequest)) + .isInstanceOf(NotFoundParentCommentException.class); + } + + @Test + @DisplayName("마이 페이지 댓글 페이징 조회") + public void getCommentsByMemberId() throws Exception { + // given + final Long memberId = authPrincipal.id(); + final List content = List.of( + new CommentFindByMemberDao(1L, null, "컴포넌트 제목1", null, CommentContent.from("댓글 내용1"), LocalDateTime.now(), false), + new CommentFindByMemberDao(2L, 1L, "컴포넌트 제목2", CommentContent.from("댓글 내용1"), CommentContent.from("댓글 내용2"), LocalDateTime.now(), true) + ); + final Page page = new PageImpl<>(content, pageable, content.size()); + final PageResponse expect = PageResponse.from( + page.map(CommentFindByMemberResponse::from) + ); + + // when + doReturn(page).when(commentRepository) + .findAllByMemberIdWithPagination(memberId, pageable); + final PageResponse actual = commentService.getCommentsByMemberId(authPrincipal, pageable); + + // then + assertThat(actual).isEqualTo(expect); + } + + @Test + @DisplayName("비로그인 컴포넌트 댓글 페이징 조회") + public void getCommentsByComponentIdNoLoggedIn() throws Exception { + // given + final Long componentId = 1L; + final List content = List.of( + new CommentFindByComponentDao(1L, Nickname.from("닉네임1"), ProfileImage.from(null), Job.DEVELOPER, 1L, null, CommentImage.from(null), CommentContent.from("댓글 내용1"), LocalDateTime.now(), Count.create(), false, false), + new CommentFindByComponentDao(2L, Nickname.from("닉네임2"), ProfileImage.from(null), Job.DEVELOPER, 2L, 1L, CommentImage.from(null), CommentContent.from("댓글 내용2"), LocalDateTime.now(), Count.create(), false, true) + ); + final Page page = new PageImpl<>(content, pageable, content.size()); + final PageResponse expect = PageResponse.from( + page.map(CommentFindByComponentResponse::from) + ); + + // when + doReturn(page).when(commentRepository) + .findAllByComponentIdWithPagination(componentId, pageable); + final PageResponse actual = commentService.getCommentsByComponentId(null, componentId, pageable); + + // then + assertThat(actual).isEqualTo(expect); + } + + @Test + @DisplayName("로그인 컴포넌트 댓글 페이징 조회") + public void getCommentsByComponentIdWhenLoggedIn() throws Exception { + // given + final Long componentId = 1L; + final Long memberId = authPrincipal.id(); + final List content = List.of( + new CommentFindByComponentDao(1L, Nickname.from("닉네임1"), ProfileImage.from(null), Job.DEVELOPER, 1L, null, CommentImage.from(null), CommentContent.from("댓글 내용1"), LocalDateTime.now(), Count.create(), false, false), + new CommentFindByComponentDao(2L, Nickname.from("닉네임2"), ProfileImage.from(null), Job.DEVELOPER, 2L, 1L, CommentImage.from(null), CommentContent.from("댓글 내용2"), LocalDateTime.now(), Count.create(), false, true) + ); + final Page page = new PageImpl<>(content, pageable, content.size()); + final PageResponse expect = PageResponse.from( + page.map(CommentFindByComponentResponse::from) + ); + + // when + doReturn(page).when(commentRepository) + .findAllByComponentIdWithLikeStatusAndPagination(componentId, memberId, pageable); + final PageResponse actual = commentService.getCommentsByComponentId(authPrincipal, componentId, pageable); + + // then + assertThat(actual).isEqualTo(expect); + } + + @ParameterizedTest + @DisplayName("댓글 이미지만 수정") + @EnumSource(value = CommentFixture.class) + public void updateImage(final CommentFixture fixture) throws Exception { + // given + final Long memberId = authPrincipal.id(); + final Comment comment = fixture.생성(); + final Long commentId = comment.getId(); + final String content = comment.getContent().getValue(); + final String newObjectKey = "/comment/new.jpg"; + final CommentUpdateRequest request = new CommentUpdateRequest(newObjectKey, content); + + // when + doReturn(Optional.of(comment)).when(commentRepository) + .findByIdAndMemberId(commentId, memberId); + + // then + assertDoesNotThrow( + () -> commentService.update(authPrincipal, commentId, request) + ); + assertThat(comment.getImage().getImage().getObjectKey()).isEqualTo(newObjectKey); + } + + @ParameterizedTest + @DisplayName("댓글 내용만 수정") + @EnumSource(value = CommentFixture.class) + public void updateContent(final CommentFixture fixture) throws Exception { + // given + final Long memberId = authPrincipal.id(); + final Comment comment = fixture.생성(); + final Long commentId = comment.getId(); + final String objectKey = comment.getImage().getImage().getObjectKey(); + final String newContent = "수정된 내용"; + final CommentUpdateRequest request = new CommentUpdateRequest(objectKey, newContent); + + // when + doReturn(Optional.of(comment)).when(commentRepository) + .findByIdAndMemberId(commentId, memberId); + + // then + assertDoesNotThrow( + () -> commentService.update(authPrincipal, commentId, request) + ); + assertThat(comment.getContent().getValue()).isEqualTo(newContent); + } + + @ParameterizedTest + @DisplayName("댓글 이미지, 내용 모두 수정") + @EnumSource(value = CommentFixture.class) + public void updateAll(final CommentFixture fixture) throws Exception { + // given + final Long memberId = authPrincipal.id(); + final Comment comment = fixture.생성(); + final Long commentId = comment.getId(); + final String newContent = "수정된 내용"; + final String newObjectKey = "/comment/new.jpg"; + final CommentUpdateRequest request = new CommentUpdateRequest(newObjectKey, newContent); + + // when + doReturn(Optional.of(comment)).when(commentRepository) + .findByIdAndMemberId(commentId, memberId); + + // then + assertDoesNotThrow( + () -> commentService.update(authPrincipal, commentId, request) + ); + assertThat(comment.getImage().getImage().getObjectKey()).isEqualTo(newObjectKey); + assertThat(comment.getContent().getValue()).isEqualTo(newContent); + } + + @ParameterizedTest + @DisplayName("댓글 삭제") + @EnumSource(value = CommentFixture.class) + public void delete(final CommentFixture fixture) throws Exception { + // given + final Comment comment = fixture.생성(); + final Long commentId = comment.getId(); + final Long memberId = authPrincipal.id(); + + // when + doNothing().when(commentRepository) + .deleteByIdAndMemberId(commentId, memberId); + + // then + assertDoesNotThrow( + () -> commentService.delete(authPrincipal, commentId) + ); + } + + @ParameterizedTest + @DisplayName("댓글 좋아요") + @EnumSource(value = CommentFixture.class) + public void likeComment(final CommentFixture fixture) throws Exception { + // given + final Comment comment = fixture.생성(); + final Long commentId = comment.getId(); + final Long memberId = authPrincipal.id(); + final CommentLikeEvent event = CommentLikeEvent.of(authPrincipal, commentId); + + // when + doReturn(false).when(commentLikeRepository) + .existsByCommentIdAndMemberId(commentId, memberId); + doNothing().when(eventPublisher) + .publishEvent(event); + + // then + assertDoesNotThrow( + () -> commentService.likeComment(authPrincipal, commentId) + ); + } + + @ParameterizedTest + @DisplayName("댓글 좋아요 취소") + @EnumSource(value = CommentFixture.class) + public void unlikeComment(final CommentFixture fixture) throws Exception { + // given + final Comment comment = fixture.생성(); + final Long commentId = comment.getId(); + final Long memberId = authPrincipal.id(); + final CommentUnlikeEvent event = CommentUnlikeEvent.of(authPrincipal, commentId); + + // when + doReturn(true).when(commentLikeRepository) + .existsByCommentIdAndMemberId(commentId, memberId); + doNothing().when(eventPublisher) + .publishEvent(event); + + // then + assertDoesNotThrow( + () -> commentService.unlikeComment(authPrincipal, commentId) + ); + } + + @ParameterizedTest + @DisplayName("댓글 좋아요시 좋아요를 이미 좋아요를 눌렀다면 예외 발생") + @EnumSource(value = CommentFixture.class) + public void likeCommentWhenAlreadyLiked(final CommentFixture fixture) throws Exception { + // given + final Comment comment = fixture.생성(); + final Long commentId = comment.getId(); + final Long memberId = authPrincipal.id(); + + // when + doReturn(true).when(commentLikeRepository) + .existsByCommentIdAndMemberId(commentId, memberId); + + // then + assertThatThrownBy(() -> commentService.likeComment(authPrincipal, commentId)) + .isInstanceOf(AlreadyLikedException.class); + } + + @ParameterizedTest + @DisplayName("댓글 좋아요 취소시 좋아요를 누른적이 없는 경우 예외 발생") + @EnumSource(value = CommentFixture.class) + public void unlikeCommentWhenNoLike(final CommentFixture fixture) throws Exception { + // given + final Comment comment = fixture.생성(); + final Long commentId = comment.getId(); + final Long memberId = authPrincipal.id(); + + // when + doReturn(false).when(commentLikeRepository) + .existsByCommentIdAndMemberId(commentId, memberId); + + // then + assertThatThrownBy(() -> commentService.unlikeComment(authPrincipal, commentId)) + .isInstanceOf(NoLikedException.class); + } +} \ No newline at end of file From 293b67ac37033c37c0fa8f750d72a2b757bd45a5 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 18:18:23 +0900 Subject: [PATCH 064/156] =?UTF-8?q?[REFACTOR]=20QCommentDaoFactory.createF?= =?UTF-8?q?orComponent()=20=EC=88=98=EC=A0=95=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9E=90=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=EC=97=90=20nul?= =?UTF-8?q?l=20=EB=A6=AC=ED=84=B0=EB=9F=B4=20=EB=8C=80=EC=8B=A0=20Expressi?= =?UTF-8?q?ons.nullExpression()=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ject/componote/domain/comment/dao/QCommentDaoFactory.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ject/componote/domain/comment/dao/QCommentDaoFactory.java b/src/main/java/ject/componote/domain/comment/dao/QCommentDaoFactory.java index ac4f012..1e100cb 100644 --- a/src/main/java/ject/componote/domain/comment/dao/QCommentDaoFactory.java +++ b/src/main/java/ject/componote/domain/comment/dao/QCommentDaoFactory.java @@ -2,6 +2,7 @@ import com.querydsl.core.types.ConstructorExpression; import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.Expressions; import ject.componote.domain.comment.domain.QComment; import org.springframework.stereotype.Component; @@ -25,7 +26,7 @@ public ConstructorExpression createForComponent() { comment.content, comment.createdAt, comment.likeCount, - null, + Expressions.nullExpression(Boolean.class), comment.parentId.isNotNull() ); } From 51100a4f03550a4c93ddbf5ccc35fda20b22f482 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Fri, 27 Dec 2024 18:25:31 +0900 Subject: [PATCH 065/156] =?UTF-8?q?[FEAT]=20CommentService=EC=97=90=20@Tra?= =?UTF-8?q?nsactional=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../componote/domain/comment/application/CommentService.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/ject/componote/domain/comment/application/CommentService.java b/src/main/java/ject/componote/domain/comment/application/CommentService.java index 9b0a79f..a1df865 100644 --- a/src/main/java/ject/componote/domain/comment/application/CommentService.java +++ b/src/main/java/ject/componote/domain/comment/application/CommentService.java @@ -26,15 +26,18 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor @Service +@Transactional(readOnly = true) public class CommentService { private final ApplicationEventPublisher eventPublisher; private final CommentRepository commentRepository; private final CommentLikeRepository commentLikeRepository; private final FileService fileService; + @Transactional public CommentCreateResponse create(final AuthPrincipal authPrincipal, final CommentCreateRequest request) { if (isReply(request)) { validateParentId(request.parentId()); @@ -63,6 +66,7 @@ public PageResponse getCommentsByComponentId(fin } @CommenterValidation + @Transactional public void update(final AuthPrincipal authPrincipal, final Long commentId, final CommentUpdateRequest commentUpdateRequest) { final Comment comment = findCommentByIdAndMemberId(commentId, authPrincipal.id()); @@ -78,6 +82,7 @@ public void update(final AuthPrincipal authPrincipal, final Long commentId, fina } @CommenterValidation + @Transactional public void delete(final AuthPrincipal authPrincipal, final Long commentId) { commentRepository.deleteByIdAndMemberId(commentId, authPrincipal.id()); } From 6ad10ea3a3b628d7478fb3e5b82d6b878f5e3201 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:10:39 +0900 Subject: [PATCH 066/156] =?UTF-8?q?[REFACTOR]=20ContentBlock=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/{detail => }/block/ContentBlock.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) rename src/main/java/ject/componote/domain/component/domain/{detail => }/block/ContentBlock.java (80%) diff --git a/src/main/java/ject/componote/domain/component/domain/detail/block/ContentBlock.java b/src/main/java/ject/componote/domain/component/domain/block/ContentBlock.java similarity index 80% rename from src/main/java/ject/componote/domain/component/domain/detail/block/ContentBlock.java rename to src/main/java/ject/componote/domain/component/domain/block/ContentBlock.java index 68b387c..6ba3163 100644 --- a/src/main/java/ject/componote/domain/component/domain/detail/block/ContentBlock.java +++ b/src/main/java/ject/componote/domain/component/domain/block/ContentBlock.java @@ -1,4 +1,4 @@ -package ject.componote.domain.component.domain.detail.block; +package ject.componote.domain.component.domain.block; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -9,7 +9,6 @@ import jakarta.persistence.Id; import jakarta.persistence.Inheritance; import jakarta.persistence.InheritanceType; -import ject.componote.domain.component.domain.detail.DetailType; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -27,13 +26,15 @@ public abstract class ContentBlock { @Column(name = "type", nullable = false) @Enumerated(EnumType.STRING) - private DetailType type; + private BlockType type; @Column(name = "orders", nullable = false) private Integer order; - public ContentBlock(final DetailType type, final Integer order) { + public ContentBlock(final BlockType type, final Integer order) { this.type = type; this.order = order; } + + public abstract String getValue(); } From 7793668e96e88398e80323ba344b8161a7c8f08b Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:10:53 +0900 Subject: [PATCH 067/156] =?UTF-8?q?[REFACTOR]=20DetailType=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=EB=AA=85=EC=9D=84=20BlockType=20=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../componote/domain/component/domain/block/BlockType.java | 5 +++++ .../componote/domain/component/domain/detail/DetailType.java | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 src/main/java/ject/componote/domain/component/domain/block/BlockType.java delete mode 100644 src/main/java/ject/componote/domain/component/domain/detail/DetailType.java diff --git a/src/main/java/ject/componote/domain/component/domain/block/BlockType.java b/src/main/java/ject/componote/domain/component/domain/block/BlockType.java new file mode 100644 index 0000000..6b1b2a7 --- /dev/null +++ b/src/main/java/ject/componote/domain/component/domain/block/BlockType.java @@ -0,0 +1,5 @@ +package ject.componote.domain.component.domain.block; + +public enum BlockType { + INTRODUCTION, DESCRIPTION, USE_CASE, REFERENCE; +} diff --git a/src/main/java/ject/componote/domain/component/domain/detail/DetailType.java b/src/main/java/ject/componote/domain/component/domain/detail/DetailType.java deleted file mode 100644 index 2855481..0000000 --- a/src/main/java/ject/componote/domain/component/domain/detail/DetailType.java +++ /dev/null @@ -1,5 +0,0 @@ -package ject.componote.domain.component.domain.detail; - -public enum DetailType { - INTRODUCTION, DESCRIPTION, USE_CASE, REFERENCE; -} From bab15139b2909101ffff10236ed5ac52b329a372 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:11:02 +0900 Subject: [PATCH 068/156] =?UTF-8?q?[FEAT]=20ComponentSummaryResponse=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/ComponentSummaryResponse.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/dto/find/response/ComponentSummaryResponse.java diff --git a/src/main/java/ject/componote/domain/component/dto/find/response/ComponentSummaryResponse.java b/src/main/java/ject/componote/domain/component/dto/find/response/ComponentSummaryResponse.java new file mode 100644 index 0000000..5d21bbf --- /dev/null +++ b/src/main/java/ject/componote/domain/component/dto/find/response/ComponentSummaryResponse.java @@ -0,0 +1,30 @@ +package ject.componote.domain.component.dto.find.response; + +import ject.componote.domain.component.dao.ComponentSummaryDao; +import ject.componote.domain.component.domain.summary.ComponentSummary; + +public record ComponentSummaryResponse( + Long id, + String thumbnailUrl, + String title, + String description, + String type, + Long bookmarkCount, + Long commentCount, + Long designReferenceCount, + Boolean isBookmarked) { + public static ComponentSummaryResponse from(final ComponentSummaryDao dao) { + final ComponentSummary summary = dao.summary(); + return new ComponentSummaryResponse( + dao.id(), + summary.getThumbnail().getImage().toUrl(), + summary.getTitle(), + summary.getDescription(), + dao.type().name(), + dao.bookmarkCount().getValue(), + dao.commentCount().getValue(), + dao.designReferenceCount().getValue(), + dao.isBookmarked() + ); + } +} From 46fe8a24062946f7f9c6fed40880990af55aba5f Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:11:46 +0900 Subject: [PATCH 069/156] =?UTF-8?q?[FEAT]=20ComponentSummaryResponse=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/summary/ComponentSummary.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/ject/componote/domain/component/domain/summary/ComponentSummary.java b/src/main/java/ject/componote/domain/component/domain/summary/ComponentSummary.java index 57b0194..b84f3d8 100644 --- a/src/main/java/ject/componote/domain/component/domain/summary/ComponentSummary.java +++ b/src/main/java/ject/componote/domain/component/domain/summary/ComponentSummary.java @@ -3,8 +3,8 @@ import jakarta.persistence.Column; import jakarta.persistence.Convert; import jakarta.persistence.Embeddable; -import ject.componote.domain.common.model.Image; -import ject.componote.domain.common.model.converter.ImageConverter; +import ject.componote.domain.component.model.ComponentThumbnail; +import ject.componote.domain.component.model.converter.ComponentThumbnailConverter; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -18,18 +18,18 @@ public class ComponentSummary { @Column(name = "title", nullable = false) private String title; - @Column(name = "summary", nullable = false) - private String summary; + @Column(name = "description", nullable = false) + private String description; - @Convert(converter = ImageConverter.class) + @Convert(converter = ComponentThumbnailConverter.class) @Column(name = "thumbnail", nullable = false) - private Image thumbnail; + private ComponentThumbnail thumbnail; - private ComponentSummary(final String title, final String summary, final Image thumbnail) { + private ComponentSummary(final String title, final String description, final ComponentThumbnail thumbnail) { validateTitle(title); - validateSummary(summary); + validateDescription(description); this.title = title; - this.summary = summary; + this.description = description; this.thumbnail = thumbnail; } @@ -37,11 +37,11 @@ private void validateTitle(final String title) { } - private void validateSummary(final String summary) { + private void validateDescription(final String description) { } - public static ComponentSummary of(final String title, final String summary, final Image thumbnail) { - return new ComponentSummary(title, summary, thumbnail); + public static ComponentSummary of(final String title, final String description, final String thumbnailObjectKey) { + return new ComponentSummary(title, description, ComponentThumbnail.from(thumbnailObjectKey)); } } From 7a69582d6a4522dc95aae2e9b5935c4e149061e7 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:11:56 +0900 Subject: [PATCH 070/156] =?UTF-8?q?[FEAT]=20ComponentSearchRequest=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/find/request/ComponentSearchRequest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/dto/find/request/ComponentSearchRequest.java diff --git a/src/main/java/ject/componote/domain/component/dto/find/request/ComponentSearchRequest.java b/src/main/java/ject/componote/domain/component/dto/find/request/ComponentSearchRequest.java new file mode 100644 index 0000000..a25eac3 --- /dev/null +++ b/src/main/java/ject/componote/domain/component/dto/find/request/ComponentSearchRequest.java @@ -0,0 +1,12 @@ +package ject.componote.domain.component.dto.find.request; + +import jakarta.annotation.Nullable; +import jakarta.validation.constraints.NotBlank; +import ject.componote.domain.component.domain.ComponentType; + +import java.util.List; + +public record ComponentSearchRequest( + @NotBlank String keyword, + @Nullable List types) { +} From f4e771ccd039f50097dcf3e8fdc86ffafddffa19 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:12:15 +0900 Subject: [PATCH 071/156] =?UTF-8?q?[FEAT]=20Component=20=EC=99=80=20Design?= =?UTF-8?q?=20=EC=97=B0=EA=B4=80=EA=B4=80=EA=B3=84=20=EC=A4=91=EA=B0=84=20?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EB=B8=94=EC=9D=B8=20ComponentDesign=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/domain/ComponentDesign.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/domain/ComponentDesign.java diff --git a/src/main/java/ject/componote/domain/component/domain/ComponentDesign.java b/src/main/java/ject/componote/domain/component/domain/ComponentDesign.java new file mode 100644 index 0000000..25e04df --- /dev/null +++ b/src/main/java/ject/componote/domain/component/domain/ComponentDesign.java @@ -0,0 +1,36 @@ +package ject.componote.domain.component.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import ject.componote.domain.design.domain.Design; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@ToString +public class ComponentDesign { + @Id + @GeneratedValue + private Long id; + + @Column(name = "component_id", nullable = false) + private Long componentId; + + @Column(name = "design_id", nullable = false) + private Long designId; + + private ComponentDesign(final Long componentId, final Long designId) { + this.componentId = componentId; + this.designId = designId; + } + + public static ComponentDesign from(final Component component, final Design design) { + return new ComponentDesign(component.getId(), design.getId()); + } +} From 3f31159b1fdb29b9f06784a57783167689a4a80f Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:12:44 +0900 Subject: [PATCH 072/156] =?UTF-8?q?[FEAT]=20Component=EC=97=90=20designRef?= =?UTF-8?q?erenceCount,=20viewCount=20=ED=95=84=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/component/domain/Component.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/ject/componote/domain/component/domain/Component.java b/src/main/java/ject/componote/domain/component/domain/Component.java index 620c8b8..a82e401 100644 --- a/src/main/java/ject/componote/domain/component/domain/Component.java +++ b/src/main/java/ject/componote/domain/component/domain/Component.java @@ -16,7 +16,7 @@ import ject.componote.domain.common.domain.BaseEntity; import ject.componote.domain.common.model.Count; import ject.componote.domain.common.model.converter.CountConverter; -import ject.componote.domain.component.domain.detail.block.ContentBlock; +import ject.componote.domain.component.domain.block.ContentBlock; import ject.componote.domain.component.domain.summary.ComponentSummary; import lombok.AccessLevel; import lombok.Getter; @@ -56,12 +56,22 @@ public class Component extends BaseEntity { @Convert(converter = CountConverter.class) private Count commentCount; - private Component(final ComponentType type, final List mixedNames, final ComponentSummary summary) { + @Column(name = "design_reference_count", nullable = false) + @Convert(converter = CountConverter.class) + private Count designReferenceCount; + + @Column(name = "view_count", nullable = false) + @Convert(converter = CountConverter.class) + private Count viewCount; + + private Component(final ComponentType type, final List mixedNames, final ComponentSummary summary, final List contentBlocks) { this.type = type; this.mixedNames.addAll(mixedNames); this.summary = summary; this.bookmarkCount = Count.create(); this.commentCount = Count.create(); + this.designReferenceCount = Count.create(); + this.viewCount = Count.create(); } public static Component of(final ComponentType type, final List mixedNames, final ComponentSummary summary) { From ede9e8b1060c35a401422bec10cdc9b65cfd9037 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:13:13 +0900 Subject: [PATCH 073/156] =?UTF-8?q?[FEAT]=20GlobalExceptionHandler?= =?UTF-8?q?=EC=97=90=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EA=B4=80=EB=A0=A8=20=EC=98=88=EC=99=B8=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../componote/global/error/GlobalExceptionHandler.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/ject/componote/global/error/GlobalExceptionHandler.java b/src/main/java/ject/componote/global/error/GlobalExceptionHandler.java index 4bcdd00..b9ed55c 100644 --- a/src/main/java/ject/componote/global/error/GlobalExceptionHandler.java +++ b/src/main/java/ject/componote/global/error/GlobalExceptionHandler.java @@ -4,6 +4,7 @@ import jakarta.validation.ConstraintViolationException; import ject.componote.infra.error.InfraException; import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DataAccessException; import org.springframework.http.ResponseEntity; import org.springframework.validation.FieldError; import org.springframework.web.HttpRequestMethodNotSupportedException; @@ -38,6 +39,12 @@ public ResponseEntity handleSQLException(final SQLException excep .body(ErrorResponse.of(INTERNAL_SERVER_ERROR, "SQL 오류입니다.")); } + @ExceptionHandler(DataAccessException.class) + public ResponseEntity handleDataAccessException(final DataAccessException exception) { + return ResponseEntity.status(BAD_REQUEST) + .body(ErrorResponse.of(BAD_REQUEST, exception.getLocalizedMessage())); + } + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) public ResponseEntity handleRequestMethodNotSupportedException(final HttpRequestMethodNotSupportedException exception) { return ResponseEntity.status(METHOD_NOT_ALLOWED) From ed30d5de008a3c6c83f2217fbb35ed32a74372ee Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:13:45 +0900 Subject: [PATCH 074/156] =?UTF-8?q?[FEAT]=20Component=EC=97=90=20@DynamicU?= =?UTF-8?q?pdate,=20=EC=A7=80=EC=97=B0=20=EB=A1=9C=EB=94=A9=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=86=8D=EC=84=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/component/domain/Component.java | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/main/java/ject/componote/domain/component/domain/Component.java b/src/main/java/ject/componote/domain/component/domain/Component.java index a82e401..5f553de 100644 --- a/src/main/java/ject/componote/domain/component/domain/Component.java +++ b/src/main/java/ject/componote/domain/component/domain/Component.java @@ -21,10 +21,12 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.DynamicUpdate; import java.util.ArrayList; import java.util.List; +@DynamicUpdate @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -44,7 +46,7 @@ public class Component extends BaseEntity { @Embedded private ComponentSummary summary; - @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "component_id") private List contentBlocks = new ArrayList<>(); @@ -66,29 +68,27 @@ public class Component extends BaseEntity { private Component(final ComponentType type, final List mixedNames, final ComponentSummary summary, final List contentBlocks) { this.type = type; - this.mixedNames.addAll(mixedNames); + this.mixedNames.addAll(parseMixedNames(mixedNames)); this.summary = summary; + this.contentBlocks.addAll(contentBlocks); this.bookmarkCount = Count.create(); this.commentCount = Count.create(); this.designReferenceCount = Count.create(); this.viewCount = Count.create(); } - public static Component of(final ComponentType type, final List mixedNames, final ComponentSummary summary) { - return new Component(type, mixedNames, summary); - } - - public void addBlock(final ContentBlock contentBlock) { - this.contentBlocks.add(contentBlock); + public static Component of(final String title, final String description, final String thumbnailObjectKey, final ComponentType type, final List mixedNames, final List contentBlocks) { + return new Component(type, mixedNames, ComponentSummary.of(title, description, thumbnailObjectKey), contentBlocks); } - // 중복 검사 필요... Set으로 둬야되나? 너무 비효율적일 것 같음... - public void addMixedName(final String mixedName) { - mixedNames.add(MixedName.from(mixedName)); + public void increaseViewCount() { + this.viewCount.increase(); } - public void removeMixedName(final String mixedName) { - mixedNames.remove(MixedName.from(mixedName)); + private List parseMixedNames(final List mixedNames) { + return mixedNames.stream() + .map(MixedName::from) + .toList(); } @Override @@ -99,6 +99,8 @@ public String toString() { ", summary=" + summary + ", commentCount=" + commentCount + ", bookmarkCount=" + bookmarkCount + + ", designReferenceCount=" + designReferenceCount + + ", viewCount=" + viewCount + '}'; } } From 64b45b7c6a3ff3cc0e05a8954f42040d7ec05de2 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:13:54 +0900 Subject: [PATCH 075/156] =?UTF-8?q?[FEAT]=20ComponentException=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/component/error/ComponentException.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/ject/componote/domain/component/error/ComponentException.java b/src/main/java/ject/componote/domain/component/error/ComponentException.java index dc25293..75e488d 100644 --- a/src/main/java/ject/componote/domain/component/error/ComponentException.java +++ b/src/main/java/ject/componote/domain/component/error/ComponentException.java @@ -1,4 +1,10 @@ package ject.componote.domain.component.error; -public class ComponentException { +import ject.componote.global.error.ComponoteException; +import org.springframework.http.HttpStatus; + +public class ComponentException extends ComponoteException { + public ComponentException(final String message, final HttpStatus status) { + super(message, status); + } } From 5ad52cbddf4af6452f612c99cea9a2d5c1d9d15d Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:14:20 +0900 Subject: [PATCH 076/156] =?UTF-8?q?[FEAT]=20DynamicUpdate=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=EC=9D=84=20=EC=9C=84=ED=95=B4=20ComponentSummary?= =?UTF-8?q?=EC=97=90=20@EqualsAndHashCode=20=EC=96=B4=EB=85=B8=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/component/domain/summary/ComponentSummary.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/ject/componote/domain/component/domain/summary/ComponentSummary.java b/src/main/java/ject/componote/domain/component/domain/summary/ComponentSummary.java index b84f3d8..c056c60 100644 --- a/src/main/java/ject/componote/domain/component/domain/summary/ComponentSummary.java +++ b/src/main/java/ject/componote/domain/component/domain/summary/ComponentSummary.java @@ -6,11 +6,13 @@ import ject.componote.domain.component.model.ComponentThumbnail; import ject.componote.domain.component.model.converter.ComponentThumbnailConverter; import lombok.AccessLevel; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.ToString; @Embeddable +@EqualsAndHashCode @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @ToString From 07c92d960bb1c95bae0d19bcfc0bdc341d68226c Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:26:30 +0900 Subject: [PATCH 077/156] =?UTF-8?q?[FEAT]=20BookmarkRepository=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../componote/domain/bookmark/dao/BookmarkRepository.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/ject/componote/domain/bookmark/dao/BookmarkRepository.java diff --git a/src/main/java/ject/componote/domain/bookmark/dao/BookmarkRepository.java b/src/main/java/ject/componote/domain/bookmark/dao/BookmarkRepository.java new file mode 100644 index 0000000..9215cf2 --- /dev/null +++ b/src/main/java/ject/componote/domain/bookmark/dao/BookmarkRepository.java @@ -0,0 +1,8 @@ +package ject.componote.domain.bookmark.dao; + +import ject.componote.domain.bookmark.domain.Bookmark; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BookmarkRepository extends JpaRepository { + boolean existsByComponentIdAndMemberId(final Long componentId, final Long memberId); +} From 7cf7748708b726cbcdae68d9dfc2959f63736811 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:27:15 +0900 Subject: [PATCH 078/156] =?UTF-8?q?[FEAT]=20ComponentBlockResponse=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/find/response/ComponentBlockResponse.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/dto/find/response/ComponentBlockResponse.java diff --git a/src/main/java/ject/componote/domain/component/dto/find/response/ComponentBlockResponse.java b/src/main/java/ject/componote/domain/component/dto/find/response/ComponentBlockResponse.java new file mode 100644 index 0000000..5c4d63c --- /dev/null +++ b/src/main/java/ject/componote/domain/component/dto/find/response/ComponentBlockResponse.java @@ -0,0 +1,12 @@ +package ject.componote.domain.component.dto.find.response; + +import ject.componote.domain.component.domain.block.ContentBlock; + +public record ComponentBlockResponse(Integer order, String content) { + public static ComponentBlockResponse from(final ContentBlock contentBlock) { + return new ComponentBlockResponse( + contentBlock.getOrder(), + contentBlock.getValue() + ); + } +} From 92cb81469771b49e9f768d374a743304d999de12 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:28:21 +0900 Subject: [PATCH 079/156] =?UTF-8?q?[FEAT]=20ComponentImage=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/model/ComponentImage.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/model/ComponentImage.java diff --git a/src/main/java/ject/componote/domain/component/model/ComponentImage.java b/src/main/java/ject/componote/domain/component/model/ComponentImage.java new file mode 100644 index 0000000..5b19569 --- /dev/null +++ b/src/main/java/ject/componote/domain/component/model/ComponentImage.java @@ -0,0 +1,35 @@ +package ject.componote.domain.component.model; + +import ject.componote.domain.common.model.BaseImage; +import ject.componote.domain.component.error.InvalidComponentImageExtensionException; +import ject.componote.domain.component.error.NotFoundComponentImageException; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.springframework.util.StringUtils; + +import java.util.List; + +@EqualsAndHashCode +@Getter +public class ComponentImage { + private static final List ALLOWED_IMAGE_EXTENSIONS = List.of("png"); + + private final BaseImage image; + + private ComponentImage(final BaseImage image) { + this.image = image; + } + + public static ComponentImage from(final String objectKey) { + if (objectKey == null || objectKey.isEmpty()) { + throw new NotFoundComponentImageException(); + } + + final String extension = StringUtils.getFilenameExtension(objectKey); + if (!ALLOWED_IMAGE_EXTENSIONS.contains(extension)) { + throw new InvalidComponentImageExtensionException(extension); + } + + return new ComponentImage(BaseImage.from(objectKey)); + } +} From 210a7a5fef9f6966c490a711e09e208d4d52ff9e Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:28:27 +0900 Subject: [PATCH 080/156] =?UTF-8?q?[FEAT]=20ComponentImageConverter=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/ComponentImageConverter.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/model/converter/ComponentImageConverter.java diff --git a/src/main/java/ject/componote/domain/component/model/converter/ComponentImageConverter.java b/src/main/java/ject/componote/domain/component/model/converter/ComponentImageConverter.java new file mode 100644 index 0000000..e4a3118 --- /dev/null +++ b/src/main/java/ject/componote/domain/component/model/converter/ComponentImageConverter.java @@ -0,0 +1,18 @@ +package ject.componote.domain.component.model.converter; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +import ject.componote.domain.component.model.ComponentImage; + +@Converter +public class ComponentImageConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(final ComponentImage attribute) { + return attribute.getImage().getObjectKey(); + } + + @Override + public ComponentImage convertToEntityAttribute(final String dbData) { + return ComponentImage.from(dbData); + } +} From cefe41e092e9d290ecf41e6af6c91b38b3f46934 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:28:35 +0900 Subject: [PATCH 081/156] =?UTF-8?q?[FEAT]=20ComponentMapper=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/util/ComponentMapper.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/util/ComponentMapper.java diff --git a/src/main/java/ject/componote/domain/component/util/ComponentMapper.java b/src/main/java/ject/componote/domain/component/util/ComponentMapper.java new file mode 100644 index 0000000..15c2712 --- /dev/null +++ b/src/main/java/ject/componote/domain/component/util/ComponentMapper.java @@ -0,0 +1,47 @@ +package ject.componote.domain.component.util; + +import ject.componote.domain.component.domain.Component; +import ject.componote.domain.component.domain.MixedName; +import ject.componote.domain.component.domain.block.BlockType; +import ject.componote.domain.component.domain.block.ContentBlock; +import ject.componote.domain.component.domain.summary.ComponentSummary; +import ject.componote.domain.component.dto.find.response.ComponentBlockResponse; +import ject.componote.domain.component.dto.find.response.ComponentDetailResponse; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@org.springframework.stereotype.Component +public class ComponentMapper { + public ComponentDetailResponse mapFrom(final Component component, final Boolean isBookmarked) { + final ComponentSummary summary = component.getSummary(); + return new ComponentDetailResponse( + summary.getTitle(), + parseMixedNames(component), + summary.getDescription(), + component.getCommentCount().getValue(), + component.getBookmarkCount().getValue(), + component.getDesignReferenceCount().getValue(), + summary.getThumbnail().getImage().toUrl(), + parseBlocks(component), + isBookmarked + ); + } + + private List parseMixedNames(final Component component) { + return component.getMixedNames() + .stream() + .map(MixedName::getName) + .toList(); + } + + private Map> parseBlocks(final Component component) { + final List contentBlocks = component.getContentBlocks(); + return contentBlocks.stream() + .collect(Collectors.groupingBy( + ContentBlock::getType, + Collectors.mapping(ComponentBlockResponse::from, Collectors.toList())) + ); + } +} From 670db1bf178cb485e080b131af64821417217032 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:28:52 +0900 Subject: [PATCH 082/156] =?UTF-8?q?[FEAT]=20ComponentThumbnail=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ComponentSearchStrategy.java | 67 +++++++++++++++++++ .../component/model/ComponentThumbnail.java | 36 ++++++++++ 2 files changed, 103 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/application/ComponentSearchStrategy.java create mode 100644 src/main/java/ject/componote/domain/component/model/ComponentThumbnail.java diff --git a/src/main/java/ject/componote/domain/component/application/ComponentSearchStrategy.java b/src/main/java/ject/componote/domain/component/application/ComponentSearchStrategy.java new file mode 100644 index 0000000..00fada4 --- /dev/null +++ b/src/main/java/ject/componote/domain/component/application/ComponentSearchStrategy.java @@ -0,0 +1,67 @@ +package ject.componote.domain.component.application; + +import ject.componote.domain.auth.model.AuthPrincipal; +import ject.componote.domain.component.dao.ComponentRepository; +import ject.componote.domain.component.dao.ComponentSummaryDao; +import ject.componote.domain.component.dto.find.request.ComponentSearchRequest; +import ject.componote.domain.component.error.InvalidCommentSearchStrategyException; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.Arrays; +import java.util.function.BiPredicate; + +@RequiredArgsConstructor +public enum ComponentSearchStrategy { + WITH_BOOKMARK_AND_FILTER( + (authPrincipal, request) -> isLoggedIn(authPrincipal) && hasFilter(request), + (authPrincipal, componentRepository, request, pageable) -> + componentRepository.searchWithBookmarkAndTypes(authPrincipal.id(), request.keyword(), request.types(), pageable) + ), + + WITH_BOOKMARK( + (authPrincipal, request) -> isLoggedIn(authPrincipal) && !hasFilter(request), + (authPrincipal, componentRepository, request, pageable) -> + componentRepository.searchWithBookmark(authPrincipal.id(), request.keyword(), pageable) + ), + + WITHOUT_BOOKMARK_AND_FILTER( + (authPrincipal, request) -> !isLoggedIn(authPrincipal) && hasFilter(request), + (authPrincipal, componentRepository, request, pageable) -> + componentRepository.searchByKeywordWithTypes(request.keyword(), request.types(), pageable) + ), + + WITHOUT_BOOKMARK( + (authPrincipal, request) -> !isLoggedIn(authPrincipal) && !hasFilter(request), + (authPrincipal, componentRepository, request, pageable) -> + componentRepository.searchByKeyword(request.keyword(), pageable) + ); + + private final BiPredicate condition; + private final ComponentSearchFunction searchFunction; + + public static Page searchBy(final AuthPrincipal authPrincipal, + final ComponentRepository componentRepository, + final ComponentSearchRequest request, + final Pageable pageable) { + return Arrays.stream(values()) + .filter(strategy -> strategy.condition.test(authPrincipal, request)) + .findFirst() + .orElseThrow(InvalidCommentSearchStrategyException::new) + .searchFunction.search(authPrincipal, componentRepository, request, pageable); + } + + private static boolean isLoggedIn(final AuthPrincipal authPrincipal) { + return authPrincipal != null; + } + + private static boolean hasFilter(final ComponentSearchRequest request) { + return request.types() != null && !request.types().isEmpty(); + } + + @FunctionalInterface + private interface ComponentSearchFunction { + Page search(final AuthPrincipal authPrincipal, final ComponentRepository componentRepository, final ComponentSearchRequest request, final Pageable pageable); + } +} diff --git a/src/main/java/ject/componote/domain/component/model/ComponentThumbnail.java b/src/main/java/ject/componote/domain/component/model/ComponentThumbnail.java new file mode 100644 index 0000000..007a1fd --- /dev/null +++ b/src/main/java/ject/componote/domain/component/model/ComponentThumbnail.java @@ -0,0 +1,36 @@ +package ject.componote.domain.component.model; + +import ject.componote.domain.common.model.BaseImage; +import ject.componote.domain.component.error.InvalidComponentThumbnailExtensionException; +import ject.componote.domain.component.error.NotFoundComponentThumbnailException; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.springframework.util.StringUtils; + +import java.util.Arrays; +import java.util.List; + +@EqualsAndHashCode +@Getter +public class ComponentThumbnail { + private static final List ALLOWED_IMAGE_EXTENSIONS = Arrays.asList("jpg", "jpeg", "png"); + + private final BaseImage image; + + private ComponentThumbnail(final BaseImage image) { + this.image = image; + } + + public static ComponentThumbnail from(final String objectKey) { + if (objectKey == null || objectKey.isEmpty()) { + throw new NotFoundComponentThumbnailException(); + } + + final String extension = StringUtils.getFilenameExtension(objectKey); + if (!ALLOWED_IMAGE_EXTENSIONS.contains(extension)) { + throw new InvalidComponentThumbnailExtensionException(extension); + } + + return new ComponentThumbnail(BaseImage.from(objectKey)); + } +} From b01bfebb845900bc42a979d0059490c2c02316ac Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:28:57 +0900 Subject: [PATCH 083/156] =?UTF-8?q?[FEAT]=20ComponentThumbnailConverter=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/ComponentThumbnailConverter.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/model/converter/ComponentThumbnailConverter.java diff --git a/src/main/java/ject/componote/domain/component/model/converter/ComponentThumbnailConverter.java b/src/main/java/ject/componote/domain/component/model/converter/ComponentThumbnailConverter.java new file mode 100644 index 0000000..603b8da --- /dev/null +++ b/src/main/java/ject/componote/domain/component/model/converter/ComponentThumbnailConverter.java @@ -0,0 +1,18 @@ +package ject.componote.domain.component.model.converter; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +import ject.componote.domain.component.model.ComponentThumbnail; + +@Converter +public class ComponentThumbnailConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(final ComponentThumbnail attribute) { + return attribute.getImage().getObjectKey(); + } + + @Override + public ComponentThumbnail convertToEntityAttribute(final String dbData) { + return ComponentThumbnail.from(dbData); + } +} From b6d88e73618e0b6af3842bc7a7623d4f52563f20 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:29:12 +0900 Subject: [PATCH 084/156] =?UTF-8?q?[FEAT]=20ComponentSummaryDao=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/dao/ComponentSummaryDao.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/dao/ComponentSummaryDao.java diff --git a/src/main/java/ject/componote/domain/component/dao/ComponentSummaryDao.java b/src/main/java/ject/componote/domain/component/dao/ComponentSummaryDao.java new file mode 100644 index 0000000..420bf7b --- /dev/null +++ b/src/main/java/ject/componote/domain/component/dao/ComponentSummaryDao.java @@ -0,0 +1,17 @@ +package ject.componote.domain.component.dao; + +import ject.componote.domain.common.model.Count; +import ject.componote.domain.component.domain.ComponentType; +import ject.componote.domain.component.domain.summary.ComponentSummary; + +public record ComponentSummaryDao( + Long id, + ComponentSummary summary, + ComponentType type, + Count bookmarkCount, + Count commentCount, + Count designReferenceCount, + Count viewCount, + Boolean isBookmarked +) { +} From 2359f6f4dd49b1df7f23a3be9f029456dc782bac Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:29:34 +0900 Subject: [PATCH 085/156] =?UTF-8?q?[FEAT]=20ComponentViewCountIncreaseEven?= =?UTF-8?q?t=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/find/event/ComponentViewCountIncreaseEvent.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/dto/find/event/ComponentViewCountIncreaseEvent.java diff --git a/src/main/java/ject/componote/domain/component/dto/find/event/ComponentViewCountIncreaseEvent.java b/src/main/java/ject/componote/domain/component/dto/find/event/ComponentViewCountIncreaseEvent.java new file mode 100644 index 0000000..15315f7 --- /dev/null +++ b/src/main/java/ject/componote/domain/component/dto/find/event/ComponentViewCountIncreaseEvent.java @@ -0,0 +1,9 @@ +package ject.componote.domain.component.dto.find.event; + +import ject.componote.domain.component.domain.Component; + +public record ComponentViewCountIncreaseEvent(Long componentId) { + public static ComponentViewCountIncreaseEvent from(final Component component) { + return new ComponentViewCountIncreaseEvent(component.getId()); + } +} From ca31f11eb39778b5a012b4d755d1415f6c6f00d0 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:29:42 +0900 Subject: [PATCH 086/156] =?UTF-8?q?[FEAT]=20ComponentViewCountEventHandler?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ComponentViewCountEventHandler.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/application/ComponentViewCountEventHandler.java diff --git a/src/main/java/ject/componote/domain/component/application/ComponentViewCountEventHandler.java b/src/main/java/ject/componote/domain/component/application/ComponentViewCountEventHandler.java new file mode 100644 index 0000000..52169b0 --- /dev/null +++ b/src/main/java/ject/componote/domain/component/application/ComponentViewCountEventHandler.java @@ -0,0 +1,30 @@ +package ject.componote.domain.component.application; + +import ject.componote.domain.component.dao.ComponentRepository; +import ject.componote.domain.component.domain.Component; +import ject.componote.domain.component.dto.find.event.ComponentViewCountIncreaseEvent; +import ject.componote.domain.component.error.NotFoundComponentException; +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.transaction.annotation.Transactional; + +@org.springframework.stereotype.Component +@RequiredArgsConstructor +public class ComponentViewCountEventHandler { + private final ComponentRepository componentRepository; + + @Async + @EventListener + @Transactional + public void handleViewCountIncrease(final ComponentViewCountIncreaseEvent event) { + final Long componentId = event.componentId(); + final Component component = findComponentById(componentId); + component.increaseViewCount(); + } + + private Component findComponentById(final Long componentId) { + return componentRepository.findById(componentId) + .orElseThrow(() -> new NotFoundComponentException(componentId)); + } +} From 0d1223eae0216b28627cdb19b996a7def32e1f19 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:29:52 +0900 Subject: [PATCH 087/156] =?UTF-8?q?[FEAT]=20InvalidCommentSearchStrategyEx?= =?UTF-8?q?ception=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../error/InvalidCommentSearchStrategyException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/error/InvalidCommentSearchStrategyException.java diff --git a/src/main/java/ject/componote/domain/component/error/InvalidCommentSearchStrategyException.java b/src/main/java/ject/componote/domain/component/error/InvalidCommentSearchStrategyException.java new file mode 100644 index 0000000..6b7e2d2 --- /dev/null +++ b/src/main/java/ject/componote/domain/component/error/InvalidCommentSearchStrategyException.java @@ -0,0 +1,9 @@ +package ject.componote.domain.component.error; + +import org.springframework.http.HttpStatus; + +public class InvalidCommentSearchStrategyException extends ComponentException { + public InvalidCommentSearchStrategyException() { + super("컴포넌트 검색에 실패했습니다. 요청 Body를 확인해주세요.", HttpStatus.BAD_REQUEST); + } +} From 6591efd322a3d7b685c41b1eb74bdd0515f56b61 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:30:04 +0900 Subject: [PATCH 088/156] =?UTF-8?q?[FEAT]=20InvalidComponentImageExtension?= =?UTF-8?q?Exception=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../error/InvalidComponentImageExtensionException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/error/InvalidComponentImageExtensionException.java diff --git a/src/main/java/ject/componote/domain/component/error/InvalidComponentImageExtensionException.java b/src/main/java/ject/componote/domain/component/error/InvalidComponentImageExtensionException.java new file mode 100644 index 0000000..dc6094b --- /dev/null +++ b/src/main/java/ject/componote/domain/component/error/InvalidComponentImageExtensionException.java @@ -0,0 +1,9 @@ +package ject.componote.domain.component.error; + +import org.springframework.http.HttpStatus; + +public class InvalidComponentImageExtensionException extends ComponentException { + public InvalidComponentImageExtensionException(final String extension) { + super("확장자가 올바르지 않습니다. 입력된 확장자: " + extension, HttpStatus.BAD_REQUEST); + } +} From 1e226e3d664451213399895bd55f707598820581 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:30:14 +0900 Subject: [PATCH 089/156] =?UTF-8?q?[FEAT]=20InvalidComponentThumbnailExten?= =?UTF-8?q?sionException=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InvalidComponentThumbnailExtensionException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/error/InvalidComponentThumbnailExtensionException.java diff --git a/src/main/java/ject/componote/domain/component/error/InvalidComponentThumbnailExtensionException.java b/src/main/java/ject/componote/domain/component/error/InvalidComponentThumbnailExtensionException.java new file mode 100644 index 0000000..beae5f5 --- /dev/null +++ b/src/main/java/ject/componote/domain/component/error/InvalidComponentThumbnailExtensionException.java @@ -0,0 +1,9 @@ +package ject.componote.domain.component.error; + +import org.springframework.http.HttpStatus; + +public class InvalidComponentThumbnailExtensionException extends ComponentException { + public InvalidComponentThumbnailExtensionException(final String extension) { + super("확장자가 올바르지 않습니다. 입력된 확장자: " + extension, HttpStatus.BAD_REQUEST); + } +} From b1073055f760adc3cf40070eb59fc36b41f4b8cd Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:30:22 +0900 Subject: [PATCH 090/156] =?UTF-8?q?[FEAT]=20NotFoundComponentException=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/error/NotFoundComponentException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/error/NotFoundComponentException.java diff --git a/src/main/java/ject/componote/domain/component/error/NotFoundComponentException.java b/src/main/java/ject/componote/domain/component/error/NotFoundComponentException.java new file mode 100644 index 0000000..0785f8f --- /dev/null +++ b/src/main/java/ject/componote/domain/component/error/NotFoundComponentException.java @@ -0,0 +1,9 @@ +package ject.componote.domain.component.error; + +import org.springframework.http.HttpStatus; + +public class NotFoundComponentException extends ComponentException { + public NotFoundComponentException(final Long componentId) { + super("컴포넌트를 찾을 수 없습니다. 컴포넌트 ID: " + componentId, HttpStatus.NOT_FOUND); + } +} From bd3dbd63d8102f99e9e32bb645221dfc3a7f0bb6 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:30:27 +0900 Subject: [PATCH 091/156] =?UTF-8?q?[FEAT]=20NotFoundComponentImageExceptio?= =?UTF-8?q?n=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/error/NotFoundComponentImageException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/error/NotFoundComponentImageException.java diff --git a/src/main/java/ject/componote/domain/component/error/NotFoundComponentImageException.java b/src/main/java/ject/componote/domain/component/error/NotFoundComponentImageException.java new file mode 100644 index 0000000..6be9b2e --- /dev/null +++ b/src/main/java/ject/componote/domain/component/error/NotFoundComponentImageException.java @@ -0,0 +1,9 @@ +package ject.componote.domain.component.error; + +import org.springframework.http.HttpStatus; + +public class NotFoundComponentImageException extends ComponentException { + public NotFoundComponentImageException() { + super("이미지 objectKey를 찾을 수 없습니다.", HttpStatus.NOT_FOUND); + } +} From e8c07e93c26da1aa5a962a025b7571ac7eb43516 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:30:34 +0900 Subject: [PATCH 092/156] =?UTF-8?q?[FEAT]=20NotFoundComponentThumbnailExce?= =?UTF-8?q?ption=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../error/NotFoundComponentThumbnailException.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/error/NotFoundComponentThumbnailException.java diff --git a/src/main/java/ject/componote/domain/component/error/NotFoundComponentThumbnailException.java b/src/main/java/ject/componote/domain/component/error/NotFoundComponentThumbnailException.java new file mode 100644 index 0000000..9cf7698 --- /dev/null +++ b/src/main/java/ject/componote/domain/component/error/NotFoundComponentThumbnailException.java @@ -0,0 +1,9 @@ +package ject.componote.domain.component.error; + +import org.springframework.http.HttpStatus; + +public class NotFoundComponentThumbnailException extends ComponentException { + public NotFoundComponentThumbnailException() { + super("이미지 objectKey를 찾을 수 없습니다.", HttpStatus.NOT_FOUND); + } +} From f7df90e63c9c8ba71890d65e5076a492863bfd0a Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:31:01 +0900 Subject: [PATCH 093/156] =?UTF-8?q?[FEAT]=20ComponentDetailResponse=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/ComponentDetailResponse.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/dto/find/response/ComponentDetailResponse.java diff --git a/src/main/java/ject/componote/domain/component/dto/find/response/ComponentDetailResponse.java b/src/main/java/ject/componote/domain/component/dto/find/response/ComponentDetailResponse.java new file mode 100644 index 0000000..54d6034 --- /dev/null +++ b/src/main/java/ject/componote/domain/component/dto/find/response/ComponentDetailResponse.java @@ -0,0 +1,20 @@ +package ject.componote.domain.component.dto.find.response; + +import ject.componote.domain.component.domain.block.BlockType; + +import java.util.List; +import java.util.Map; + +public record ComponentDetailResponse( + String title, + List mixedNames, + String description, + Long commentCount, + Long bookmarkCount, + Long designReferenceCount, + String thumbnailUrl, + Map> blocks, + Boolean isBookmarked +) { + +} From 3d62926678f0cafdda43eb337d7e192bed5a73bf Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:32:09 +0900 Subject: [PATCH 094/156] =?UTF-8?q?[REFACTOR]=20FileClient=20=EC=83=81?= =?UTF-8?q?=EC=88=98=20=EA=B2=BD=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ject/componote/infra/file/application/FileClient.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/ject/componote/infra/file/application/FileClient.java b/src/main/java/ject/componote/infra/file/application/FileClient.java index 5e2a58d..0595d2c 100644 --- a/src/main/java/ject/componote/infra/file/application/FileClient.java +++ b/src/main/java/ject/componote/infra/file/application/FileClient.java @@ -23,10 +23,10 @@ public class FileClient { private final TimeoutDecorator timeoutDecorator; private final WebClient webClient; - public FileClient(@Value("${file.max-retry}") final int maxRetry, - @Value("${file.timeout}") final int timeout, - @Value("${file.client.move.method}") final HttpMethod method, - @Value("${file.client.move.uri}") final String uri, + public FileClient(@Value("${storage.max-retry}") final int maxRetry, + @Value("${storage.timeout}") final int timeout, + @Value("${storage.client.move.method}") final HttpMethod method, + @Value("${storage.client.move.uri}") final String uri, final TimeoutDecorator timeoutDecorator, final WebClient webClient) { this.maxRetry = maxRetry; From 95edf389257db196023fcf3d24e6a0d5c89674a5 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:32:22 +0900 Subject: [PATCH 095/156] =?UTF-8?q?[FEAT]=20ComponentRepository=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/dao/ComponentRepository.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/dao/ComponentRepository.java diff --git a/src/main/java/ject/componote/domain/component/dao/ComponentRepository.java b/src/main/java/ject/componote/domain/component/dao/ComponentRepository.java new file mode 100644 index 0000000..a405be1 --- /dev/null +++ b/src/main/java/ject/componote/domain/component/dao/ComponentRepository.java @@ -0,0 +1,16 @@ +package ject.componote.domain.component.dao; + +import ject.componote.domain.component.domain.Component; +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; + +public interface ComponentRepository extends JpaRepository, ComponentQueryDsl { + @Query( + nativeQuery = true, + value = "UPDATE component c SET c.view_count = c.view_count + 1 WHERE c.id =:id" + ) + @Modifying(clearAutomatically = true) + void increaseViewCount(@Param("id") final Long id); // 변경 감지 대신 사용할 메서드 +} From 2d558fbde4873a3afa7f5014625796d7f7fb7c2a Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:32:28 +0900 Subject: [PATCH 096/156] =?UTF-8?q?[FEAT]=20ComponentQueryDsl=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/component/dao/ComponentQueryDsl.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/dao/ComponentQueryDsl.java diff --git a/src/main/java/ject/componote/domain/component/dao/ComponentQueryDsl.java b/src/main/java/ject/componote/domain/component/dao/ComponentQueryDsl.java new file mode 100644 index 0000000..6ea0ae4 --- /dev/null +++ b/src/main/java/ject/componote/domain/component/dao/ComponentQueryDsl.java @@ -0,0 +1,14 @@ +package ject.componote.domain.component.dao; + +import ject.componote.domain.component.domain.ComponentType; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +public interface ComponentQueryDsl { + Page searchByKeywordWithTypes(final String keyword, final List types, final Pageable pageable); + Page searchByKeyword(final String keyword, final Pageable pageable); + Page searchWithBookmark(final Long memberId, final String keyword, final Pageable pageable); + Page searchWithBookmarkAndTypes(final Long memberId, final String keyword, final List types, final Pageable pageable); +} From 2846a24255a9e31152422b30e65a706d5ba74cc1 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:32:33 +0900 Subject: [PATCH 097/156] =?UTF-8?q?[FEAT]=20ComponentQueryDslImpl=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/dao/ComponentQueryDslImpl.java | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/dao/ComponentQueryDslImpl.java diff --git a/src/main/java/ject/componote/domain/component/dao/ComponentQueryDslImpl.java b/src/main/java/ject/componote/domain/component/dao/ComponentQueryDslImpl.java new file mode 100644 index 0000000..4214b5c --- /dev/null +++ b/src/main/java/ject/componote/domain/component/dao/ComponentQueryDslImpl.java @@ -0,0 +1,91 @@ +package ject.componote.domain.component.dao; + +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import ject.componote.domain.component.domain.ComponentType; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +import static ject.componote.domain.bookmark.domain.QBookmark.bookmark; +import static ject.componote.domain.component.domain.QComponent.component; +import static ject.componote.domain.component.domain.QMixedName.mixedName; +import static ject.componote.global.util.RepositoryUtils.eqExpression; +import static ject.componote.global.util.RepositoryUtils.toPage; + +@RequiredArgsConstructor +public class ComponentQueryDslImpl implements ComponentQueryDsl { + private final QComponentDaoFactory componentDaoFactory; + private final JPAQueryFactory queryFactory; + + @Override + public Page searchByKeyword(final String keyword, final Pageable pageable) { + return search(null, keyword, null, pageable, false); + } + + @Override + public Page searchByKeywordWithTypes(final String keyword, final List types, final Pageable pageable) { + return search(null, keyword, types, pageable, false); + } + + @Override + public Page searchWithBookmark(final Long memberId, final String keyword, final Pageable pageable) { + return search(memberId, keyword, null, pageable, true); + } + + @Override + public Page searchWithBookmarkAndTypes(final Long memberId, final String keyword, final List types, final Pageable pageable) { + return search(memberId, keyword, types, pageable, true); + } + + public Page search(final Long memberId, + final String keyword, + final List types, + final Pageable pageable, + final boolean withBookmark) { + final JPAQuery countQuery = createCountQuery(keyword, types); + final JPAQuery baseQuery = createBaseQuery(memberId, keyword, types, withBookmark); + return toPage(baseQuery, countQuery, component, pageable); + } + + private JPAQuery createCountQuery(final String keyword, final List types) { + return queryFactory.select(component.countDistinct()) + .from(component) + .leftJoin(component.mixedNames, mixedName) + .where(createSearchCondition(keyword, types)); + } + + private JPAQuery createBaseQuery(final Long memberId, + final String keyword, + final List types, + final boolean withBookmark) { + final JPAQuery query = queryFactory.selectDistinct(componentDaoFactory.createForSummary(withBookmark)) + .from(component) + .leftJoin(component.mixedNames, mixedName); + + if (withBookmark && memberId != null) { + query.leftJoin(bookmark) + .on(eqExpression(bookmark.componentId, component.id) + .and(eqExpression(bookmark.memberId, memberId))); + } + + return query.where(createSearchCondition(keyword, types)); + } + + private BooleanExpression createSearchCondition(final String keyword, final List types) { + final BooleanExpression keywordCondition = createKeywordCondition(keyword); + if (types != null && !types.isEmpty()) { + return keywordCondition.and(component.type.in(types)); + } + + return keywordCondition; + } + + private static BooleanExpression createKeywordCondition(final String keyword) { + return mixedName.name.contains(keyword) + .or(component.summary.title.contains(keyword)); + } +} From 92cf58f6cb5e1c767541eedf09cec5f02a92ea24 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:32:41 +0900 Subject: [PATCH 098/156] =?UTF-8?q?[FEAT]=20ComponentService=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ComponentService.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/application/ComponentService.java diff --git a/src/main/java/ject/componote/domain/component/application/ComponentService.java b/src/main/java/ject/componote/domain/component/application/ComponentService.java new file mode 100644 index 0000000..2276772 --- /dev/null +++ b/src/main/java/ject/componote/domain/component/application/ComponentService.java @@ -0,0 +1,57 @@ +package ject.componote.domain.component.application; + +import ject.componote.domain.auth.model.AuthPrincipal; +import ject.componote.domain.bookmark.dao.BookmarkRepository; +import ject.componote.domain.common.dto.response.PageResponse; +import ject.componote.domain.component.dao.ComponentRepository; +import ject.componote.domain.component.domain.Component; +import ject.componote.domain.component.dto.find.event.ComponentViewCountIncreaseEvent; +import ject.componote.domain.component.dto.find.request.ComponentSearchRequest; +import ject.componote.domain.component.dto.find.response.ComponentDetailResponse; +import ject.componote.domain.component.dto.find.response.ComponentSummaryResponse; +import ject.componote.domain.component.error.NotFoundComponentException; +import ject.componote.domain.component.util.ComponentMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ComponentService { + private final ApplicationEventPublisher eventPublisher; + private final BookmarkRepository bookmarkRepository; + private final ComponentMapper componentMapper; + private final ComponentRepository componentRepository; + + public ComponentDetailResponse getComponentDetail(final AuthPrincipal authPrincipal, final Long componentId) { + final Component component = findComponentById(componentId); + eventPublisher.publishEvent(ComponentViewCountIncreaseEvent.from(component)); + return componentMapper.mapFrom(component, isBookmarked(authPrincipal, componentId)); + } + + public PageResponse search(final AuthPrincipal authPrincipal, + final ComponentSearchRequest request, + final Pageable pageable) { + final Page page = ComponentSearchStrategy.searchBy(authPrincipal, componentRepository, request, pageable) + .map(ComponentSummaryResponse::from); + return PageResponse.from(page); + } + + private Component findComponentById(final Long componentId) { + return componentRepository.findById(componentId) + .orElseThrow(() -> new NotFoundComponentException(componentId)); + } + + private boolean isBookmarked(final AuthPrincipal authPrincipal, final Long componentId) { + if (authPrincipal == null) { + return false; + } + + final Long memberId = authPrincipal.id(); + return bookmarkRepository.existsByComponentIdAndMemberId(componentId, memberId); + } +} From 2ffb343fbfb9ffd116d78801662c50df819f3c7a Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:32:46 +0900 Subject: [PATCH 099/156] =?UTF-8?q?[FEAT]=20ComponentController=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/api/ComponentController.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/api/ComponentController.java diff --git a/src/main/java/ject/componote/domain/component/api/ComponentController.java b/src/main/java/ject/componote/domain/component/api/ComponentController.java new file mode 100644 index 0000000..e7093ed --- /dev/null +++ b/src/main/java/ject/componote/domain/component/api/ComponentController.java @@ -0,0 +1,45 @@ +package ject.componote.domain.component.api; + +import jakarta.validation.Valid; +import ject.componote.domain.auth.model.AuthPrincipal; +import ject.componote.domain.auth.model.Authenticated; +import ject.componote.domain.common.dto.response.PageResponse; +import ject.componote.domain.component.application.ComponentService; +import ject.componote.domain.component.dto.find.request.ComponentSearchRequest; +import ject.componote.domain.component.dto.find.response.ComponentSummaryResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/components") +@RequiredArgsConstructor +@RestController +public class ComponentController { + private final ComponentService componentService; + + @GetMapping("/search") + public ResponseEntity> search( + @Authenticated final AuthPrincipal authPrincipal, + @ModelAttribute @Valid final ComponentSearchRequest request, + @PageableDefault final Pageable pageable + ) { + return ResponseEntity.ok( + componentService.search(authPrincipal, request, pageable) + ); + } + + @GetMapping("/{componentId}") + public ResponseEntity getComponentDetail( + @Authenticated final AuthPrincipal authPrincipal, + @PathVariable("componentId") final Long componentId) { + return ResponseEntity.ok( + componentService.getComponentDetail(authPrincipal, componentId) + ); + } +} From 49e0286586de553f25121d1e06e8b3e6c87f9705 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:33:06 +0900 Subject: [PATCH 100/156] =?UTF-8?q?[FEAT]=20TextBlock=EC=97=90=20getValue(?= =?UTF-8?q?)=20=EC=B6=94=EC=83=81=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/component/domain/block/detail/TextBlock.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/ject/componote/domain/component/domain/block/detail/TextBlock.java b/src/main/java/ject/componote/domain/component/domain/block/detail/TextBlock.java index 050540c..e477d28 100644 --- a/src/main/java/ject/componote/domain/component/domain/block/detail/TextBlock.java +++ b/src/main/java/ject/componote/domain/component/domain/block/detail/TextBlock.java @@ -29,4 +29,9 @@ private TextBlock(final BlockType type, final ComponentContent content, final In public static TextBlock of(final BlockType type, final ComponentContent content, final Integer order) { return new TextBlock(type, content, order); } + + @Override + public String getValue() { + return content.getValue(); + } } From f34fda06036bde52dd2a48a32f5d25c577aecba0 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:33:13 +0900 Subject: [PATCH 101/156] =?UTF-8?q?[FEAT]=20ImageBlock=EC=97=90=20getValue?= =?UTF-8?q?()=20=EC=B6=94=EC=83=81=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/block/detail/ImageBlock.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/java/ject/componote/domain/component/domain/block/detail/ImageBlock.java b/src/main/java/ject/componote/domain/component/domain/block/detail/ImageBlock.java index ae85b68..f367ff6 100644 --- a/src/main/java/ject/componote/domain/component/domain/block/detail/ImageBlock.java +++ b/src/main/java/ject/componote/domain/component/domain/block/detail/ImageBlock.java @@ -3,10 +3,10 @@ import jakarta.persistence.Column; import jakarta.persistence.Convert; import jakarta.persistence.Entity; -import ject.componote.domain.common.model.BaseImage; -import ject.componote.domain.common.model.converter.BaseImageConverter; import ject.componote.domain.component.domain.block.BlockType; import ject.componote.domain.component.domain.block.ContentBlock; +import ject.componote.domain.component.model.ComponentImage; +import ject.componote.domain.component.model.converter.ComponentImageConverter; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -17,16 +17,21 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @ToString public class ImageBlock extends ContentBlock { - @Convert(converter = BaseImageConverter.class) + @Convert(converter = ComponentImageConverter.class) @Column(name = "image", nullable = false) - private BaseImage image; + private ComponentImage image; - private ImageBlock(final BlockType type, final BaseImage image, final Integer order) { + private ImageBlock(final BlockType type, final ComponentImage image, final Integer order) { super(type, order); this.image = image; } - public static ImageBlock of(final BlockType type, final BaseImage image, final Integer order) { + public static ImageBlock of(final BlockType type, final ComponentImage image, final Integer order) { return new ImageBlock(type, image, order); } + + @Override + public String getValue() { + return image.getImage().toUrl(); + } } \ No newline at end of file From 978ff3b72492ecf968d55ccaa14bf33466b4b5d3 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:33:29 +0900 Subject: [PATCH 102/156] =?UTF-8?q?[TEST]=20ComponentFixture=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../componote/fixture/ComponentFixture.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/test/java/ject/componote/fixture/ComponentFixture.java diff --git a/src/test/java/ject/componote/fixture/ComponentFixture.java b/src/test/java/ject/componote/fixture/ComponentFixture.java new file mode 100644 index 0000000..7febc89 --- /dev/null +++ b/src/test/java/ject/componote/fixture/ComponentFixture.java @@ -0,0 +1,29 @@ +package ject.componote.fixture; + +import ject.componote.domain.component.domain.Component; +import ject.componote.domain.component.domain.ComponentType; + +import java.util.Collections; +import java.util.List; + +public enum ComponentFixture { + INPUT_COMPONENT("input title", "input description", "objectKey.jpg", ComponentType.INPUT, List.of("hello")); + + private final String title; + private final String description; + private final String thumbnailObjectKey; + private final ComponentType type; + private final List mixedNames; + + ComponentFixture(final String title, final String description, final String thumbnailObjectKey, final ComponentType type, final List mixedNames) { + this.title = title; + this.description = description; + this.thumbnailObjectKey = thumbnailObjectKey; + this.type = type; + this.mixedNames = mixedNames; + } + + public Component 생성() { + return Component.of(title, description, thumbnailObjectKey, type, mixedNames, Collections.emptyList()); + } +} From 7b7b2f66b87dde264129a7906c351d3e64d4d0e9 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:39:09 +0900 Subject: [PATCH 103/156] =?UTF-8?q?[REFACTOR]=20ALLOWED=5FIMAGE=5FEXTENSIO?= =?UTF-8?q?NS=20=EC=83=9D=EC=84=B1=EC=9D=84=20Arrays.asList()=20=EB=A1=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ject/componote/domain/component/model/ComponentImage.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/ject/componote/domain/component/model/ComponentImage.java b/src/main/java/ject/componote/domain/component/model/ComponentImage.java index 5b19569..0de7ac4 100644 --- a/src/main/java/ject/componote/domain/component/model/ComponentImage.java +++ b/src/main/java/ject/componote/domain/component/model/ComponentImage.java @@ -7,12 +7,14 @@ import lombok.Getter; import org.springframework.util.StringUtils; +import java.util.Arrays; import java.util.List; @EqualsAndHashCode @Getter public class ComponentImage { - private static final List ALLOWED_IMAGE_EXTENSIONS = List.of("png"); + // Arrays.asList 로 만든 List: contains(null) 시 NPE 발생하지 않고 false 리턴 + private static final List ALLOWED_IMAGE_EXTENSIONS = Arrays.asList("png"); private final BaseImage image; From 44cee1987cbe9ef2d89a8b24873f7083a02a55c3 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:39:14 +0900 Subject: [PATCH 104/156] =?UTF-8?q?[TEST]=20ComponentImageTest=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/model/ComponentImageTest.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/test/java/ject/componote/domain/component/model/ComponentImageTest.java diff --git a/src/test/java/ject/componote/domain/component/model/ComponentImageTest.java b/src/test/java/ject/componote/domain/component/model/ComponentImageTest.java new file mode 100644 index 0000000..67aafef --- /dev/null +++ b/src/test/java/ject/componote/domain/component/model/ComponentImageTest.java @@ -0,0 +1,31 @@ +package ject.componote.domain.component.model; + +import ject.componote.domain.component.error.InvalidComponentImageExtensionException; +import ject.componote.domain.component.error.NotFoundComponentImageException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ComponentImageTest { + @ParameterizedTest(name = "값: {0}") + @DisplayName("확장자가 잘못된 경우") + @ValueSource(strings = {"hello.gif", "hello", "hello.jqp"}) + public void invalidExtension(final String objectKey) { + assertThatThrownBy(() -> ComponentImage.from(objectKey)) + .isInstanceOf(InvalidComponentImageExtensionException.class); + } + + @Test + @DisplayName("objectKey가 전달되지 않으면 예외 발생") + public void createDefault() { + // given + final String objectKey = null; + + // then + assertThatThrownBy(() -> ComponentImage.from(objectKey)) + .isInstanceOf(NotFoundComponentImageException.class); + } +} \ No newline at end of file From 3416f848352b81b2afd1160ea762b47035edd0b8 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:39:22 +0900 Subject: [PATCH 105/156] =?UTF-8?q?[TEST]=20ComponentMapperTest=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/util/ComponentMapperTest.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/test/java/ject/componote/domain/component/util/ComponentMapperTest.java diff --git a/src/test/java/ject/componote/domain/component/util/ComponentMapperTest.java b/src/test/java/ject/componote/domain/component/util/ComponentMapperTest.java new file mode 100644 index 0000000..4b3c6a8 --- /dev/null +++ b/src/test/java/ject/componote/domain/component/util/ComponentMapperTest.java @@ -0,0 +1,39 @@ +package ject.componote.domain.component.util; + +import ject.componote.domain.component.domain.Component; +import ject.componote.domain.component.dto.find.response.ComponentDetailResponse; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; + +import static ject.componote.fixture.ComponentFixture.INPUT_COMPONENT; +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(MockitoExtension.class) +class ComponentMapperTest { + @InjectMocks + ComponentMapper componentMapper; + + @ParameterizedTest + @DisplayName("Component를 ComponentDetailResponse로 변환") + @ValueSource(booleans = {true, false}) + public void mapFromWithBookmark(final boolean isBookmarked) throws Exception { + // given + final Component component = INPUT_COMPONENT.생성(); + + // when + final ComponentDetailResponse response = componentMapper.mapFrom(component, isBookmarked); + + // then + assertThat(response).isNotNull(); + assertThat(response.isBookmarked()).isEqualTo(isBookmarked); + assertThat(response.bookmarkCount()).isEqualTo(component.getBookmarkCount().getValue()); + assertThat(response.commentCount()).isEqualTo(component.getCommentCount().getValue()); + assertThat(response.title()).isEqualTo(component.getSummary().getTitle()); + assertThat(response.description()).isEqualTo(component.getSummary().getDescription()); + assertThat(response.thumbnailUrl()).isEqualTo(component.getSummary().getThumbnail().getImage().toUrl()); + } +} \ No newline at end of file From 13a7c009899e900084a5a6c4641b73c636e407fa Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:39:29 +0900 Subject: [PATCH 106/156] =?UTF-8?q?[TEST]=20ComponentServiceTest=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ComponentServiceTest.java | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 src/test/java/ject/componote/domain/component/application/ComponentServiceTest.java diff --git a/src/test/java/ject/componote/domain/component/application/ComponentServiceTest.java b/src/test/java/ject/componote/domain/component/application/ComponentServiceTest.java new file mode 100644 index 0000000..1dcbe58 --- /dev/null +++ b/src/test/java/ject/componote/domain/component/application/ComponentServiceTest.java @@ -0,0 +1,210 @@ +package ject.componote.domain.component.application; + +import ject.componote.domain.auth.model.AuthPrincipal; +import ject.componote.domain.bookmark.dao.BookmarkRepository; +import ject.componote.domain.common.dto.response.PageResponse; +import ject.componote.domain.component.dao.ComponentRepository; +import ject.componote.domain.component.dao.ComponentSummaryDao; +import ject.componote.domain.component.domain.Component; +import ject.componote.domain.component.domain.ComponentType; +import ject.componote.domain.component.dto.find.event.ComponentViewCountIncreaseEvent; +import ject.componote.domain.component.dto.find.request.ComponentSearchRequest; +import ject.componote.domain.component.dto.find.response.ComponentDetailResponse; +import ject.componote.domain.component.dto.find.response.ComponentSummaryResponse; +import ject.componote.domain.component.error.NotFoundComponentException; +import ject.componote.domain.component.util.ComponentMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import static ject.componote.fixture.ComponentFixture.INPUT_COMPONENT; +import static ject.componote.fixture.MemberFixture.KIM; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class ComponentServiceTest { + @Mock + ApplicationEventPublisher eventPublisher; + + @Mock + BookmarkRepository bookmarkRepository; + + @Mock + ComponentMapper componentMapper; + + @Mock + ComponentRepository componentRepository; + + @InjectMocks + ComponentService componentService; + + Component component = INPUT_COMPONENT.생성(); + + static Stream provideDetailInputs() { + final AuthPrincipal authPrincipal = AuthPrincipal.from(KIM.생성(1L)); + return Stream.of( + new DetailInput("비회원", null, false), + new DetailInput("회원 (북마크 X)", authPrincipal, false), + new DetailInput("회원 (북마크 O)", authPrincipal, true) + ); + } + + static Stream provideSearchInputs() { + final AuthPrincipal authPrincipal = AuthPrincipal.from(KIM.생성(1L)); + return Stream.of( + new SearchInput("비회원, 필터 X", null, "검색어", null), + new SearchInput("비회원, 필터 O", null, "검색어", List.of(ComponentType.INPUT)), + new SearchInput("회원, 필터 X", authPrincipal, "검색어", null), + new SearchInput("회원, 필터 O", authPrincipal, "검색어", List.of(ComponentType.INPUT)) + ); + } + + @ParameterizedTest + @MethodSource("provideDetailInputs") + @DisplayName("컴포넌트 상세 조회") + public void getComponentDetail(final DetailInput input) throws Exception { + // given + final Long componentId = component.getId(); + final ComponentViewCountIncreaseEvent event = ComponentViewCountIncreaseEvent.from(component); + final ComponentDetailResponse expect = new ComponentDetailResponse( + component.getSummary().getTitle(), + Collections.emptyList(), + component.getSummary().getDescription(), + component.getCommentCount().getValue(), + component.getBookmarkCount().getValue(), + component.getDesignReferenceCount().getValue(), + component.getSummary().getThumbnail().getImage().toUrl(), + Collections.emptyMap(), + input.isBookmarked + ); + + // when + doReturn(Optional.of(component)).when(componentRepository) + .findById(componentId); + doNothing().when(eventPublisher) + .publishEvent(event); + doReturn(expect).when(componentMapper) + .mapFrom(component, input.isBookmarked); + if (input.isBookmarked) { + doReturn(true).when(bookmarkRepository) + .existsByComponentIdAndMemberId(componentId, input.authPrincipal.id()); + } + + final ComponentDetailResponse actual = componentService.getComponentDetail(input.authPrincipal, componentId); + + // then + assertThat(actual).isEqualTo(expect); + } + + @ParameterizedTest + @MethodSource("provideDetailInputs") + @DisplayName("컴포넌트 상세 조회 시 componentId 가 잘못된 경우 예외 발생") + public void getComponentDetailWhenInvalidComponentId(final DetailInput input) throws Exception { + // given + final Long componentId = component.getId(); + + // when + doReturn(Optional.empty()).when(componentRepository) + .findById(componentId); + + // then + assertThatThrownBy(() -> componentService.getComponentDetail(input.authPrincipal, componentId)) + .isInstanceOf(NotFoundComponentException.class); + } + + @ParameterizedTest + @MethodSource("provideSearchInputs") + @DisplayName("컴포넌트 검색") + public void search(final SearchInput input) throws Exception { + // given + final AuthPrincipal authPrincipal = input.authPrincipal; + final String keyword = input.keyword; + final List types = input.types; + final ComponentSearchRequest request = input.toRequest(); + final Pageable pageable = input.pageable; + final Page page = new PageImpl<>(Collections.emptyList(), pageable, 0L); + + // when + switch (input.displayName) { + case "비회원, 필터 X" -> doReturn(page).when(componentRepository) + .searchByKeyword(keyword, pageable); + case "비회원, 필터 O" -> doReturn(page).when(componentRepository) + .searchByKeywordWithTypes(keyword, types, pageable); + case "회원, 필터 X" -> doReturn(page).when(componentRepository) + .searchWithBookmark(authPrincipal.id(), keyword, pageable); + case "회원, 필터 O" -> doReturn(page).when(componentRepository) + .searchWithBookmarkAndTypes(authPrincipal.id(), keyword, types, pageable); + default -> throw new IllegalStateException("Unexpected value: " + input.displayName); + } + + final PageResponse actual = componentService.search(authPrincipal, request, pageable); + + // then + assertThat(actual).isNotNull(); + switch (input.displayName) { + case "비회원, 필터 X" -> verify(componentRepository) + .searchByKeyword(keyword, pageable); + case "비회원, 필터 O" -> verify(componentRepository) + .searchByKeywordWithTypes(keyword, types, pageable); + case "회원, 필터 X" -> verify(componentRepository) + .searchWithBookmark(authPrincipal.id(), keyword, pageable); + case "회원, 필터 O" -> verify(componentRepository) + .searchWithBookmarkAndTypes(authPrincipal.id(), keyword, types, pageable); + default -> throw new IllegalStateException("Unexpected value: " + input.displayName); + } + } + + static class DetailInput { + String displayName; + AuthPrincipal authPrincipal; + boolean isBookmarked; + + public DetailInput(final String displayName, + final AuthPrincipal authPrincipal, + final boolean isBookmarked) { + this.displayName = displayName; + this.authPrincipal = authPrincipal; + this.isBookmarked = isBookmarked; + } + } + + static class SearchInput { + String displayName; + AuthPrincipal authPrincipal; + String keyword; + List types; + Pageable pageable; + + public SearchInput(final String displayName, + final AuthPrincipal authPrincipal, + final String keyword, + final List types) { + this.displayName = displayName; + this.authPrincipal = authPrincipal; + this.keyword = keyword; + this.types = types; + this.pageable = Pageable.unpaged(); + } + + public ComponentSearchRequest toRequest() { + return new ComponentSearchRequest(keyword, types); + } + } +} \ No newline at end of file From f3be836326d9dc8323883b2af9a24f491e57965b Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:39:34 +0900 Subject: [PATCH 107/156] =?UTF-8?q?[TEST]=20ComponentThumbnailTest=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/ComponentThumbnailTest.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/test/java/ject/componote/domain/component/model/ComponentThumbnailTest.java diff --git a/src/test/java/ject/componote/domain/component/model/ComponentThumbnailTest.java b/src/test/java/ject/componote/domain/component/model/ComponentThumbnailTest.java new file mode 100644 index 0000000..765157d --- /dev/null +++ b/src/test/java/ject/componote/domain/component/model/ComponentThumbnailTest.java @@ -0,0 +1,31 @@ +package ject.componote.domain.component.model; + +import ject.componote.domain.component.error.InvalidComponentThumbnailExtensionException; +import ject.componote.domain.component.error.NotFoundComponentThumbnailException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ComponentThumbnailTest { + @ParameterizedTest(name = "값: {0}") + @DisplayName("확장자가 잘못된 경우") + @ValueSource(strings = {"hello.gif", "hello", "hello.jqp"}) + public void invalidExtension(final String objectKey) { + assertThatThrownBy(() -> ComponentThumbnail.from(objectKey)) + .isInstanceOf(InvalidComponentThumbnailExtensionException.class); + } + + @Test + @DisplayName("objectKey가 전달되지 않으면 예외 발생") + public void createDefault() { + // given + final String objectKey = null; + + // then + assertThatThrownBy(() -> ComponentThumbnail.from(objectKey)) + .isInstanceOf(NotFoundComponentThumbnailException.class); + } +} \ No newline at end of file From a79cf4a6bb5e373506d3b51a3fd717ef3e2ffff8 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:39:43 +0900 Subject: [PATCH 108/156] =?UTF-8?q?[TEST]=20ComponentViewCountEventHandler?= =?UTF-8?q?Test=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ComponentViewCountEventHandlerTest.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/test/java/ject/componote/domain/component/application/ComponentViewCountEventHandlerTest.java diff --git a/src/test/java/ject/componote/domain/component/application/ComponentViewCountEventHandlerTest.java b/src/test/java/ject/componote/domain/component/application/ComponentViewCountEventHandlerTest.java new file mode 100644 index 0000000..861789d --- /dev/null +++ b/src/test/java/ject/componote/domain/component/application/ComponentViewCountEventHandlerTest.java @@ -0,0 +1,67 @@ +package ject.componote.domain.component.application; + +import ject.componote.domain.common.model.Count; +import ject.componote.domain.component.dao.ComponentRepository; +import ject.componote.domain.component.domain.Component; +import ject.componote.domain.component.dto.find.event.ComponentViewCountIncreaseEvent; +import ject.componote.domain.component.error.NotFoundComponentException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static ject.componote.fixture.ComponentFixture.INPUT_COMPONENT; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.doReturn; + +@ExtendWith(MockitoExtension.class) +class ComponentViewCountEventHandlerTest { + @Mock + ComponentRepository componentRepository; + + @InjectMocks + ComponentViewCountEventHandler componentViewCountEventHandler; + + Component component = INPUT_COMPONENT.생성(); + + @Test + @DisplayName("조회 수 증가") + public void handleViewCountIncrease() throws Exception { + // given + final ComponentViewCountIncreaseEvent event = ComponentViewCountIncreaseEvent.from(component); + final Long componentId = component.getId(); + final Count previousViewCount = component.getViewCount(); + + // when + doReturn(Optional.of(component)).when(componentRepository) + .findById(componentId); + componentViewCountEventHandler.handleViewCountIncrease(event); + + // then + final Count newViewCount = component.getViewCount(); + previousViewCount.increase(); + assertThat(previousViewCount).isEqualTo(newViewCount); + } + + @Test + @DisplayName("조회 수 증가 시 componentId 가 잘못된 경우 예외 발생") + public void handleViewCountIncreaseWhenInvalidComponentId() throws Exception { + // given + final ComponentViewCountIncreaseEvent event = ComponentViewCountIncreaseEvent.from(component); + final Long componentId = component.getId(); + + // when + doReturn(Optional.empty()).when(componentRepository) + .findById(componentId); + + // then + assertThatThrownBy(() -> componentViewCountEventHandler.handleViewCountIncrease(event)) + .isInstanceOf(NotFoundComponentException.class); + + } +} \ No newline at end of file From 22fd7a49678b1229bbe2c357a91898c41b5b747b Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 15:46:07 +0900 Subject: [PATCH 109/156] =?UTF-8?q?[TEST]=20ComponentSearchStrategyParamet?= =?UTF-8?q?erizedTest=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ComponentSearchStrategyTest.java | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 src/test/java/ject/componote/domain/component/application/ComponentSearchStrategyTest.java diff --git a/src/test/java/ject/componote/domain/component/application/ComponentSearchStrategyTest.java b/src/test/java/ject/componote/domain/component/application/ComponentSearchStrategyTest.java new file mode 100644 index 0000000..04e802d --- /dev/null +++ b/src/test/java/ject/componote/domain/component/application/ComponentSearchStrategyTest.java @@ -0,0 +1,133 @@ +package ject.componote.domain.component.application; + +import ject.componote.domain.auth.model.AuthPrincipal; +import ject.componote.domain.component.dao.ComponentRepository; +import ject.componote.domain.component.dao.ComponentSummaryDao; +import ject.componote.domain.component.domain.ComponentType; +import ject.componote.domain.component.dto.find.request.ComponentSearchRequest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; + +import java.util.List; +import java.util.stream.Stream; + +import static ject.componote.fixture.MemberFixture.KIM; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class ComponentSearchStrategyParameterizedTest { + @Mock + ComponentRepository componentRepository; + + static Stream provideTestInputs() { + final AuthPrincipal authPrincipal = AuthPrincipal.from(KIM.생성(1L)); + final List types = List.of(ComponentType.INPUT, ComponentType.DISPLAY); + return Stream.of( + new TestInput( + "WITH_BOOKMARK_AND_FILTER", + new ComponentSearchRequest("keyword", types), + authPrincipal, + true, + "searchWithBookmarkAndTypes" + ), + new TestInput( + "WITH_BOOKMARK", + new ComponentSearchRequest("keyword", null), + authPrincipal, + true, + "searchWithBookmark" + ), + new TestInput( + "WITHOUT_BOOKMARK_AND_FILTER", + new ComponentSearchRequest("keyword", null), + null, + false, + "searchByKeyword" + ), + new TestInput( + "WITHOUT_BOOKMARK", + new ComponentSearchRequest("keyword", types), + null, + false, + "searchByKeywordWithTypes" + ) + ); + } + + @ParameterizedTest + @MethodSource("provideTestInputs") + @DisplayName("요청값에 알맞는 검색 메서드 실행") + void testComponentSearchStrategy(final TestInput input) { + // given + final AuthPrincipal authPrincipal = input.authPrincipal; + final String keyword = input.request.keyword(); + final List types = input.request.types(); + final Pageable pageable = Pageable.unpaged(); + final Page expect = new PageImpl<>(List.of()); + + // when + switch (input.expectedMethod) { + case "searchWithBookmarkAndTypes" -> doReturn(expect).when(componentRepository) + .searchWithBookmarkAndTypes(authPrincipal.id(), keyword, types, pageable); + case "searchWithBookmark" -> doReturn(expect).when(componentRepository) + .searchWithBookmark(authPrincipal.id(), keyword, pageable); + case "searchByKeywordWithTypes" -> doReturn(expect).when(componentRepository) + .searchByKeywordWithTypes(keyword, types, pageable); + case "searchByKeyword" -> doReturn(expect).when(componentRepository) + .searchByKeyword(keyword, pageable); + default -> throw new IllegalStateException("Unexpected value: " + input.expectedMethod); + } + + final Page actual = ComponentSearchStrategy.searchBy( + authPrincipal, + componentRepository, + input.request, + pageable + ); + + // then + assertNotNull(actual); + + // 메서드 호출 여부 검증 + switch (input.expectedMethod) { + case "searchWithBookmarkAndTypes" -> verify(componentRepository) + .searchWithBookmarkAndTypes(authPrincipal.id(), keyword, types, pageable); + case "searchWithBookmark" -> verify(componentRepository) + .searchWithBookmark(authPrincipal.id(), keyword, pageable); + case "searchByKeywordWithTypes" -> verify(componentRepository) + .searchByKeywordWithTypes(keyword, types, pageable); + case "searchByKeyword" -> verify(componentRepository) + .searchByKeyword(keyword, pageable); + default -> throw new IllegalStateException("Unexpected value: " + input.expectedMethod); + } + } + + static class TestInput { + String strategyName; + ComponentSearchRequest request; + AuthPrincipal authPrincipal; + boolean isLoggedIn; + String expectedMethod; + + TestInput(final String strategyName, + final ComponentSearchRequest request, + final AuthPrincipal authPrincipal, + final boolean isLoggedIn, + final String expectedMethod) { + this.strategyName = strategyName; + this.request = request; + this.authPrincipal = authPrincipal; + this.isLoggedIn = isLoggedIn; + this.expectedMethod = expectedMethod; + } + } +} \ No newline at end of file From c469dadf64ad088f6128a59b13d0f800976310d1 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 16:15:21 +0900 Subject: [PATCH 110/156] =?UTF-8?q?[REFACTOR]=20FAQRepository,=20NoticeRep?= =?UTF-8?q?ository=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/announcement/{domain => dao}/FAQRepository.java | 3 ++- .../domain/announcement/{domain => dao}/NoticeRepository.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) rename src/main/java/ject/componote/domain/announcement/{domain => dao}/FAQRepository.java (56%) rename src/main/java/ject/componote/domain/announcement/{domain => dao}/NoticeRepository.java (56%) diff --git a/src/main/java/ject/componote/domain/announcement/domain/FAQRepository.java b/src/main/java/ject/componote/domain/announcement/dao/FAQRepository.java similarity index 56% rename from src/main/java/ject/componote/domain/announcement/domain/FAQRepository.java rename to src/main/java/ject/componote/domain/announcement/dao/FAQRepository.java index c6a0e3e..b9b3faf 100644 --- a/src/main/java/ject/componote/domain/announcement/domain/FAQRepository.java +++ b/src/main/java/ject/componote/domain/announcement/dao/FAQRepository.java @@ -1,5 +1,6 @@ -package ject.componote.domain.announcement.domain; +package ject.componote.domain.announcement.dao; +import ject.componote.domain.announcement.domain.FAQ; import org.springframework.data.jpa.repository.JpaRepository; public interface FAQRepository extends JpaRepository { diff --git a/src/main/java/ject/componote/domain/announcement/domain/NoticeRepository.java b/src/main/java/ject/componote/domain/announcement/dao/NoticeRepository.java similarity index 56% rename from src/main/java/ject/componote/domain/announcement/domain/NoticeRepository.java rename to src/main/java/ject/componote/domain/announcement/dao/NoticeRepository.java index e1a72c4..466bea3 100644 --- a/src/main/java/ject/componote/domain/announcement/domain/NoticeRepository.java +++ b/src/main/java/ject/componote/domain/announcement/dao/NoticeRepository.java @@ -1,5 +1,6 @@ -package ject.componote.domain.announcement.domain; +package ject.componote.domain.announcement.dao; +import ject.componote.domain.announcement.domain.Notice; import org.springframework.data.jpa.repository.JpaRepository; public interface NoticeRepository extends JpaRepository { From 08e33979bcd120a7c596e5a70bbf9d9209f75b9a Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 16:36:07 +0900 Subject: [PATCH 111/156] =?UTF-8?q?[FEAT]=20FAQContent=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/faq/model/FAQContent.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/java/ject/componote/domain/faq/model/FAQContent.java diff --git a/src/main/java/ject/componote/domain/faq/model/FAQContent.java b/src/main/java/ject/componote/domain/faq/model/FAQContent.java new file mode 100644 index 0000000..09719da --- /dev/null +++ b/src/main/java/ject/componote/domain/faq/model/FAQContent.java @@ -0,0 +1,20 @@ +package ject.componote.domain.faq.model; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@EqualsAndHashCode +@Getter +@ToString +public class FAQContent { + private final String value; + + private FAQContent(final String value) { + this.value = value; + } + + public static FAQContent from(final String value) { + return new FAQContent(value); + } +} From c763ed735d1f04686b630d1f25db6f39207bf9d4 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 16:36:12 +0900 Subject: [PATCH 112/156] =?UTF-8?q?[FEAT]=20FAQContentConverter=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/converter/FAQContentConverter.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/java/ject/componote/domain/faq/model/converter/FAQContentConverter.java diff --git a/src/main/java/ject/componote/domain/faq/model/converter/FAQContentConverter.java b/src/main/java/ject/componote/domain/faq/model/converter/FAQContentConverter.java new file mode 100644 index 0000000..f62c23c --- /dev/null +++ b/src/main/java/ject/componote/domain/faq/model/converter/FAQContentConverter.java @@ -0,0 +1,18 @@ +package ject.componote.domain.faq.model.converter; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +import ject.componote.domain.faq.model.FAQContent; + +@Converter +public class FAQContentConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(final FAQContent attribute) { + return attribute.getValue(); + } + + @Override + public FAQContent convertToEntityAttribute(final String dbData) { + return FAQContent.from(dbData); + } +} From 037a749ca99ce2c6222fe8516beb71aba98b792e Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 16:36:18 +0900 Subject: [PATCH 113/156] =?UTF-8?q?[FEAT]=20FAQTitle=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../componote/domain/faq/model/FAQTitle.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/java/ject/componote/domain/faq/model/FAQTitle.java diff --git a/src/main/java/ject/componote/domain/faq/model/FAQTitle.java b/src/main/java/ject/componote/domain/faq/model/FAQTitle.java new file mode 100644 index 0000000..1059e17 --- /dev/null +++ b/src/main/java/ject/componote/domain/faq/model/FAQTitle.java @@ -0,0 +1,20 @@ +package ject.componote.domain.faq.model; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@Getter +@EqualsAndHashCode +@ToString +public class FAQTitle { + private final String value; + + private FAQTitle(final String value) { + this.value = value; + } + + public static FAQTitle from(final String value) { + return new FAQTitle(value); + } +} From 75ddd945f4ec3fea94341d6dfeb9918275009fa1 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 16:36:22 +0900 Subject: [PATCH 114/156] =?UTF-8?q?[FEAT]=20FAQTitleConverter=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../faq/model/converter/FAQTitleConverter.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/java/ject/componote/domain/faq/model/converter/FAQTitleConverter.java diff --git a/src/main/java/ject/componote/domain/faq/model/converter/FAQTitleConverter.java b/src/main/java/ject/componote/domain/faq/model/converter/FAQTitleConverter.java new file mode 100644 index 0000000..66f3744 --- /dev/null +++ b/src/main/java/ject/componote/domain/faq/model/converter/FAQTitleConverter.java @@ -0,0 +1,18 @@ +package ject.componote.domain.faq.model.converter; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +import ject.componote.domain.faq.model.FAQTitle; + +@Converter +public class FAQTitleConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(final FAQTitle attribute) { + return attribute.getValue(); + } + + @Override + public FAQTitle convertToEntityAttribute(final String dbData) { + return FAQTitle.from(dbData); + } +} From f4ea7216ec9bb8b9c0aa1c4e372b31366d65e945 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 16:36:26 +0900 Subject: [PATCH 115/156] =?UTF-8?q?[FEAT]=20NoticeContent=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/notice/model/NoticeContent.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/java/ject/componote/domain/notice/model/NoticeContent.java diff --git a/src/main/java/ject/componote/domain/notice/model/NoticeContent.java b/src/main/java/ject/componote/domain/notice/model/NoticeContent.java new file mode 100644 index 0000000..83e774b --- /dev/null +++ b/src/main/java/ject/componote/domain/notice/model/NoticeContent.java @@ -0,0 +1,20 @@ +package ject.componote.domain.notice.model; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@EqualsAndHashCode +@Getter +@ToString +public class NoticeContent { + private final String value; + + private NoticeContent(final String value) { + this.value = value; + } + + public static NoticeContent from(final String value) { + return new NoticeContent(value); + } +} From 0318c83f71b8d43bae87cca165629fe5b8eb711d Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 16:36:30 +0900 Subject: [PATCH 116/156] =?UTF-8?q?[FEAT]=20NoticeContentConverter=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/NoticeContentConverter.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/java/ject/componote/domain/notice/model/converter/NoticeContentConverter.java diff --git a/src/main/java/ject/componote/domain/notice/model/converter/NoticeContentConverter.java b/src/main/java/ject/componote/domain/notice/model/converter/NoticeContentConverter.java new file mode 100644 index 0000000..16c91f3 --- /dev/null +++ b/src/main/java/ject/componote/domain/notice/model/converter/NoticeContentConverter.java @@ -0,0 +1,18 @@ +package ject.componote.domain.notice.model.converter; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +import ject.componote.domain.notice.model.NoticeContent; + +@Converter +public class NoticeContentConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(final NoticeContent attribute) { + return attribute.getValue(); + } + + @Override + public NoticeContent convertToEntityAttribute(final String dbData) { + return NoticeContent.from(dbData); + } +} From b9cf1840134323343e39a6ff6bb1a70994ff66c0 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 16:36:35 +0900 Subject: [PATCH 117/156] =?UTF-8?q?[FEAT]=20NoticeTitle=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/notice/model/NoticeTitle.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main/java/ject/componote/domain/notice/model/NoticeTitle.java diff --git a/src/main/java/ject/componote/domain/notice/model/NoticeTitle.java b/src/main/java/ject/componote/domain/notice/model/NoticeTitle.java new file mode 100644 index 0000000..6112dc8 --- /dev/null +++ b/src/main/java/ject/componote/domain/notice/model/NoticeTitle.java @@ -0,0 +1,20 @@ +package ject.componote.domain.notice.model; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@EqualsAndHashCode +@Getter +@ToString +public class NoticeTitle { + private final String value; + + private NoticeTitle(final String value) { + this.value = value; + } + + public static NoticeTitle from(final String value) { + return new NoticeTitle(value); + } +} From fffa6f5b9a27ea46904ac4762d91af766e832b8c Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 16:36:39 +0900 Subject: [PATCH 118/156] =?UTF-8?q?[FEAT]=20NoticeTitleConverter=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/converter/NoticeTitleConverter.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/java/ject/componote/domain/notice/model/converter/NoticeTitleConverter.java diff --git a/src/main/java/ject/componote/domain/notice/model/converter/NoticeTitleConverter.java b/src/main/java/ject/componote/domain/notice/model/converter/NoticeTitleConverter.java new file mode 100644 index 0000000..abc330d --- /dev/null +++ b/src/main/java/ject/componote/domain/notice/model/converter/NoticeTitleConverter.java @@ -0,0 +1,18 @@ +package ject.componote.domain.notice.model.converter; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +import ject.componote.domain.notice.model.NoticeTitle; + +@Converter +public class NoticeTitleConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(final NoticeTitle attribute) { + return attribute.getValue(); + } + + @Override + public NoticeTitle convertToEntityAttribute(final String dbData) { + return NoticeTitle.from(dbData); + } +} From 48aca128414782833c38ea07550901ef9cc3c058 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 16:37:11 +0900 Subject: [PATCH 119/156] =?UTF-8?q?[REFACTOR]=20Notice,=20FAQ=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../announcement/api/FAQController.java | 4 ---- .../announcement/api/NoticeController.java | 4 ---- .../announcement/error/FAQException.java | 4 ---- .../announcement/error/NoticeException.java | 4 ---- .../announcement/model/Description.java | 20 ------------------- .../domain/announcement/model/Title.java | 20 ------------------- .../model/converter/DescriptionConverter.java | 18 ----------------- .../model/converter/TitleConverter.java | 18 ----------------- .../domain/faq/api/FAQController.java | 4 ++++ .../dao/FAQRepository.java | 4 ++-- .../{announcement => faq}/domain/FAQ.java | 19 +++++++++--------- .../{announcement => faq}/domain/FAQType.java | 2 +- .../domain/faq/error/FAQException.java | 4 ++++ .../domain/notice/api/NoticeController.java | 13 ++++++++++++ .../dao/NoticeRepository.java | 4 ++-- .../domain/Notice.java | 20 +++++++++---------- .../domain/notice/error/NoticeException.java | 4 ++++ 17 files changed, 49 insertions(+), 117 deletions(-) delete mode 100644 src/main/java/ject/componote/domain/announcement/api/FAQController.java delete mode 100644 src/main/java/ject/componote/domain/announcement/api/NoticeController.java delete mode 100644 src/main/java/ject/componote/domain/announcement/error/FAQException.java delete mode 100644 src/main/java/ject/componote/domain/announcement/error/NoticeException.java delete mode 100644 src/main/java/ject/componote/domain/announcement/model/Description.java delete mode 100644 src/main/java/ject/componote/domain/announcement/model/Title.java delete mode 100644 src/main/java/ject/componote/domain/announcement/model/converter/DescriptionConverter.java delete mode 100644 src/main/java/ject/componote/domain/announcement/model/converter/TitleConverter.java create mode 100644 src/main/java/ject/componote/domain/faq/api/FAQController.java rename src/main/java/ject/componote/domain/{announcement => faq}/dao/FAQRepository.java (56%) rename src/main/java/ject/componote/domain/{announcement => faq}/domain/FAQ.java (60%) rename src/main/java/ject/componote/domain/{announcement => faq}/domain/FAQType.java (54%) create mode 100644 src/main/java/ject/componote/domain/faq/error/FAQException.java create mode 100644 src/main/java/ject/componote/domain/notice/api/NoticeController.java rename src/main/java/ject/componote/domain/{announcement => notice}/dao/NoticeRepository.java (56%) rename src/main/java/ject/componote/domain/{announcement => notice}/domain/Notice.java (55%) create mode 100644 src/main/java/ject/componote/domain/notice/error/NoticeException.java diff --git a/src/main/java/ject/componote/domain/announcement/api/FAQController.java b/src/main/java/ject/componote/domain/announcement/api/FAQController.java deleted file mode 100644 index 0d65f26..0000000 --- a/src/main/java/ject/componote/domain/announcement/api/FAQController.java +++ /dev/null @@ -1,4 +0,0 @@ -package ject.componote.domain.announcement.api; - -public class FAQController { -} diff --git a/src/main/java/ject/componote/domain/announcement/api/NoticeController.java b/src/main/java/ject/componote/domain/announcement/api/NoticeController.java deleted file mode 100644 index 520cbb2..0000000 --- a/src/main/java/ject/componote/domain/announcement/api/NoticeController.java +++ /dev/null @@ -1,4 +0,0 @@ -package ject.componote.domain.announcement.api; - -public class NoticeController { -} diff --git a/src/main/java/ject/componote/domain/announcement/error/FAQException.java b/src/main/java/ject/componote/domain/announcement/error/FAQException.java deleted file mode 100644 index 700651e..0000000 --- a/src/main/java/ject/componote/domain/announcement/error/FAQException.java +++ /dev/null @@ -1,4 +0,0 @@ -package ject.componote.domain.announcement.error; - -public class FAQException { -} diff --git a/src/main/java/ject/componote/domain/announcement/error/NoticeException.java b/src/main/java/ject/componote/domain/announcement/error/NoticeException.java deleted file mode 100644 index 7c4924f..0000000 --- a/src/main/java/ject/componote/domain/announcement/error/NoticeException.java +++ /dev/null @@ -1,4 +0,0 @@ -package ject.componote.domain.announcement.error; - -public class NoticeException { -} diff --git a/src/main/java/ject/componote/domain/announcement/model/Description.java b/src/main/java/ject/componote/domain/announcement/model/Description.java deleted file mode 100644 index 2c5bdcc..0000000 --- a/src/main/java/ject/componote/domain/announcement/model/Description.java +++ /dev/null @@ -1,20 +0,0 @@ -package ject.componote.domain.announcement.model; - -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.ToString; - -@EqualsAndHashCode -@Getter -@ToString -public class Description { - private final String value; - - private Description(final String value) { - this.value = value; - } - - public static Description from(final String value) { - return new Description(value); - } -} diff --git a/src/main/java/ject/componote/domain/announcement/model/Title.java b/src/main/java/ject/componote/domain/announcement/model/Title.java deleted file mode 100644 index e9da9c4..0000000 --- a/src/main/java/ject/componote/domain/announcement/model/Title.java +++ /dev/null @@ -1,20 +0,0 @@ -package ject.componote.domain.announcement.model; - -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.ToString; - -@EqualsAndHashCode -@Getter -@ToString -public class Title { - private final String value; - - private Title(final String value) { - this.value = value; - } - - public static Title from(final String value) { - return new Title(value); - } -} diff --git a/src/main/java/ject/componote/domain/announcement/model/converter/DescriptionConverter.java b/src/main/java/ject/componote/domain/announcement/model/converter/DescriptionConverter.java deleted file mode 100644 index 7fcaa55..0000000 --- a/src/main/java/ject/componote/domain/announcement/model/converter/DescriptionConverter.java +++ /dev/null @@ -1,18 +0,0 @@ -package ject.componote.domain.announcement.model.converter; - -import jakarta.persistence.AttributeConverter; -import jakarta.persistence.Converter; -import ject.componote.domain.announcement.model.Description; - -@Converter -public class DescriptionConverter implements AttributeConverter { - @Override - public String convertToDatabaseColumn(final Description attribute) { - return attribute.getValue(); - } - - @Override - public Description convertToEntityAttribute(final String dbData) { - return Description.from(dbData); - } -} diff --git a/src/main/java/ject/componote/domain/announcement/model/converter/TitleConverter.java b/src/main/java/ject/componote/domain/announcement/model/converter/TitleConverter.java deleted file mode 100644 index d6c9920..0000000 --- a/src/main/java/ject/componote/domain/announcement/model/converter/TitleConverter.java +++ /dev/null @@ -1,18 +0,0 @@ -package ject.componote.domain.announcement.model.converter; - -import jakarta.persistence.AttributeConverter; -import jakarta.persistence.Converter; -import ject.componote.domain.announcement.model.Title; - -@Converter -public class TitleConverter implements AttributeConverter { - @Override - public String convertToDatabaseColumn(final Title attribute) { - return attribute.getValue(); - } - - @Override - public Title convertToEntityAttribute(final String dbData) { - return Title.from(dbData); - } -} diff --git a/src/main/java/ject/componote/domain/faq/api/FAQController.java b/src/main/java/ject/componote/domain/faq/api/FAQController.java new file mode 100644 index 0000000..d61fe13 --- /dev/null +++ b/src/main/java/ject/componote/domain/faq/api/FAQController.java @@ -0,0 +1,4 @@ +package ject.componote.domain.faq.api; + +public class FAQController { +} diff --git a/src/main/java/ject/componote/domain/announcement/dao/FAQRepository.java b/src/main/java/ject/componote/domain/faq/dao/FAQRepository.java similarity index 56% rename from src/main/java/ject/componote/domain/announcement/dao/FAQRepository.java rename to src/main/java/ject/componote/domain/faq/dao/FAQRepository.java index b9b3faf..aac53cd 100644 --- a/src/main/java/ject/componote/domain/announcement/dao/FAQRepository.java +++ b/src/main/java/ject/componote/domain/faq/dao/FAQRepository.java @@ -1,6 +1,6 @@ -package ject.componote.domain.announcement.dao; +package ject.componote.domain.faq.dao; -import ject.componote.domain.announcement.domain.FAQ; +import ject.componote.domain.faq.domain.FAQ; import org.springframework.data.jpa.repository.JpaRepository; public interface FAQRepository extends JpaRepository { diff --git a/src/main/java/ject/componote/domain/announcement/domain/FAQ.java b/src/main/java/ject/componote/domain/faq/domain/FAQ.java similarity index 60% rename from src/main/java/ject/componote/domain/announcement/domain/FAQ.java rename to src/main/java/ject/componote/domain/faq/domain/FAQ.java index c26bd17..98175ed 100644 --- a/src/main/java/ject/componote/domain/announcement/domain/FAQ.java +++ b/src/main/java/ject/componote/domain/faq/domain/FAQ.java @@ -1,4 +1,4 @@ -package ject.componote.domain.announcement.domain; +package ject.componote.domain.faq.domain; import jakarta.persistence.Column; import jakarta.persistence.Convert; @@ -8,10 +8,9 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import ject.componote.domain.announcement.model.Description; -import ject.componote.domain.announcement.model.Title; -import ject.componote.domain.announcement.model.converter.DescriptionConverter; -import ject.componote.domain.announcement.model.converter.TitleConverter; +import ject.componote.domain.faq.model.FAQContent; +import ject.componote.domain.faq.model.FAQTitle; +import ject.componote.domain.faq.model.converter.FAQContentConverter; import ject.componote.domain.common.domain.BaseEntity; import lombok.AccessLevel; import lombok.Getter; @@ -31,11 +30,11 @@ public class FAQ extends BaseEntity { @Enumerated(EnumType.STRING) private FAQType type; - @Convert(converter = TitleConverter.class) + @Convert(converter = FAQTitle.class) @Column(name = "title", nullable = false) - private Title title; + private FAQTitle title; - @Convert(converter = DescriptionConverter.class) - @Column(name = "description", nullable = false) - private Description description; + @Convert(converter = FAQContentConverter.class) + @Column(name = "content", nullable = false) + private FAQContent content; } diff --git a/src/main/java/ject/componote/domain/announcement/domain/FAQType.java b/src/main/java/ject/componote/domain/faq/domain/FAQType.java similarity index 54% rename from src/main/java/ject/componote/domain/announcement/domain/FAQType.java rename to src/main/java/ject/componote/domain/faq/domain/FAQType.java index 795ff75..72e108b 100644 --- a/src/main/java/ject/componote/domain/announcement/domain/FAQType.java +++ b/src/main/java/ject/componote/domain/faq/domain/FAQType.java @@ -1,4 +1,4 @@ -package ject.componote.domain.announcement.domain; +package ject.componote.domain.faq.domain; public enum FAQType { COMPONENT, DESIGN, SERVICE, ETC; diff --git a/src/main/java/ject/componote/domain/faq/error/FAQException.java b/src/main/java/ject/componote/domain/faq/error/FAQException.java new file mode 100644 index 0000000..508ba7f --- /dev/null +++ b/src/main/java/ject/componote/domain/faq/error/FAQException.java @@ -0,0 +1,4 @@ +package ject.componote.domain.faq.error; + +public class FAQException { +} diff --git a/src/main/java/ject/componote/domain/notice/api/NoticeController.java b/src/main/java/ject/componote/domain/notice/api/NoticeController.java new file mode 100644 index 0000000..e38192a --- /dev/null +++ b/src/main/java/ject/componote/domain/notice/api/NoticeController.java @@ -0,0 +1,13 @@ +package ject.componote.domain.notice.api; + +import ject.componote.domain.notice.application.NoticeService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class NoticeController { + private final NoticeService noticeService; + + +} diff --git a/src/main/java/ject/componote/domain/announcement/dao/NoticeRepository.java b/src/main/java/ject/componote/domain/notice/dao/NoticeRepository.java similarity index 56% rename from src/main/java/ject/componote/domain/announcement/dao/NoticeRepository.java rename to src/main/java/ject/componote/domain/notice/dao/NoticeRepository.java index 466bea3..878b0a9 100644 --- a/src/main/java/ject/componote/domain/announcement/dao/NoticeRepository.java +++ b/src/main/java/ject/componote/domain/notice/dao/NoticeRepository.java @@ -1,6 +1,6 @@ -package ject.componote.domain.announcement.dao; +package ject.componote.domain.notice.dao; -import ject.componote.domain.announcement.domain.Notice; +import ject.componote.domain.notice.domain.Notice; import org.springframework.data.jpa.repository.JpaRepository; public interface NoticeRepository extends JpaRepository { diff --git a/src/main/java/ject/componote/domain/announcement/domain/Notice.java b/src/main/java/ject/componote/domain/notice/domain/Notice.java similarity index 55% rename from src/main/java/ject/componote/domain/announcement/domain/Notice.java rename to src/main/java/ject/componote/domain/notice/domain/Notice.java index 742b721..9951c43 100644 --- a/src/main/java/ject/componote/domain/announcement/domain/Notice.java +++ b/src/main/java/ject/componote/domain/notice/domain/Notice.java @@ -1,4 +1,4 @@ -package ject.componote.domain.announcement.domain; +package ject.componote.domain.notice.domain; import jakarta.persistence.Column; import jakarta.persistence.Convert; @@ -6,11 +6,11 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import ject.componote.domain.announcement.model.Description; -import ject.componote.domain.announcement.model.Title; -import ject.componote.domain.announcement.model.converter.DescriptionConverter; -import ject.componote.domain.announcement.model.converter.TitleConverter; import ject.componote.domain.common.domain.BaseEntity; +import ject.componote.domain.notice.model.NoticeContent; +import ject.componote.domain.notice.model.NoticeTitle; +import ject.componote.domain.notice.model.converter.NoticeContentConverter; +import ject.componote.domain.notice.model.converter.NoticeTitleConverter; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -25,11 +25,11 @@ public class Notice extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Convert(converter = TitleConverter.class) + @Convert(converter = NoticeTitleConverter.class) @Column(name = "title", nullable = false) - private Title title; + private NoticeTitle title; - @Convert(converter = DescriptionConverter.class) - @Column(name = "description", nullable = false) - private Description description; + @Convert(converter = NoticeContentConverter.class) + @Column(name = "content", nullable = false) + private NoticeContent content; } diff --git a/src/main/java/ject/componote/domain/notice/error/NoticeException.java b/src/main/java/ject/componote/domain/notice/error/NoticeException.java new file mode 100644 index 0000000..3db3cda --- /dev/null +++ b/src/main/java/ject/componote/domain/notice/error/NoticeException.java @@ -0,0 +1,4 @@ +package ject.componote.domain.notice.error; + +public class NoticeException { +} From 4655904d66bc5e6d0cd47c269e9b654019aaed7c Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 17:13:40 +0900 Subject: [PATCH 120/156] =?UTF-8?q?[FEAT]=20NoticeRepository=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ject/componote/domain/notice/dao/NoticeRepository.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/ject/componote/domain/notice/dao/NoticeRepository.java b/src/main/java/ject/componote/domain/notice/dao/NoticeRepository.java index 878b0a9..d798519 100644 --- a/src/main/java/ject/componote/domain/notice/dao/NoticeRepository.java +++ b/src/main/java/ject/componote/domain/notice/dao/NoticeRepository.java @@ -1,7 +1,10 @@ package ject.componote.domain.notice.dao; import ject.componote.domain.notice.domain.Notice; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; public interface NoticeRepository extends JpaRepository { + Page findAllWithPagination(final Pageable pageable); } From 1182e5b05bb43b64cd6c30aa5bec4314ec7c1b0d Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 17:13:47 +0900 Subject: [PATCH 121/156] =?UTF-8?q?[FEAT]=20NoticeService=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/notice/api/NoticeController.java | 13 +++++++++- .../notice/application/NoticeService.java | 24 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 src/main/java/ject/componote/domain/notice/application/NoticeService.java diff --git a/src/main/java/ject/componote/domain/notice/api/NoticeController.java b/src/main/java/ject/componote/domain/notice/api/NoticeController.java index e38192a..17fb90b 100644 --- a/src/main/java/ject/componote/domain/notice/api/NoticeController.java +++ b/src/main/java/ject/componote/domain/notice/api/NoticeController.java @@ -1,13 +1,24 @@ package ject.componote.domain.notice.api; +import ject.componote.domain.common.dto.response.PageResponse; import ject.componote.domain.notice.application.NoticeService; +import ject.componote.domain.notice.dto.response.NoticeResponse; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor +@RequestMapping("/notices") public class NoticeController { private final NoticeService noticeService; - + public ResponseEntity> getNotices(@PageableDefault final Pageable pageable) { + return ResponseEntity.ok( + noticeService.getNotices(pageable) + ); + } } diff --git a/src/main/java/ject/componote/domain/notice/application/NoticeService.java b/src/main/java/ject/componote/domain/notice/application/NoticeService.java new file mode 100644 index 0000000..0247e6a --- /dev/null +++ b/src/main/java/ject/componote/domain/notice/application/NoticeService.java @@ -0,0 +1,24 @@ +package ject.componote.domain.notice.application; + +import ject.componote.domain.common.dto.response.PageResponse; +import ject.componote.domain.notice.dao.NoticeRepository; +import ject.componote.domain.notice.dto.response.NoticeResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class NoticeService { + private final NoticeRepository noticeRepository; + + public PageResponse getNotices(final Pageable pageable) { + final Page page = noticeRepository.findAllWithPagination(pageable) + .map(NoticeResponse::from); + return PageResponse.from(page); + } +} From 1b9ae56c420b6a1a76e1cf365dbd42f79744fb3f Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 17:13:57 +0900 Subject: [PATCH 122/156] =?UTF-8?q?[FEAT]=20NoticeResponse=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/notice/dto/response/NoticeResponse.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/ject/componote/domain/notice/dto/response/NoticeResponse.java diff --git a/src/main/java/ject/componote/domain/notice/dto/response/NoticeResponse.java b/src/main/java/ject/componote/domain/notice/dto/response/NoticeResponse.java new file mode 100644 index 0000000..3582095 --- /dev/null +++ b/src/main/java/ject/componote/domain/notice/dto/response/NoticeResponse.java @@ -0,0 +1,11 @@ +package ject.componote.domain.notice.dto.response; + +import ject.componote.domain.notice.domain.Notice; + +import java.time.LocalDate; + +public record NoticeResponse(String title, String content, LocalDate createdDate) { + public static NoticeResponse from(final Notice notice) { + return new NoticeResponse(notice.getTitle().getValue(), notice.getContent().getValue(), notice.getCreatedAt().toLocalDate()); + } +} From 95d3e96d705c4be3b5e0bf20ee0691824dabd8c9 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 17:28:55 +0900 Subject: [PATCH 123/156] =?UTF-8?q?[REFACTOR]=20=EB=88=84=EB=9D=BD?= =?UTF-8?q?=EB=90=9C=20@GetMapping=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ject/componote/domain/notice/api/NoticeController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/ject/componote/domain/notice/api/NoticeController.java b/src/main/java/ject/componote/domain/notice/api/NoticeController.java index 17fb90b..25d8c3f 100644 --- a/src/main/java/ject/componote/domain/notice/api/NoticeController.java +++ b/src/main/java/ject/componote/domain/notice/api/NoticeController.java @@ -7,6 +7,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -16,6 +17,7 @@ public class NoticeController { private final NoticeService noticeService; + @GetMapping public ResponseEntity> getNotices(@PageableDefault final Pageable pageable) { return ResponseEntity.ok( noticeService.getNotices(pageable) From 60a90e5b29ffca1c75ced56e930cad1731645d0f Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 18:51:39 +0900 Subject: [PATCH 124/156] =?UTF-8?q?[HOTFIX]=20FAQ=20title=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=EC=97=90=20@Convert=20=EC=86=8D=EC=84=B1=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ject/componote/domain/faq/domain/FAQ.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ject/componote/domain/faq/domain/FAQ.java b/src/main/java/ject/componote/domain/faq/domain/FAQ.java index 98175ed..40a4c84 100644 --- a/src/main/java/ject/componote/domain/faq/domain/FAQ.java +++ b/src/main/java/ject/componote/domain/faq/domain/FAQ.java @@ -12,6 +12,7 @@ import ject.componote.domain.faq.model.FAQTitle; import ject.componote.domain.faq.model.converter.FAQContentConverter; import ject.componote.domain.common.domain.BaseEntity; +import ject.componote.domain.faq.model.converter.FAQTitleConverter; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -30,7 +31,7 @@ public class FAQ extends BaseEntity { @Enumerated(EnumType.STRING) private FAQType type; - @Convert(converter = FAQTitle.class) + @Convert(converter = FAQTitleConverter.class) @Column(name = "title", nullable = false) private FAQTitle title; From 7e0f27871c37cc9edad251fff23833a9807d80e2 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 19:08:20 +0900 Subject: [PATCH 125/156] =?UTF-8?q?[REFACTOR]=20NoticeRepository=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EB=84=A4=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20findAll()=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../componote/domain/notice/application/NoticeService.java | 2 +- .../ject/componote/domain/notice/dao/NoticeRepository.java | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/ject/componote/domain/notice/application/NoticeService.java b/src/main/java/ject/componote/domain/notice/application/NoticeService.java index 0247e6a..f423961 100644 --- a/src/main/java/ject/componote/domain/notice/application/NoticeService.java +++ b/src/main/java/ject/componote/domain/notice/application/NoticeService.java @@ -17,7 +17,7 @@ public class NoticeService { private final NoticeRepository noticeRepository; public PageResponse getNotices(final Pageable pageable) { - final Page page = noticeRepository.findAllWithPagination(pageable) + final Page page = noticeRepository.findAll(pageable) .map(NoticeResponse::from); return PageResponse.from(page); } diff --git a/src/main/java/ject/componote/domain/notice/dao/NoticeRepository.java b/src/main/java/ject/componote/domain/notice/dao/NoticeRepository.java index d798519..878b0a9 100644 --- a/src/main/java/ject/componote/domain/notice/dao/NoticeRepository.java +++ b/src/main/java/ject/componote/domain/notice/dao/NoticeRepository.java @@ -1,10 +1,7 @@ package ject.componote.domain.notice.dao; import ject.componote.domain.notice.domain.Notice; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; public interface NoticeRepository extends JpaRepository { - Page findAllWithPagination(final Pageable pageable); } From 1d7a37041ff3cbb923d2c19be4cb78f5d1481a27 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 19:08:38 +0900 Subject: [PATCH 126/156] =?UTF-8?q?[FEAT]=20FAQTypeConstant=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/faq/api/FAQTypeConstant.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/main/java/ject/componote/domain/faq/api/FAQTypeConstant.java diff --git a/src/main/java/ject/componote/domain/faq/api/FAQTypeConstant.java b/src/main/java/ject/componote/domain/faq/api/FAQTypeConstant.java new file mode 100644 index 0000000..71ef473 --- /dev/null +++ b/src/main/java/ject/componote/domain/faq/api/FAQTypeConstant.java @@ -0,0 +1,23 @@ +package ject.componote.domain.faq.api; + +import ject.componote.domain.faq.domain.FAQType; +import lombok.Getter; + +@Getter +public enum FAQTypeConstant { + ALL(null), + COMPONENT(FAQType.COMPONENT), + DESIGN(FAQType.DESIGN), + SERVICE(FAQType.SERVICE), + ETC(FAQType.ETC); + + private final FAQType type; + + FAQTypeConstant(final FAQType type) { + this.type = type; + } + + public boolean isAll() { + return this == ALL; + } +} From 10a45f8a5d2d3bd636da8ffafdf6d66f2e2e8291 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 19:08:48 +0900 Subject: [PATCH 127/156] =?UTF-8?q?[FEAT]=20FAQRequest=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ject/componote/domain/faq/dto/request/FAQRequest.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/ject/componote/domain/faq/dto/request/FAQRequest.java diff --git a/src/main/java/ject/componote/domain/faq/dto/request/FAQRequest.java b/src/main/java/ject/componote/domain/faq/dto/request/FAQRequest.java new file mode 100644 index 0000000..cab1707 --- /dev/null +++ b/src/main/java/ject/componote/domain/faq/dto/request/FAQRequest.java @@ -0,0 +1,7 @@ +package ject.componote.domain.faq.dto.request; + +import jakarta.validation.constraints.NotNull; +import ject.componote.domain.faq.api.FAQTypeConstant; + +public record FAQRequest(@NotNull FAQTypeConstant type) { +} From 914fa9165e75504af6d65d62c487c1db5643c47e Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 19:08:53 +0900 Subject: [PATCH 128/156] =?UTF-8?q?[FEAT]=20FAQResponse=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../componote/domain/faq/dto/response/FAQResponse.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/ject/componote/domain/faq/dto/response/FAQResponse.java diff --git a/src/main/java/ject/componote/domain/faq/dto/response/FAQResponse.java b/src/main/java/ject/componote/domain/faq/dto/response/FAQResponse.java new file mode 100644 index 0000000..60daad2 --- /dev/null +++ b/src/main/java/ject/componote/domain/faq/dto/response/FAQResponse.java @@ -0,0 +1,9 @@ +package ject.componote.domain.faq.dto.response; + +import ject.componote.domain.faq.domain.FAQ; + +public record FAQResponse(String title, String content) { + public static FAQResponse from(final FAQ faq) { + return new FAQResponse(faq.getTitle().getValue(), faq.getContent().getValue()); + } +} From 074c3e1663281a7697e0d7f03fc96fd6d96dbd38 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 19:09:00 +0900 Subject: [PATCH 129/156] =?UTF-8?q?[FEAT]=20FAQRepository=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/ject/componote/domain/faq/dao/FAQRepository.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/ject/componote/domain/faq/dao/FAQRepository.java b/src/main/java/ject/componote/domain/faq/dao/FAQRepository.java index aac53cd..43935b5 100644 --- a/src/main/java/ject/componote/domain/faq/dao/FAQRepository.java +++ b/src/main/java/ject/componote/domain/faq/dao/FAQRepository.java @@ -1,7 +1,11 @@ package ject.componote.domain.faq.dao; import ject.componote.domain.faq.domain.FAQ; +import ject.componote.domain.faq.domain.FAQType; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; public interface FAQRepository extends JpaRepository { + Page findAllByType(final FAQType type, final Pageable pageable); } From 36f16f02870decdcbba397163ab113158f5e9b6e Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 19:09:05 +0900 Subject: [PATCH 130/156] =?UTF-8?q?[FEAT]=20FAQController=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/faq/api/FAQController.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/main/java/ject/componote/domain/faq/api/FAQController.java b/src/main/java/ject/componote/domain/faq/api/FAQController.java index d61fe13..4207867 100644 --- a/src/main/java/ject/componote/domain/faq/api/FAQController.java +++ b/src/main/java/ject/componote/domain/faq/api/FAQController.java @@ -1,4 +1,30 @@ package ject.componote.domain.faq.api; +import jakarta.validation.Valid; +import ject.componote.domain.common.dto.response.PageResponse; +import ject.componote.domain.faq.application.FAQService; +import ject.componote.domain.faq.dto.request.FAQRequest; +import ject.componote.domain.faq.dto.response.FAQResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/faqs") +@RestController +@RequiredArgsConstructor public class FAQController { + private final FAQService faqService; + + @GetMapping + public ResponseEntity> getFAQs(@ModelAttribute @Valid final FAQRequest faqRequest, + @PageableDefault final Pageable pageable) { + return ResponseEntity.ok( + faqService.getFAQs(faqRequest, pageable) + ); + } } From ac3e3492100a5215d6e60896d80aa7bf61a108c6 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 19:09:11 +0900 Subject: [PATCH 131/156] =?UTF-8?q?[FEAT]=20FAQService=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/faq/application/FAQService.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/main/java/ject/componote/domain/faq/application/FAQService.java diff --git a/src/main/java/ject/componote/domain/faq/application/FAQService.java b/src/main/java/ject/componote/domain/faq/application/FAQService.java new file mode 100644 index 0000000..18a8408 --- /dev/null +++ b/src/main/java/ject/componote/domain/faq/application/FAQService.java @@ -0,0 +1,37 @@ +package ject.componote.domain.faq.application; + +import ject.componote.domain.common.dto.response.PageResponse; +import ject.componote.domain.faq.api.FAQTypeConstant; +import ject.componote.domain.faq.dao.FAQRepository; +import ject.componote.domain.faq.domain.FAQ; +import ject.componote.domain.faq.domain.FAQType; +import ject.componote.domain.faq.dto.request.FAQRequest; +import ject.componote.domain.faq.dto.response.FAQResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class FAQService { + private final FAQRepository faqRepository; + + public PageResponse getFAQs(final FAQRequest request, final Pageable pageable) { + final FAQTypeConstant typeConstant = request.type(); + final Page page = findAllFAQsWithConditions(typeConstant, pageable) + .map(FAQResponse::from); + return PageResponse.from(page); + } + + private Page findAllFAQsWithConditions(final FAQTypeConstant typeConstant, final Pageable pageable) { + if (typeConstant.isAll()) { + return faqRepository.findAll(pageable); + } + + final FAQType type = typeConstant.getType(); + return faqRepository.findAllByType(type, pageable); + } +} From 6c289f4a19452741b236bdba76b3819c67f1156f Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 19:12:16 +0900 Subject: [PATCH 132/156] =?UTF-8?q?[REFACTOR]=20BaseImage=20URL=20Prefix?= =?UTF-8?q?=20=EA=B2=BD=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ject/componote/domain/common/model/BaseImage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ject/componote/domain/common/model/BaseImage.java b/src/main/java/ject/componote/domain/common/model/BaseImage.java index 543ae46..2714fd2 100644 --- a/src/main/java/ject/componote/domain/common/model/BaseImage.java +++ b/src/main/java/ject/componote/domain/common/model/BaseImage.java @@ -8,7 +8,7 @@ @EqualsAndHashCode @ToString public class BaseImage { - private static final String IMAGE_URL_PREFIX = "https://componote.s3.ap-northeast-2.amazonaws.com/permanent"; + private static final String IMAGE_URL_PREFIX = "https://componote.s3.ap-northeast-2.amazonaws.com/permanent/"; private static final BaseImage EMPTY_INSTANCE = new BaseImage(null); private final String objectKey; From 351102551c09f51ddbc45d940cdca5b6fcd3262b Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 19:43:18 +0900 Subject: [PATCH 133/156] =?UTF-8?q?[FEAT]=20AbstractImage=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/common/model/AbstractImage.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/main/java/ject/componote/domain/common/model/AbstractImage.java diff --git a/src/main/java/ject/componote/domain/common/model/AbstractImage.java b/src/main/java/ject/componote/domain/common/model/AbstractImage.java new file mode 100644 index 0000000..3a2777a --- /dev/null +++ b/src/main/java/ject/componote/domain/common/model/AbstractImage.java @@ -0,0 +1,30 @@ +package ject.componote.domain.common.model; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@EqualsAndHashCode +@Getter +@ToString +public abstract class AbstractImage { + private static final String IMAGE_URL_PREFIX = "https://componote.s3.ap-northeast-2.amazonaws.com/permanent/"; + + private final String objectKey; + + protected AbstractImage(final String objectKey) { + this.objectKey = objectKey; + } + + public boolean isEmpty() { + return objectKey == null || objectKey.isEmpty(); + } + + public String toUrl() { + if (isEmpty()) { + return null; + } + + return IMAGE_URL_PREFIX + objectKey; + } +} From bcbe3842ee9741d73bc685e4c0bdb5dc6fc097ae Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 19:44:03 +0900 Subject: [PATCH 134/156] =?UTF-8?q?[REFACTOR]=20ProfileImage=20=EA=B0=80?= =?UTF-8?q?=20AbstractImage=20=EB=A5=BC=20=EC=83=81=EC=86=8D=20=EB=B0=9B?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/application/AuthService.java | 2 +- .../domain/auth/application/MemberService.java | 2 +- .../dto/find/response/MemberSummaryResponse.java | 2 +- .../domain/auth/model/ProfileImage.java | 16 ++++++---------- .../model/converter/ProfileImageConverter.java | 3 +-- .../domain/auth/application/AuthServiceTest.java | 6 +++--- .../auth/application/MemberServiceTest.java | 2 +- 7 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/main/java/ject/componote/domain/auth/application/AuthService.java b/src/main/java/ject/componote/domain/auth/application/AuthService.java index 9ae4221..cca2b0b 100644 --- a/src/main/java/ject/componote/domain/auth/application/AuthService.java +++ b/src/main/java/ject/componote/domain/auth/application/AuthService.java @@ -39,7 +39,7 @@ public MemberSignupResponse signup(final MemberSignupRequest request) { } final Member member = memberRepository.save(request.toMember()); - fileService.moveImage(member.getProfileImage().getImage()); // 맘에 안듬... + fileService.moveImage(member.getProfileImage()); return MemberSignupResponse.from(member); } diff --git a/src/main/java/ject/componote/domain/auth/application/MemberService.java b/src/main/java/ject/componote/domain/auth/application/MemberService.java index 7f9d1a9..ae2cf49 100644 --- a/src/main/java/ject/componote/domain/auth/application/MemberService.java +++ b/src/main/java/ject/componote/domain/auth/application/MemberService.java @@ -37,7 +37,7 @@ public void updateProfileImage(final AuthPrincipal authPrincipal, final MemberPr return; } - fileService.moveImage(profileImage.getImage()); + fileService.moveImage(profileImage); member.updateProfileImage(profileImage); memberRepository.save(member); } diff --git a/src/main/java/ject/componote/domain/auth/dto/find/response/MemberSummaryResponse.java b/src/main/java/ject/componote/domain/auth/dto/find/response/MemberSummaryResponse.java index a9d8d77..867205a 100644 --- a/src/main/java/ject/componote/domain/auth/dto/find/response/MemberSummaryResponse.java +++ b/src/main/java/ject/componote/domain/auth/dto/find/response/MemberSummaryResponse.java @@ -6,7 +6,7 @@ public record MemberSummaryResponse(String nickname, String profileImageUrl) { public static MemberSummaryResponse from(MemberSummaryDao memberSummaryDao) { return new MemberSummaryResponse( memberSummaryDao.nickname().getValue(), - memberSummaryDao.profileImage().getImage().toUrl() + memberSummaryDao.profileImage().toUrl() ); } } diff --git a/src/main/java/ject/componote/domain/auth/model/ProfileImage.java b/src/main/java/ject/componote/domain/auth/model/ProfileImage.java index 0c7437b..11b588e 100644 --- a/src/main/java/ject/componote/domain/auth/model/ProfileImage.java +++ b/src/main/java/ject/componote/domain/auth/model/ProfileImage.java @@ -1,26 +1,22 @@ package ject.componote.domain.auth.model; import ject.componote.domain.auth.error.InvalidProfileImageExtensionException; -import ject.componote.domain.common.model.BaseImage; +import ject.componote.domain.common.model.AbstractImage; import lombok.EqualsAndHashCode; -import lombok.Getter; import lombok.ToString; import org.springframework.util.StringUtils; import java.util.Arrays; import java.util.List; -@EqualsAndHashCode -@Getter +@EqualsAndHashCode(callSuper = true) @ToString -public class ProfileImage { +public class ProfileImage extends AbstractImage { private static final List ALLOWED_IMAGE_EXTENSIONS = Arrays.asList("jpg", "jpeg", "png"); private static final ProfileImage DEFAULT_PROFILE_IMAGE = ProfileImage.from("/profiles/default-profile-image.png"); - private final BaseImage image; - - private ProfileImage(final BaseImage image) { - this.image = image; + public ProfileImage(final String objectKey) { + super(objectKey); } public static ProfileImage from(final String objectKey) { @@ -33,6 +29,6 @@ public static ProfileImage from(final String objectKey) { throw new InvalidProfileImageExtensionException(extension); } - return new ProfileImage(BaseImage.from(objectKey)); + return new ProfileImage(objectKey); } } diff --git a/src/main/java/ject/componote/domain/auth/model/converter/ProfileImageConverter.java b/src/main/java/ject/componote/domain/auth/model/converter/ProfileImageConverter.java index 0e9c866..83264e4 100644 --- a/src/main/java/ject/componote/domain/auth/model/converter/ProfileImageConverter.java +++ b/src/main/java/ject/componote/domain/auth/model/converter/ProfileImageConverter.java @@ -8,8 +8,7 @@ public class ProfileImageConverter implements AttributeConverter { @Override public String convertToDatabaseColumn(final ProfileImage attribute) { - return attribute.getImage() - .getObjectKey(); + return attribute.getObjectKey(); } @Override diff --git a/src/test/java/ject/componote/domain/auth/application/AuthServiceTest.java b/src/test/java/ject/componote/domain/auth/application/AuthServiceTest.java index b983504..69989ae 100644 --- a/src/test/java/ject/componote/domain/auth/application/AuthServiceTest.java +++ b/src/test/java/ject/componote/domain/auth/application/AuthServiceTest.java @@ -52,7 +52,7 @@ class AuthServiceTest { Long socialAccountId = 1L; Member member = KIM.생성(socialAccountId); ProfileImage profileImage = member.getProfileImage(); - String profileImageObjectKey = profileImage.getImage().getObjectKey(); + String profileImageObjectKey = profileImage.getObjectKey(); @DisplayName("회원 가입") @Test @@ -74,7 +74,7 @@ public void signup() throws Exception { doReturn(member).when(memberRepository) .save(any()); doNothing().when(fileService) - .moveImage(profileImage.getImage()); + .moveImage(profileImage); final MemberSignupResponse actual = authService.signup(request); // then @@ -142,7 +142,7 @@ public void signupWhenMoveFail() throws Exception { doReturn(member).when(memberRepository) .save(any()); doThrow(FileClientException.class).when(fileService) - .moveImage(profileImage.getImage()); + .moveImage(profileImage); // then assertThatThrownBy(() -> authService.signup(request)) diff --git a/src/test/java/ject/componote/domain/auth/application/MemberServiceTest.java b/src/test/java/ject/componote/domain/auth/application/MemberServiceTest.java index e51e02a..593a093 100644 --- a/src/test/java/ject/componote/domain/auth/application/MemberServiceTest.java +++ b/src/test/java/ject/componote/domain/auth/application/MemberServiceTest.java @@ -77,7 +77,7 @@ public void updateProfileImage() throws Exception { doReturn(Optional.of(member)).when(memberRepository) .findById(memberId); doNothing().when(fileService) - .moveImage(newProfileImage.getImage()); + .moveImage(newProfileImage); memberService.updateProfileImage(authPrincipal, request); // then From 3a7791e27904c33fe7737749bc04a85e22ee8124 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 19:44:53 +0900 Subject: [PATCH 135/156] =?UTF-8?q?[REFACTOR]=20CommentImage=20=EA=B0=80?= =?UTF-8?q?=20AbstractImage=20=EB=A5=BC=20=EC=83=81=EC=86=8D=20=EB=B0=9B?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/application/CommentService.java | 4 ++-- .../CommentFindByComponentResponse.java | 4 ++-- .../domain/comment/model/CommentImage.java | 19 ++++++++----------- .../converter/CommentImageConverter.java | 3 +-- .../CommentCreationStrategyTest.java | 2 +- .../application/CommentServiceTest.java | 8 ++++---- .../comment/model/CommentImageTest.java | 3 +-- 7 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/main/java/ject/componote/domain/comment/application/CommentService.java b/src/main/java/ject/componote/domain/comment/application/CommentService.java index a1df865..a6418e4 100644 --- a/src/main/java/ject/componote/domain/comment/application/CommentService.java +++ b/src/main/java/ject/componote/domain/comment/application/CommentService.java @@ -46,7 +46,7 @@ public CommentCreateResponse create(final AuthPrincipal authPrincipal, final Com final Comment comment = commentRepository.save( CommentCreationStrategy.createBy(request, authPrincipal.id()) ); - fileService.moveImage(comment.getImage().getImage()); + fileService.moveImage(comment.getImage()); return CommentCreateResponse.from(comment); } @@ -72,7 +72,7 @@ public void update(final AuthPrincipal authPrincipal, final Long commentId, fina final CommentImage image = CommentImage.from(commentUpdateRequest.imageObjectKey()); if (!comment.equalsImage(image)) { - fileService.moveImage(image.getImage()); + fileService.moveImage(image); } final CommentContent content = CommentContent.from(commentUpdateRequest.content()); // 가비지가 발생하지 않을까? diff --git a/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByComponentResponse.java b/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByComponentResponse.java index 249b2cd..49563b0 100644 --- a/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByComponentResponse.java +++ b/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByComponentResponse.java @@ -19,7 +19,7 @@ public static CommentFindByComponentResponse from(final CommentFindByComponentDa createProfileResponse(dto), dto.commentId(), dto.parentId(), - dto.commentImage().getImage().toUrl(), + dto.commentImage().toUrl(), dto.content().getValue(), dto.createdAt(), dto.likeCount().getValue(), @@ -31,7 +31,7 @@ private static CommentProfileResponse createProfileResponse(final CommentFindByC return new CommentProfileResponse( dto.memberId(), dto.nickname().getValue(), - dto.profileImage().getImage().toUrl(), + dto.profileImage().toUrl(), dto.job().name() ); } diff --git a/src/main/java/ject/componote/domain/comment/model/CommentImage.java b/src/main/java/ject/componote/domain/comment/model/CommentImage.java index 327aa36..5fb226b 100644 --- a/src/main/java/ject/componote/domain/comment/model/CommentImage.java +++ b/src/main/java/ject/componote/domain/comment/model/CommentImage.java @@ -1,30 +1,27 @@ package ject.componote.domain.comment.model; import ject.componote.domain.comment.error.InvalidCommentImageExtensionException; -import ject.componote.domain.common.model.BaseImage; +import ject.componote.domain.common.model.AbstractImage; import lombok.EqualsAndHashCode; -import lombok.Getter; import lombok.ToString; import org.springframework.util.StringUtils; import java.util.Arrays; import java.util.List; -@EqualsAndHashCode -@Getter +@EqualsAndHashCode(callSuper = true) @ToString -public class CommentImage { +public class CommentImage extends AbstractImage { private static final List ALLOWED_IMAGE_EXTENSIONS = Arrays.asList("jpg", "jpeg", "png", "gif"); + private static final CommentImage EMPTY_INSTANCE = new CommentImage(null); - private final BaseImage image; - - private CommentImage(final BaseImage image) { - this.image = image; + public CommentImage(final String objectKey) { + super(objectKey); } public static CommentImage from(final String objectKey) { if (objectKey == null || objectKey.isEmpty()) { - return new CommentImage(BaseImage.getEmptyInstance()); + return EMPTY_INSTANCE; } final String extension = StringUtils.getFilenameExtension(objectKey); @@ -32,6 +29,6 @@ public static CommentImage from(final String objectKey) { throw new InvalidCommentImageExtensionException(extension); } - return new CommentImage(BaseImage.from(objectKey)); + return new CommentImage(objectKey); } } diff --git a/src/main/java/ject/componote/domain/comment/model/converter/CommentImageConverter.java b/src/main/java/ject/componote/domain/comment/model/converter/CommentImageConverter.java index c2189ed..adabc6d 100644 --- a/src/main/java/ject/componote/domain/comment/model/converter/CommentImageConverter.java +++ b/src/main/java/ject/componote/domain/comment/model/converter/CommentImageConverter.java @@ -8,8 +8,7 @@ public class CommentImageConverter implements AttributeConverter { @Override public String convertToDatabaseColumn(final CommentImage attribute) { - return attribute.getImage() - .getObjectKey(); + return attribute.getObjectKey(); } @Override diff --git a/src/test/java/ject/componote/domain/comment/application/CommentCreationStrategyTest.java b/src/test/java/ject/componote/domain/comment/application/CommentCreationStrategyTest.java index 0289dc8..b5f8f03 100644 --- a/src/test/java/ject/componote/domain/comment/application/CommentCreationStrategyTest.java +++ b/src/test/java/ject/componote/domain/comment/application/CommentCreationStrategyTest.java @@ -24,7 +24,7 @@ public void createBy(final CommentFixture fixture) throws Exception { // then assertThat(createdComment.getContent().getValue()).isEqualTo(createRequest.content()); - assertThat(createdComment.getImage().getImage().getObjectKey()).isEqualTo(createRequest.imageObjectKey()); + assertThat(createdComment.getImage().getObjectKey()).isEqualTo(createRequest.imageObjectKey()); assertThat(createdComment.getMemberId()).isEqualTo(memberId); assertThat(createdComment.getComponentId()).isEqualTo(createRequest.componentId()); assertThat(createdComment.getParentId()).isEqualTo(createRequest.parentId()); diff --git a/src/test/java/ject/componote/domain/comment/application/CommentServiceTest.java b/src/test/java/ject/componote/domain/comment/application/CommentServiceTest.java index bea9bf3..4882f20 100644 --- a/src/test/java/ject/componote/domain/comment/application/CommentServiceTest.java +++ b/src/test/java/ject/componote/domain/comment/application/CommentServiceTest.java @@ -93,7 +93,7 @@ public void create(final CommentFixture fixture) throws Exception { doReturn(comment).when(commentRepository) .save(any()); doNothing().when(fileService) - .moveImage(comment.getImage().getImage()); + .moveImage(comment.getImage()); final CommentCreateResponse actual = commentService.create(authPrincipal, createRequest); // then @@ -205,7 +205,7 @@ public void updateImage(final CommentFixture fixture) throws Exception { assertDoesNotThrow( () -> commentService.update(authPrincipal, commentId, request) ); - assertThat(comment.getImage().getImage().getObjectKey()).isEqualTo(newObjectKey); + assertThat(comment.getImage().getObjectKey()).isEqualTo(newObjectKey); } @ParameterizedTest @@ -216,7 +216,7 @@ public void updateContent(final CommentFixture fixture) throws Exception { final Long memberId = authPrincipal.id(); final Comment comment = fixture.생성(); final Long commentId = comment.getId(); - final String objectKey = comment.getImage().getImage().getObjectKey(); + final String objectKey = comment.getImage().getObjectKey(); final String newContent = "수정된 내용"; final CommentUpdateRequest request = new CommentUpdateRequest(objectKey, newContent); @@ -251,7 +251,7 @@ public void updateAll(final CommentFixture fixture) throws Exception { assertDoesNotThrow( () -> commentService.update(authPrincipal, commentId, request) ); - assertThat(comment.getImage().getImage().getObjectKey()).isEqualTo(newObjectKey); + assertThat(comment.getImage().getObjectKey()).isEqualTo(newObjectKey); assertThat(comment.getContent().getValue()).isEqualTo(newContent); } diff --git a/src/test/java/ject/componote/domain/comment/model/CommentImageTest.java b/src/test/java/ject/componote/domain/comment/model/CommentImageTest.java index 24b46ac..a46bd78 100644 --- a/src/test/java/ject/componote/domain/comment/model/CommentImageTest.java +++ b/src/test/java/ject/componote/domain/comment/model/CommentImageTest.java @@ -1,7 +1,6 @@ package ject.componote.domain.comment.model; import ject.componote.domain.comment.error.InvalidCommentImageExtensionException; -import ject.componote.domain.common.model.BaseImage; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -17,7 +16,7 @@ public void isNullOrEmpty() throws Exception { final String objectKey = ""; final CommentImage commentImage = CommentImage.from(objectKey); assertThat(commentImage).isNotNull(); - assertThat(commentImage.getImage()).isEqualTo(BaseImage.getEmptyInstance()); + assertThat(commentImage.getObjectKey()).isNull(); } @ParameterizedTest From 4ab66df9dbf0bcdb9eae398b2155cace9b2c160b Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 19:45:06 +0900 Subject: [PATCH 136/156] =?UTF-8?q?[REFACTOR]=20ComponentImage=20=EA=B0=80?= =?UTF-8?q?=20AbstractImage=20=EB=A5=BC=20=EC=83=81=EC=86=8D=20=EB=B0=9B?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/component/model/ComponentImage.java | 18 ++++++++---------- .../converter/ComponentImageConverter.java | 2 +- .../domain/component/util/ComponentMapper.java | 2 +- .../application/ComponentServiceTest.java | 2 +- .../component/util/ComponentMapperTest.java | 2 +- 5 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/main/java/ject/componote/domain/component/model/ComponentImage.java b/src/main/java/ject/componote/domain/component/model/ComponentImage.java index 0de7ac4..69faab2 100644 --- a/src/main/java/ject/componote/domain/component/model/ComponentImage.java +++ b/src/main/java/ject/componote/domain/component/model/ComponentImage.java @@ -1,25 +1,23 @@ package ject.componote.domain.component.model; -import ject.componote.domain.common.model.BaseImage; +import ject.componote.domain.common.model.AbstractImage; import ject.componote.domain.component.error.InvalidComponentImageExtensionException; import ject.componote.domain.component.error.NotFoundComponentImageException; import lombok.EqualsAndHashCode; -import lombok.Getter; +import lombok.ToString; import org.springframework.util.StringUtils; import java.util.Arrays; import java.util.List; -@EqualsAndHashCode -@Getter -public class ComponentImage { +@EqualsAndHashCode(callSuper = true) +@ToString +public class ComponentImage extends AbstractImage { // Arrays.asList 로 만든 List: contains(null) 시 NPE 발생하지 않고 false 리턴 private static final List ALLOWED_IMAGE_EXTENSIONS = Arrays.asList("png"); - private final BaseImage image; - - private ComponentImage(final BaseImage image) { - this.image = image; + public ComponentImage(final String objectKey) { + super(objectKey); } public static ComponentImage from(final String objectKey) { @@ -32,6 +30,6 @@ public static ComponentImage from(final String objectKey) { throw new InvalidComponentImageExtensionException(extension); } - return new ComponentImage(BaseImage.from(objectKey)); + return new ComponentImage(objectKey); } } diff --git a/src/main/java/ject/componote/domain/component/model/converter/ComponentImageConverter.java b/src/main/java/ject/componote/domain/component/model/converter/ComponentImageConverter.java index e4a3118..217c2ec 100644 --- a/src/main/java/ject/componote/domain/component/model/converter/ComponentImageConverter.java +++ b/src/main/java/ject/componote/domain/component/model/converter/ComponentImageConverter.java @@ -8,7 +8,7 @@ public class ComponentImageConverter implements AttributeConverter { @Override public String convertToDatabaseColumn(final ComponentImage attribute) { - return attribute.getImage().getObjectKey(); + return attribute.getObjectKey(); } @Override diff --git a/src/main/java/ject/componote/domain/component/util/ComponentMapper.java b/src/main/java/ject/componote/domain/component/util/ComponentMapper.java index 15c2712..1448d4f 100644 --- a/src/main/java/ject/componote/domain/component/util/ComponentMapper.java +++ b/src/main/java/ject/componote/domain/component/util/ComponentMapper.java @@ -23,7 +23,7 @@ public ComponentDetailResponse mapFrom(final Component component, final Boolean component.getCommentCount().getValue(), component.getBookmarkCount().getValue(), component.getDesignReferenceCount().getValue(), - summary.getThumbnail().getImage().toUrl(), + summary.getThumbnail().toUrl(), parseBlocks(component), isBookmarked ); diff --git a/src/test/java/ject/componote/domain/component/application/ComponentServiceTest.java b/src/test/java/ject/componote/domain/component/application/ComponentServiceTest.java index 1dcbe58..b78f535 100644 --- a/src/test/java/ject/componote/domain/component/application/ComponentServiceTest.java +++ b/src/test/java/ject/componote/domain/component/application/ComponentServiceTest.java @@ -90,7 +90,7 @@ public void getComponentDetail(final DetailInput input) throws Exception { component.getCommentCount().getValue(), component.getBookmarkCount().getValue(), component.getDesignReferenceCount().getValue(), - component.getSummary().getThumbnail().getImage().toUrl(), + component.getSummary().getThumbnail().toUrl(), Collections.emptyMap(), input.isBookmarked ); diff --git a/src/test/java/ject/componote/domain/component/util/ComponentMapperTest.java b/src/test/java/ject/componote/domain/component/util/ComponentMapperTest.java index 4b3c6a8..8b8c279 100644 --- a/src/test/java/ject/componote/domain/component/util/ComponentMapperTest.java +++ b/src/test/java/ject/componote/domain/component/util/ComponentMapperTest.java @@ -34,6 +34,6 @@ public void mapFromWithBookmark(final boolean isBookmarked) throws Exception { assertThat(response.commentCount()).isEqualTo(component.getCommentCount().getValue()); assertThat(response.title()).isEqualTo(component.getSummary().getTitle()); assertThat(response.description()).isEqualTo(component.getSummary().getDescription()); - assertThat(response.thumbnailUrl()).isEqualTo(component.getSummary().getThumbnail().getImage().toUrl()); + assertThat(response.thumbnailUrl()).isEqualTo(component.getSummary().getThumbnail().toUrl()); } } \ No newline at end of file From 38b6cde25594c0582cb41167ff667d8818a0dcb6 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 19:45:24 +0900 Subject: [PATCH 137/156] =?UTF-8?q?[REFACTOR]=20ComponentThumbnail=20?= =?UTF-8?q?=EC=9D=B4=20AbstractImage=20=EB=A5=BC=20=EC=83=81=EC=86=8D=20?= =?UTF-8?q?=EB=B0=9B=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/block/detail/ImageBlock.java | 2 +- .../response/ComponentSummaryResponse.java | 2 +- .../component/model/ComponentThumbnail.java | 18 ++++++++---------- .../converter/ComponentThumbnailConverter.java | 2 +- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/main/java/ject/componote/domain/component/domain/block/detail/ImageBlock.java b/src/main/java/ject/componote/domain/component/domain/block/detail/ImageBlock.java index f367ff6..b393263 100644 --- a/src/main/java/ject/componote/domain/component/domain/block/detail/ImageBlock.java +++ b/src/main/java/ject/componote/domain/component/domain/block/detail/ImageBlock.java @@ -32,6 +32,6 @@ public static ImageBlock of(final BlockType type, final ComponentImage image, fi @Override public String getValue() { - return image.getImage().toUrl(); + return image.toUrl(); } } \ No newline at end of file diff --git a/src/main/java/ject/componote/domain/component/dto/find/response/ComponentSummaryResponse.java b/src/main/java/ject/componote/domain/component/dto/find/response/ComponentSummaryResponse.java index 5d21bbf..18d6b47 100644 --- a/src/main/java/ject/componote/domain/component/dto/find/response/ComponentSummaryResponse.java +++ b/src/main/java/ject/componote/domain/component/dto/find/response/ComponentSummaryResponse.java @@ -17,7 +17,7 @@ public static ComponentSummaryResponse from(final ComponentSummaryDao dao) { final ComponentSummary summary = dao.summary(); return new ComponentSummaryResponse( dao.id(), - summary.getThumbnail().getImage().toUrl(), + summary.getThumbnail().toUrl(), summary.getTitle(), summary.getDescription(), dao.type().name(), diff --git a/src/main/java/ject/componote/domain/component/model/ComponentThumbnail.java b/src/main/java/ject/componote/domain/component/model/ComponentThumbnail.java index 007a1fd..a8f6a5e 100644 --- a/src/main/java/ject/componote/domain/component/model/ComponentThumbnail.java +++ b/src/main/java/ject/componote/domain/component/model/ComponentThumbnail.java @@ -1,24 +1,22 @@ package ject.componote.domain.component.model; -import ject.componote.domain.common.model.BaseImage; +import ject.componote.domain.common.model.AbstractImage; import ject.componote.domain.component.error.InvalidComponentThumbnailExtensionException; import ject.componote.domain.component.error.NotFoundComponentThumbnailException; import lombok.EqualsAndHashCode; -import lombok.Getter; +import lombok.ToString; import org.springframework.util.StringUtils; import java.util.Arrays; import java.util.List; -@EqualsAndHashCode -@Getter -public class ComponentThumbnail { +@EqualsAndHashCode(callSuper = true) +@ToString +public class ComponentThumbnail extends AbstractImage { private static final List ALLOWED_IMAGE_EXTENSIONS = Arrays.asList("jpg", "jpeg", "png"); - private final BaseImage image; - - private ComponentThumbnail(final BaseImage image) { - this.image = image; + public ComponentThumbnail(final String objectKey) { + super(objectKey); } public static ComponentThumbnail from(final String objectKey) { @@ -31,6 +29,6 @@ public static ComponentThumbnail from(final String objectKey) { throw new InvalidComponentThumbnailExtensionException(extension); } - return new ComponentThumbnail(BaseImage.from(objectKey)); + return new ComponentThumbnail(objectKey); } } diff --git a/src/main/java/ject/componote/domain/component/model/converter/ComponentThumbnailConverter.java b/src/main/java/ject/componote/domain/component/model/converter/ComponentThumbnailConverter.java index 603b8da..799c45c 100644 --- a/src/main/java/ject/componote/domain/component/model/converter/ComponentThumbnailConverter.java +++ b/src/main/java/ject/componote/domain/component/model/converter/ComponentThumbnailConverter.java @@ -8,7 +8,7 @@ public class ComponentThumbnailConverter implements AttributeConverter { @Override public String convertToDatabaseColumn(final ComponentThumbnail attribute) { - return attribute.getImage().getObjectKey(); + return attribute.getObjectKey(); } @Override From d814d9b3fd5655ecb2f3a7852ed559ffad6e8d6e Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 19:45:43 +0900 Subject: [PATCH 138/156] =?UTF-8?q?[REFACTOR]=20FileService.moveImage()=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=EB=A5=BC=20AbstractImage?= =?UTF-8?q?=20=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ject/componote/infra/file/application/FileService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ject/componote/infra/file/application/FileService.java b/src/main/java/ject/componote/infra/file/application/FileService.java index 396a38b..4f734fd 100644 --- a/src/main/java/ject/componote/infra/file/application/FileService.java +++ b/src/main/java/ject/componote/infra/file/application/FileService.java @@ -1,6 +1,6 @@ package ject.componote.infra.file.application; -import ject.componote.domain.common.model.BaseImage; +import ject.componote.domain.common.model.AbstractImage; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -12,7 +12,7 @@ public class FileService { private final FileClient fileClient; - public void moveImage(final BaseImage image) { + public void moveImage(final AbstractImage image) { if (image.isEmpty()) { log.warn("No image to move"); return; From c6eeba0020defc4530560fb28ac8b8e3e1e56212 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 19:48:56 +0900 Subject: [PATCH 139/156] =?UTF-8?q?[FEAT]=20FileServerErrorResponse=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=ED=8C=8C=EC=9D=BC=20=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=9D=91=EB=8B=B5=20=ED=98=95=EC=8B=9D?= =?UTF-8?q?=EC=9D=84=20=EB=8B=B4=EB=8A=94=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../file/error/FileServerErrorResponse.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/main/java/ject/componote/infra/file/error/FileServerErrorResponse.java diff --git a/src/main/java/ject/componote/infra/file/error/FileServerErrorResponse.java b/src/main/java/ject/componote/infra/file/error/FileServerErrorResponse.java new file mode 100644 index 0000000..05240b8 --- /dev/null +++ b/src/main/java/ject/componote/infra/file/error/FileServerErrorResponse.java @@ -0,0 +1,24 @@ +package ject.componote.infra.file.error; + +import ject.componote.global.error.ErrorResponse; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.http.HttpStatus; + +@AllArgsConstructor(access = AccessLevel.PUBLIC) +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class FileServerErrorResponse { + private int status; + private String message; + + public static ErrorResponse of(final HttpStatus status, final String message) { + return new ErrorResponse(status.value(), message); + } + + public static ErrorResponse of(final HttpStatus status, final Exception exception) { + return new ErrorResponse(status.value(), exception.getMessage()); + } +} From d872f645e7655fbb1a51f6d351b42dcff50a3086 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 19:49:12 +0900 Subject: [PATCH 140/156] =?UTF-8?q?[REFACTOR]=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=20=EC=97=90=EB=9F=AC=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=EC=9D=84=20FileServerErrorResponse=20=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=A7=A4=ED=95=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ject/componote/infra/file/application/FileClient.java | 8 ++++---- .../componote/infra/file/error/FileClientException.java | 5 ++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/ject/componote/infra/file/application/FileClient.java b/src/main/java/ject/componote/infra/file/application/FileClient.java index 0595d2c..3b0de4b 100644 --- a/src/main/java/ject/componote/infra/file/application/FileClient.java +++ b/src/main/java/ject/componote/infra/file/application/FileClient.java @@ -1,8 +1,8 @@ package ject.componote.infra.file.application; -import ject.componote.global.error.ErrorResponse; import ject.componote.infra.file.dto.move.request.MoveRequest; import ject.componote.infra.file.error.FileClientException; +import ject.componote.infra.file.error.FileServerErrorResponse; import ject.componote.infra.util.TimeoutDecorator; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -59,13 +59,13 @@ private Mono moveImage(final String objectKey) { } private Mono handle5xxError(final ClientResponse clientResponse) { - return clientResponse.bodyToMono(ErrorResponse.class) - .map(ErrorResponse::getMessage) + return clientResponse.bodyToMono(FileServerErrorResponse.class) + .map(FileServerErrorResponse::getMessage) .map(IllegalStateException::new); } private Mono handle4xxError(final ClientResponse clientResponse) { - return clientResponse.bodyToMono(ErrorResponse.class) + return clientResponse.bodyToMono(FileServerErrorResponse.class) .map(FileClientException::new); } diff --git a/src/main/java/ject/componote/infra/file/error/FileClientException.java b/src/main/java/ject/componote/infra/file/error/FileClientException.java index 7821176..04584bf 100644 --- a/src/main/java/ject/componote/infra/file/error/FileClientException.java +++ b/src/main/java/ject/componote/infra/file/error/FileClientException.java @@ -1,11 +1,10 @@ package ject.componote.infra.file.error; -import ject.componote.global.error.ErrorResponse; import ject.componote.infra.error.InfraException; import org.springframework.http.HttpStatus; public class FileClientException extends InfraException { - public FileClientException(final ErrorResponse errorResponse) { - super(errorResponse.getMessage(), HttpStatus.valueOf(errorResponse.getStatus())); + public FileClientException(final FileServerErrorResponse response) { + super(response.getMessage(), HttpStatus.valueOf(response.getStatus())); } } From fd576ab1d6f1d442b5b1dbc7edca868d97863ec6 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 21:48:44 +0900 Subject: [PATCH 141/156] =?UTF-8?q?[FEAT]=20CommentReplyCountIncreaseEvent?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/reply/event/CommentReplyCountIncreaseEvent.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/dto/reply/event/CommentReplyCountIncreaseEvent.java diff --git a/src/main/java/ject/componote/domain/comment/dto/reply/event/CommentReplyCountIncreaseEvent.java b/src/main/java/ject/componote/domain/comment/dto/reply/event/CommentReplyCountIncreaseEvent.java new file mode 100644 index 0000000..9baacd1 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/dto/reply/event/CommentReplyCountIncreaseEvent.java @@ -0,0 +1,7 @@ +package ject.componote.domain.comment.dto.reply.event; + +public record CommentReplyCountIncreaseEvent(Long parentId) { + public static CommentReplyCountIncreaseEvent from(final Long parentId) { + return new CommentReplyCountIncreaseEvent(parentId); + } +} From e5416b8583c6444d4f40835ad864174c11b08121 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 21:48:51 +0900 Subject: [PATCH 142/156] =?UTF-8?q?[FEAT]=20CommentReplyCountEventHandler?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommentReplyCountEventHandler.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/application/CommentReplyCountEventHandler.java diff --git a/src/main/java/ject/componote/domain/comment/application/CommentReplyCountEventHandler.java b/src/main/java/ject/componote/domain/comment/application/CommentReplyCountEventHandler.java new file mode 100644 index 0000000..f0c1813 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/application/CommentReplyCountEventHandler.java @@ -0,0 +1,31 @@ +package ject.componote.domain.comment.application; + +import ject.componote.domain.comment.dao.CommentRepository; +import ject.componote.domain.comment.domain.Comment; +import ject.componote.domain.comment.dto.reply.event.CommentReplyCountIncreaseEvent; +import ject.componote.domain.comment.error.NotFoundCommentException; +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class CommentReplyCountEventHandler { + private final CommentRepository commentRepository; + + @Async + @EventListener + @Transactional + public void handleCommentReplyCountIncreaseEvent(final CommentReplyCountIncreaseEvent event) { + final Comment comment = findCommentById(event.parentId()); + comment.increaseReplyCount(); + } + + public Comment findCommentById(final Long commentId) { + return commentRepository.findById(commentId) + .orElseThrow(() -> new NotFoundCommentException(commentId)); + } +} From c3d2cca280100261328a88c8fa425c3ce8f56fdc Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 21:49:05 +0900 Subject: [PATCH 143/156] =?UTF-8?q?[FEAT]=20CommentFindByParentDao=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/dao/CommentFindByParentDao.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/dao/CommentFindByParentDao.java diff --git a/src/main/java/ject/componote/domain/comment/dao/CommentFindByParentDao.java b/src/main/java/ject/componote/domain/comment/dao/CommentFindByParentDao.java new file mode 100644 index 0000000..e4b40e6 --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/dao/CommentFindByParentDao.java @@ -0,0 +1,23 @@ +package ject.componote.domain.comment.dao; + +import ject.componote.domain.auth.domain.Job; +import ject.componote.domain.auth.model.Nickname; +import ject.componote.domain.auth.model.ProfileImage; +import ject.componote.domain.comment.model.CommentContent; +import ject.componote.domain.comment.model.CommentImage; +import ject.componote.domain.common.model.Count; + +import java.time.LocalDateTime; + +public record CommentFindByParentDao( + Long memberId, + Nickname nickname, + ProfileImage profileImage, + Job job, + Long commentId, + CommentImage commentImage, + CommentContent content, + LocalDateTime createdAt, + Count likeCount, + boolean isLiked) { +} From 13d81f59343aa30288712330245377f7357d3ba3 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 21:49:13 +0900 Subject: [PATCH 144/156] =?UTF-8?q?[FEAT]=20CommentFindByParentResponse=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/CommentFindByParentResponse.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByParentResponse.java diff --git a/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByParentResponse.java b/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByParentResponse.java new file mode 100644 index 0000000..665680e --- /dev/null +++ b/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByParentResponse.java @@ -0,0 +1,37 @@ +package ject.componote.domain.comment.dto.find.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import ject.componote.domain.comment.dao.CommentFindByParentDao; + +import java.time.LocalDateTime; + +public record CommentFindByParentResponse( + CommentProfileResponse profile, + Long id, + @JsonInclude(JsonInclude.Include.NON_NULL) String imageUrl, + String content, + LocalDateTime createdAt, + Long likeCount, + boolean isLiked +) { + public static CommentFindByParentResponse from(final CommentFindByParentDao dto) { + return new CommentFindByParentResponse( + createProfileResponse(dto), + dto.commentId(), + dto.commentImage().getImage().toUrl(), + dto.content().getValue(), + dto.createdAt(), + dto.likeCount().getValue(), + dto.isLiked() + ); + } + + private static CommentProfileResponse createProfileResponse(final CommentFindByParentDao dto) { + return new CommentProfileResponse( + dto.memberId(), + dto.nickname().getValue(), + dto.profileImage().getImage().toUrl(), + dto.job().name() + ); + } +} From 1623747fb6b5b538bb8637cbc2baadc5734f2c19 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 21:49:32 +0900 Subject: [PATCH 145/156] =?UTF-8?q?[FEAT]=20Comment=20replyCount=20?= =?UTF-8?q?=ED=95=84=EB=93=9C,=20increaseReplyCount()=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ject/componote/domain/comment/domain/Comment.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/ject/componote/domain/comment/domain/Comment.java b/src/main/java/ject/componote/domain/comment/domain/Comment.java index 71c992a..a46d0a6 100644 --- a/src/main/java/ject/componote/domain/comment/domain/Comment.java +++ b/src/main/java/ject/componote/domain/comment/domain/Comment.java @@ -43,6 +43,10 @@ public class Comment extends BaseEntity { @Convert(converter = CountConverter.class) private Count likeCount; + @Column(name = "reply_count", nullable = false) + @Convert(converter = CountConverter.class) + private Count replyCount; + @Column(name = "component_id", nullable = false) private Long componentId; @@ -60,6 +64,7 @@ private Comment(final Long componentId, final Long memberId, final Long parentId this.image = CommentImage.from(objectKey); this.likeCount = Count.create(); this.reportCount = Count.create(); + this.replyCount = Count.create(); } public static Comment createWithImage(final Long componentId, final Long memberId, final String content, final String objectKey) { @@ -86,6 +91,10 @@ public void decreaseLikeCount() { this.likeCount.decrease(); } + public void increaseReplyCount() { + this.replyCount.increase(); + } + public boolean equalsImage(final CommentImage image) { return this.image.equals(image); } From dc2db277546a867b5a952c25f8d440433ebf85ac Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 21:50:15 +0900 Subject: [PATCH 146/156] =?UTF-8?q?[FEAT]=20CommentService.getRepliesByCom?= =?UTF-8?q?ponentId()=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20-=20=EB=8C=80=EB=8C=93=EA=B8=80=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=EB=84=A4=EC=9D=B4=EC=85=98=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/application/CommentService.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/ject/componote/domain/comment/application/CommentService.java b/src/main/java/ject/componote/domain/comment/application/CommentService.java index a1df865..8b8e0ad 100644 --- a/src/main/java/ject/componote/domain/comment/application/CommentService.java +++ b/src/main/java/ject/componote/domain/comment/application/CommentService.java @@ -2,6 +2,7 @@ import ject.componote.domain.auth.model.AuthPrincipal; import ject.componote.domain.comment.dao.CommentFindByComponentDao; +import ject.componote.domain.comment.dao.CommentFindByParentDao; import ject.componote.domain.comment.dao.CommentLikeRepository; import ject.componote.domain.comment.dao.CommentRepository; import ject.componote.domain.comment.domain.Comment; @@ -9,6 +10,7 @@ import ject.componote.domain.comment.dto.create.response.CommentCreateResponse; import ject.componote.domain.comment.dto.find.response.CommentFindByComponentResponse; import ject.componote.domain.comment.dto.find.response.CommentFindByMemberResponse; +import ject.componote.domain.comment.dto.find.response.CommentFindByParentResponse; import ject.componote.domain.comment.dto.like.event.CommentLikeEvent; import ject.componote.domain.comment.dto.like.event.CommentUnlikeEvent; import ject.componote.domain.comment.dto.update.request.CommentUpdateRequest; @@ -65,6 +67,14 @@ public PageResponse getCommentsByComponentId(fin return PageResponse.from(page); } + public PageResponse getRepliesByComponentId(final AuthPrincipal authPrincipal, + final Long parentId, + final Pageable pageable) { + final Page page = findCommentsByParentId(authPrincipal, parentId, pageable) + .map(CommentFindByParentResponse::from); + return PageResponse.from(page); + } + @CommenterValidation @Transactional public void update(final AuthPrincipal authPrincipal, final Long commentId, final CommentUpdateRequest commentUpdateRequest) { From 02c32d149d9c6679ef0ae6c6b2d7580fe0c62653 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 21:50:48 +0900 Subject: [PATCH 147/156] =?UTF-8?q?[FEAT]=20QCommentDaoFactory=EC=97=90=20?= =?UTF-8?q?CommentFindByParentDao=20=EC=83=9D=EC=84=B1=EC=9E=90=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/dao/QCommentDaoFactory.java | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/main/java/ject/componote/domain/comment/dao/QCommentDaoFactory.java b/src/main/java/ject/componote/domain/comment/dao/QCommentDaoFactory.java index 1e100cb..e248fa3 100644 --- a/src/main/java/ject/componote/domain/comment/dao/QCommentDaoFactory.java +++ b/src/main/java/ject/componote/domain/comment/dao/QCommentDaoFactory.java @@ -26,7 +26,8 @@ public ConstructorExpression createForComponent() { comment.content, comment.createdAt, comment.likeCount, - Expressions.nullExpression(Boolean.class), + comment.replyCount, + Expressions.asBoolean(false), comment.parentId.isNotNull() ); } @@ -39,16 +40,49 @@ public ConstructorExpression createForComponentWithLi member.profileImage, member.job, comment.id, - comment.parentId, comment.image, comment.content, comment.createdAt, comment.likeCount, + comment.replyCount, commentLike.isNotNull(), comment.parentId.isNotNull() ); } + public ConstructorExpression createForParent() { + return Projections.constructor( + CommentFindByParentDao.class, + member.id, + member.nickname, + member.profileImage, + member.job, + comment.id, + comment.image, + comment.content, + comment.createdAt, + comment.likeCount, + Expressions.asBoolean(false) + ); + } + + public ConstructorExpression createForParentWithLikeStatus() { + return Projections.constructor( + CommentFindByParentDao.class, + member.id, + member.nickname, + member.profileImage, + member.job, + comment.id, + comment.parentId, + comment.image, + comment.content, + comment.createdAt, + comment.likeCount, + commentLike.isNotNull() + ); + } + public ConstructorExpression createForMember(final QComment parent) { return Projections.constructor( CommentFindByMemberDao.class, From 9f04fb757485411eea0d58013ce9a20e9a138f10 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 21:51:02 +0900 Subject: [PATCH 148/156] =?UTF-8?q?[FEAT]=20CommentQueryDsl=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=EB=8C=93=EA=B8=80=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EC=85=98=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/dao/CommentQueryDsl.java | 2 ++ .../comment/dao/CommentQueryDslImpl.java | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/main/java/ject/componote/domain/comment/dao/CommentQueryDsl.java b/src/main/java/ject/componote/domain/comment/dao/CommentQueryDsl.java index e824bcb..f8b58d8 100644 --- a/src/main/java/ject/componote/domain/comment/dao/CommentQueryDsl.java +++ b/src/main/java/ject/componote/domain/comment/dao/CommentQueryDsl.java @@ -6,5 +6,7 @@ public interface CommentQueryDsl { Page findAllByComponentIdWithPagination(final Long componentId, final Pageable pageable); Page findAllByComponentIdWithLikeStatusAndPagination(final Long componentId, final Long memberId, final Pageable pageable); + Page findAllByParentIdWithPagination(final Long parentId, final Pageable pageable); + Page findAllByParentIdWithLikeStatusAndPagination(final Long parentId, final Long memberId, final Pageable pageable); Page findAllByMemberIdWithPagination(final Long memberId, final Pageable pageable); } diff --git a/src/main/java/ject/componote/domain/comment/dao/CommentQueryDslImpl.java b/src/main/java/ject/componote/domain/comment/dao/CommentQueryDslImpl.java index 9b48d02..c67c581 100644 --- a/src/main/java/ject/componote/domain/comment/dao/CommentQueryDslImpl.java +++ b/src/main/java/ject/componote/domain/comment/dao/CommentQueryDslImpl.java @@ -45,6 +45,29 @@ public Page findAllByComponentIdWithLikeStatusAndPagi return toPage(baseQuery, countQuery, comment, pageable); } + @Override + public Page findAllByParentIdWithPagination(final Long parentId, final Pageable pageable) { + final BooleanExpression predicate = eqExpression(comment.parentId, parentId); + final JPAQuery countQuery = createCountQuery(predicate); + final JPAQuery baseQuery = queryFactory.select(qCommentDaoFactory.createForParent()) + .from(comment) + .innerJoin(member).on(eqExpression(member.id, comment.memberId)) + .where(predicate); + return toPage(baseQuery, countQuery, comment, pageable); + } + + @Override + public Page findAllByParentIdWithLikeStatusAndPagination(final Long parentId, final Long memberId, final Pageable pageable) { + final BooleanExpression predicate = eqExpression(comment.parentId, parentId); + final JPAQuery countQuery = createCountQuery(predicate); + final JPAQuery baseQuery = queryFactory.select(qCommentDaoFactory.createForParent()) + .from(comment) + .innerJoin(member).on(eqExpression(member.id, comment.memberId)) + .innerJoin(commentLike).on(eqExpression(commentLike.commentId, component.id).and(eqExpression(commentLike.memberId, memberId))) + .where(predicate); + return toPage(baseQuery, countQuery, comment, pageable); + } + @Override public Page findAllByMemberIdWithPagination(final Long memberId, final Pageable pageable) { final BooleanExpression predicate = eqExpression(comment.memberId, memberId); From 3daa3a5fcaa7fca68d95afe2b589b9576ac9ab9f Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 21:51:22 +0900 Subject: [PATCH 149/156] =?UTF-8?q?[FEAT]=20CommentFindByComponentDao=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=EC=97=90=20replyCount=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/dao/CommentFindByComponentDao.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/ject/componote/domain/comment/dao/CommentFindByComponentDao.java b/src/main/java/ject/componote/domain/comment/dao/CommentFindByComponentDao.java index 884d1ca..695d18d 100644 --- a/src/main/java/ject/componote/domain/comment/dao/CommentFindByComponentDao.java +++ b/src/main/java/ject/componote/domain/comment/dao/CommentFindByComponentDao.java @@ -9,5 +9,18 @@ import java.time.LocalDateTime; -public record CommentFindByComponentDao(Long memberId, Nickname nickname, ProfileImage profileImage, Job job, Long commentId, Long parentId, CommentImage commentImage, CommentContent content, LocalDateTime createdAt, Count likeCount, boolean isLiked, boolean isReply) { +public record CommentFindByComponentDao( + Long memberId, + Nickname nickname, + ProfileImage profileImage, + Job job, + Long commentId, + Long parentId, + CommentImage commentImage, + CommentContent content, + LocalDateTime createdAt, + Count likeCount, + Count replyCount, + boolean isLiked, + boolean isReply) { } From 91f85b7e0c52f27fd7ff55f71343623c9efe5d92 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 21:52:04 +0900 Subject: [PATCH 150/156] =?UTF-8?q?[FEAT]=20CommentFindByComponentResponse?= =?UTF-8?q?=20=ED=95=84=EB=93=9C=EC=97=90=20isLiked,=20replyCount=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/find/response/CommentFindByComponentResponse.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByComponentResponse.java b/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByComponentResponse.java index 249b2cd..43059af 100644 --- a/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByComponentResponse.java +++ b/src/main/java/ject/componote/domain/comment/dto/find/response/CommentFindByComponentResponse.java @@ -13,6 +13,8 @@ public record CommentFindByComponentResponse( String content, LocalDateTime createdAt, Long likeCount, + Long replyCount, + boolean isLiked, boolean isReply) { public static CommentFindByComponentResponse from(final CommentFindByComponentDao dto) { return new CommentFindByComponentResponse( @@ -23,6 +25,8 @@ public static CommentFindByComponentResponse from(final CommentFindByComponentDa dto.content().getValue(), dto.createdAt(), dto.likeCount().getValue(), + dto.replyCount().getValue(), + dto.isLiked(), dto.isReply() ); } From cf5b3a8e6922371dd77523ce2760236b0cbf3e5c Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 21:52:28 +0900 Subject: [PATCH 151/156] =?UTF-8?q?[FEAT]=20=EB=8C=80=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=8B=9C=20=EB=B6=80=EB=AA=A8=20=EB=8C=93?= =?UTF-8?q?=EA=B8=80=20replyCount=EB=A5=BC=20=EC=A6=9D=EA=B0=80=EC=8B=9C?= =?UTF-8?q?=ED=82=A4=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../comment/application/CommentService.java | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/main/java/ject/componote/domain/comment/application/CommentService.java b/src/main/java/ject/componote/domain/comment/application/CommentService.java index 8b8e0ad..7a64052 100644 --- a/src/main/java/ject/componote/domain/comment/application/CommentService.java +++ b/src/main/java/ject/componote/domain/comment/application/CommentService.java @@ -13,6 +13,7 @@ import ject.componote.domain.comment.dto.find.response.CommentFindByParentResponse; import ject.componote.domain.comment.dto.like.event.CommentLikeEvent; import ject.componote.domain.comment.dto.like.event.CommentUnlikeEvent; +import ject.componote.domain.comment.dto.reply.event.CommentReplyCountIncreaseEvent; import ject.componote.domain.comment.dto.update.request.CommentUpdateRequest; import ject.componote.domain.comment.error.AlreadyLikedException; import ject.componote.domain.comment.error.NoLikedException; @@ -41,13 +42,11 @@ public class CommentService { @Transactional public CommentCreateResponse create(final AuthPrincipal authPrincipal, final CommentCreateRequest request) { - if (isReply(request)) { - validateParentId(request.parentId()); - } - + validateParentId(request); final Comment comment = commentRepository.save( CommentCreationStrategy.createBy(request, authPrincipal.id()) ); + increaseParentReplyCount(request); fileService.moveImage(comment.getImage().getImage()); return CommentCreateResponse.from(comment); } @@ -131,16 +130,39 @@ private Page findCommentsByComponentId(final AuthPrin return commentRepository.findAllByComponentIdWithLikeStatusAndPagination(componentId, memberId, pageable); } + private Page findCommentsByParentId(final AuthPrincipal authPrincipal, final Long parentId, final Pageable pageable) { + if (authPrincipal == null) { + return commentRepository.findAllByParentIdWithPagination(parentId, pageable); + } + + final Long memberId = authPrincipal.id(); + return commentRepository.findAllByParentIdWithLikeStatusAndPagination(parentId, memberId, pageable); + } + private boolean isReply(final CommentCreateRequest request) { return request.parentId() != null; } - private void validateParentId(final Long parentId) { + private void validateParentId(final CommentCreateRequest request) { + if (!isReply(request)) { + return; + } + + final Long parentId = request.parentId(); if (!commentRepository.existsById(parentId)) { throw new NotFoundParentCommentException(parentId); } } + private void increaseParentReplyCount(final CommentCreateRequest request) { + if (!isReply(request)) { + return; + } + + final Long parentId = request.parentId(); + eventPublisher.publishEvent(CommentReplyCountIncreaseEvent.from(parentId)); + } + private boolean isAlreadyLiked(final Long commentId, final Long memberId) { return commentLikeRepository.existsByCommentIdAndMemberId(commentId, memberId); } From 2ecd500d474eed5f3308f9a23f11f17a42540751 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 21:52:43 +0900 Subject: [PATCH 152/156] =?UTF-8?q?[FEAT]=20=EB=8C=80=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=EB=84=A4=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/api/CommentController.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/ject/componote/domain/comment/api/CommentController.java b/src/main/java/ject/componote/domain/comment/api/CommentController.java index c630090..0e75b4d 100644 --- a/src/main/java/ject/componote/domain/comment/api/CommentController.java +++ b/src/main/java/ject/componote/domain/comment/api/CommentController.java @@ -28,6 +28,7 @@ public class CommentController { private static final int DEFAULT_MEMBER_COMMENT_PAGE_SIZE = 8; private static final int DEFAULT_COMPONENT_COMMENT_PAGE_SIZE = 5; + private static final int DEFAULT_REPLY_PAGE_SIZE = 5; private final CommentService commentService; @@ -61,6 +62,16 @@ public ResponseEntity> getCommentsB return ResponseEntity.ok(pageResponse); } + @GetMapping("/comments/{parentId}/replies") + public ResponseEntity getRepliesByCommentId( + @Authenticated final AuthPrincipal authPrincipal, + @PathVariable("parentId") final Long parentId, + @PageableDefault(size = DEFAULT_REPLY_PAGE_SIZE) final Pageable pageable + ) { + final PageResponse pageResponse = commentService.getRepliesByComponentId(authPrincipal, parentId, pageable); + return ResponseEntity.ok(pageResponse); + } + @PutMapping("/comments/{commentId}") @User public ResponseEntity update(@Authenticated final AuthPrincipal authPrincipal, From 117ab2e365b50cb54264ae7de8c3c7621bb30007 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 22:05:27 +0900 Subject: [PATCH 153/156] =?UTF-8?q?[REFACTOR]=20FileClient=20@Value=20?= =?UTF-8?q?=EC=86=8D=EC=84=B1=EA=B0=92=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ject/componote/infra/file/application/FileClient.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/ject/componote/infra/file/application/FileClient.java b/src/main/java/ject/componote/infra/file/application/FileClient.java index 5e2a58d..0595d2c 100644 --- a/src/main/java/ject/componote/infra/file/application/FileClient.java +++ b/src/main/java/ject/componote/infra/file/application/FileClient.java @@ -23,10 +23,10 @@ public class FileClient { private final TimeoutDecorator timeoutDecorator; private final WebClient webClient; - public FileClient(@Value("${file.max-retry}") final int maxRetry, - @Value("${file.timeout}") final int timeout, - @Value("${file.client.move.method}") final HttpMethod method, - @Value("${file.client.move.uri}") final String uri, + public FileClient(@Value("${storage.max-retry}") final int maxRetry, + @Value("${storage.timeout}") final int timeout, + @Value("${storage.client.move.method}") final HttpMethod method, + @Value("${storage.client.move.uri}") final String uri, final TimeoutDecorator timeoutDecorator, final WebClient webClient) { this.maxRetry = maxRetry; From 839b44f51a40cb62182dde9ca3615ffa379ec4e2 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 22:05:44 +0900 Subject: [PATCH 154/156] =?UTF-8?q?[HOTFIX]=20=EC=B6=A9=EB=8F=8C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/domain/block/detail/ImageBlock.java | 8 ++++---- .../component/domain/block/detail/TextBlock.java | 8 ++++---- .../component/domain/summary/ComponentSummary.java | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/ject/componote/domain/component/domain/block/detail/ImageBlock.java b/src/main/java/ject/componote/domain/component/domain/block/detail/ImageBlock.java index ae85b68..0f7d652 100644 --- a/src/main/java/ject/componote/domain/component/domain/block/detail/ImageBlock.java +++ b/src/main/java/ject/componote/domain/component/domain/block/detail/ImageBlock.java @@ -5,8 +5,8 @@ import jakarta.persistence.Entity; import ject.componote.domain.common.model.BaseImage; import ject.componote.domain.common.model.converter.BaseImageConverter; -import ject.componote.domain.component.domain.block.BlockType; -import ject.componote.domain.component.domain.block.ContentBlock; +import ject.componote.domain.component.domain.detail.DetailType; +import ject.componote.domain.component.domain.detail.block.ContentBlock; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -21,12 +21,12 @@ public class ImageBlock extends ContentBlock { @Column(name = "image", nullable = false) private BaseImage image; - private ImageBlock(final BlockType type, final BaseImage image, final Integer order) { + private ImageBlock(final DetailType type, final BaseImage image, final Integer order) { super(type, order); this.image = image; } - public static ImageBlock of(final BlockType type, final BaseImage image, final Integer order) { + public static ImageBlock of(final DetailType type, final BaseImage image, final Integer order) { return new ImageBlock(type, image, order); } } \ No newline at end of file diff --git a/src/main/java/ject/componote/domain/component/domain/block/detail/TextBlock.java b/src/main/java/ject/componote/domain/component/domain/block/detail/TextBlock.java index 050540c..8f454f8 100644 --- a/src/main/java/ject/componote/domain/component/domain/block/detail/TextBlock.java +++ b/src/main/java/ject/componote/domain/component/domain/block/detail/TextBlock.java @@ -3,8 +3,8 @@ import jakarta.persistence.Column; import jakarta.persistence.Convert; import jakarta.persistence.Entity; -import ject.componote.domain.component.domain.block.BlockType; -import ject.componote.domain.component.domain.block.ContentBlock; +import ject.componote.domain.component.domain.detail.DetailType; +import ject.componote.domain.component.domain.detail.block.ContentBlock; import ject.componote.domain.component.model.ComponentContent; import ject.componote.domain.component.model.converter.ComponentContentConverter; import lombok.AccessLevel; @@ -21,12 +21,12 @@ public class TextBlock extends ContentBlock { @Column(name = "content", nullable = false) private ComponentContent content; - private TextBlock(final BlockType type, final ComponentContent content, final Integer order) { + private TextBlock(final DetailType type, final ComponentContent content, final Integer order) { super(type, order); this.content = content; } - public static TextBlock of(final BlockType type, final ComponentContent content, final Integer order) { + public static TextBlock of(final DetailType type, final ComponentContent content, final Integer order) { return new TextBlock(type, content, order); } } diff --git a/src/main/java/ject/componote/domain/component/domain/summary/ComponentSummary.java b/src/main/java/ject/componote/domain/component/domain/summary/ComponentSummary.java index 57b0194..2955843 100644 --- a/src/main/java/ject/componote/domain/component/domain/summary/ComponentSummary.java +++ b/src/main/java/ject/componote/domain/component/domain/summary/ComponentSummary.java @@ -3,8 +3,8 @@ import jakarta.persistence.Column; import jakarta.persistence.Convert; import jakarta.persistence.Embeddable; -import ject.componote.domain.common.model.Image; -import ject.componote.domain.common.model.converter.ImageConverter; +import ject.componote.domain.common.model.BaseImage; +import ject.componote.domain.common.model.converter.BaseImageConverter; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -21,11 +21,11 @@ public class ComponentSummary { @Column(name = "summary", nullable = false) private String summary; - @Convert(converter = ImageConverter.class) + @Convert(converter = BaseImageConverter.class) @Column(name = "thumbnail", nullable = false) - private Image thumbnail; + private BaseImage thumbnail; - private ComponentSummary(final String title, final String summary, final Image thumbnail) { + private ComponentSummary(final String title, final String summary, final BaseImage thumbnail) { validateTitle(title); validateSummary(summary); this.title = title; @@ -41,7 +41,7 @@ private void validateSummary(final String summary) { } - public static ComponentSummary of(final String title, final String summary, final Image thumbnail) { + public static ComponentSummary of(final String title, final String summary, final BaseImage thumbnail) { return new ComponentSummary(title, summary, thumbnail); } } From d150c18edeb3ec491a0f3c3cf426d89dbac44ba5 Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 22:09:18 +0900 Subject: [PATCH 155/156] =?UTF-8?q?[TEST]=20CommentServiceTest=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/comment/application/CommentServiceTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/ject/componote/domain/comment/application/CommentServiceTest.java b/src/test/java/ject/componote/domain/comment/application/CommentServiceTest.java index bea9bf3..28a16c9 100644 --- a/src/test/java/ject/componote/domain/comment/application/CommentServiceTest.java +++ b/src/test/java/ject/componote/domain/comment/application/CommentServiceTest.java @@ -144,8 +144,8 @@ public void getCommentsByComponentIdNoLoggedIn() throws Exception { // given final Long componentId = 1L; final List content = List.of( - new CommentFindByComponentDao(1L, Nickname.from("닉네임1"), ProfileImage.from(null), Job.DEVELOPER, 1L, null, CommentImage.from(null), CommentContent.from("댓글 내용1"), LocalDateTime.now(), Count.create(), false, false), - new CommentFindByComponentDao(2L, Nickname.from("닉네임2"), ProfileImage.from(null), Job.DEVELOPER, 2L, 1L, CommentImage.from(null), CommentContent.from("댓글 내용2"), LocalDateTime.now(), Count.create(), false, true) + new CommentFindByComponentDao(1L, Nickname.from("닉네임1"), ProfileImage.from(null), Job.DEVELOPER, 1L, null, CommentImage.from(null), CommentContent.from("댓글 내용1"), LocalDateTime.now(), Count.create(), Count.create(), false, false), + new CommentFindByComponentDao(2L, Nickname.from("닉네임2"), ProfileImage.from(null), Job.DEVELOPER, 2L, 1L, CommentImage.from(null), CommentContent.from("댓글 내용2"), LocalDateTime.now(), Count.create(), Count.create(), false, true) ); final Page page = new PageImpl<>(content, pageable, content.size()); final PageResponse expect = PageResponse.from( @@ -168,8 +168,8 @@ public void getCommentsByComponentIdWhenLoggedIn() throws Exception { final Long componentId = 1L; final Long memberId = authPrincipal.id(); final List content = List.of( - new CommentFindByComponentDao(1L, Nickname.from("닉네임1"), ProfileImage.from(null), Job.DEVELOPER, 1L, null, CommentImage.from(null), CommentContent.from("댓글 내용1"), LocalDateTime.now(), Count.create(), false, false), - new CommentFindByComponentDao(2L, Nickname.from("닉네임2"), ProfileImage.from(null), Job.DEVELOPER, 2L, 1L, CommentImage.from(null), CommentContent.from("댓글 내용2"), LocalDateTime.now(), Count.create(), false, true) + new CommentFindByComponentDao(1L, Nickname.from("닉네임1"), ProfileImage.from(null), Job.DEVELOPER, 1L, null, CommentImage.from(null), CommentContent.from("댓글 내용1"), LocalDateTime.now(), Count.create(), Count.create(), false, false), + new CommentFindByComponentDao(2L, Nickname.from("닉네임2"), ProfileImage.from(null), Job.DEVELOPER, 2L, 1L, CommentImage.from(null), CommentContent.from("댓글 내용2"), LocalDateTime.now(), Count.create(), Count.create(), false, true) ); final Page page = new PageImpl<>(content, pageable, content.size()); final PageResponse expect = PageResponse.from( From 9e252ee8d3c799f8e115c86bcac723f62db5b65c Mon Sep 17 00:00:00 2001 From: kmw2378 Date: Thu, 2 Jan 2025 22:18:32 +0900 Subject: [PATCH 156/156] =?UTF-8?q?[FEAT]=20QComponentDaoFactory=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/dao/QComponentDaoFactory.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/main/java/ject/componote/domain/component/dao/QComponentDaoFactory.java diff --git a/src/main/java/ject/componote/domain/component/dao/QComponentDaoFactory.java b/src/main/java/ject/componote/domain/component/dao/QComponentDaoFactory.java new file mode 100644 index 0000000..b55d616 --- /dev/null +++ b/src/main/java/ject/componote/domain/component/dao/QComponentDaoFactory.java @@ -0,0 +1,40 @@ +package ject.componote.domain.component.dao; + +import com.querydsl.core.types.ConstructorExpression; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.Expressions; +import org.springframework.stereotype.Component; + +import static ject.componote.domain.bookmark.domain.QBookmark.bookmark; +import static ject.componote.domain.component.domain.QComponent.component; + +@Component +public class QComponentDaoFactory { + public ConstructorExpression createForSummary(final boolean withBookmark) { + if (withBookmark) { + return Projections.constructor( + ComponentSummaryDao.class, + component.id, + component.summary, + component.type, + component.bookmarkCount, + component.commentCount, + component.designReferenceCount, + component.viewCount, + bookmark.isNotNull() + ); + } + + return Projections.constructor( + ComponentSummaryDao.class, + component.id, + component.summary, + component.type, + component.bookmarkCount, + component.commentCount, + component.designReferenceCount, + component.viewCount, + Expressions.asBoolean(false) + ); + } +}