From c8fad59095b49e047b05893a48e45920c27d0398 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Wed, 30 Jul 2025 20:40:22 +0900 Subject: [PATCH 01/14] =?UTF-8?q?[fix]=20=EA=B8=B0=EC=A1=B4=20createdAt=20?= =?UTF-8?q?=EA=B0=B1=EC=8B=A0=20->=20modifiedAt=20=EA=B0=B1=EC=8B=A0?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95=20=ED=9B=84=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=20=EC=A1=B0=EA=B1=B4=EB=8F=84=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#114)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java | 2 +- .../recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java | 2 +- .../service/manager/RecentSearchCreateManager.java | 2 +- .../java/konkuk/thip/recentSearch/domain/RecentSearch.java | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java b/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java index fcad1c86c..e5bc93deb 100644 --- a/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java +++ b/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java @@ -10,9 +10,9 @@ @SuperBuilder public class BaseDomainEntity { - @Setter private LocalDateTime createdAt; + @Setter private LocalDateTime modifiedAt; private StatusType status; diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java b/src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java index 5919d66bb..44cd1d21a 100644 --- a/src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java +++ b/src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java @@ -34,6 +34,6 @@ public class RecentSearchJpaEntity extends BaseJpaEntity { public void updateFrom(RecentSearch recentSearch) { this.searchTerm = recentSearch.getSearchTerm(); this.type = SearchType.from(recentSearch.getType()); - this.setCreatedAt(recentSearch.getCreatedAt()); + this.setModifiedAt(recentSearch.getModifiedAt()); } } \ No newline at end of file diff --git a/src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java b/src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java index 1a0ad1994..a8ab988e8 100644 --- a/src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java +++ b/src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java @@ -24,7 +24,7 @@ public void saveRecentSearchByUser(Long userId, String keyword) { .ifPresentOrElse( existingRecentSearch -> { // 이미 존재하면 createdAt만 갱신 - existingRecentSearch.updateCreatedAt(LocalDateTime.now()); + existingRecentSearch.updateModifiedAt(LocalDateTime.now()); recentSearchCommandPort.update(existingRecentSearch); }, () -> { diff --git a/src/main/java/konkuk/thip/recentSearch/domain/RecentSearch.java b/src/main/java/konkuk/thip/recentSearch/domain/RecentSearch.java index 068718a78..19dabeca7 100644 --- a/src/main/java/konkuk/thip/recentSearch/domain/RecentSearch.java +++ b/src/main/java/konkuk/thip/recentSearch/domain/RecentSearch.java @@ -26,7 +26,7 @@ public static RecentSearch withoutId(String searchTerm, String type, Long userId .build(); } - public void updateCreatedAt(LocalDateTime localDateTime) { - this.setCreatedAt(localDateTime); + public void updateModifiedAt(LocalDateTime localDateTime) { + this.setModifiedAt(localDateTime); } } From 7de9e15c82a823d116bc1cb4c2583a0373c6d9ad Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Wed, 30 Jul 2025 20:40:49 +0900 Subject: [PATCH 02/14] =?UTF-8?q?[feat]=20SearchType=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=ED=95=B4=EC=84=9C=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0?= =?UTF-8?q?=EC=99=80=20JPA=20enum=20=EB=A7=A4=ED=95=91=20(#114)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/out/jpa/SearchType.java | 8 +++-- .../recentSearch/domain/RecentSearchType.java | 31 +++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 src/main/java/konkuk/thip/recentSearch/domain/RecentSearchType.java diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/SearchType.java b/src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/SearchType.java index 4f01d8105..269347de7 100644 --- a/src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/SearchType.java +++ b/src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/SearchType.java @@ -1,7 +1,7 @@ package konkuk.thip.recentSearch.adapter.out.jpa; -import konkuk.thip.common.exception.BusinessException; +import konkuk.thip.common.exception.InvalidStateException; import lombok.Getter; import static konkuk.thip.common.exception.code.ErrorCode.INVALID_SEARCH_TYPE; @@ -10,7 +10,9 @@ public enum SearchType { USER_SEARCH("사용자 검색"), - BOOK_SEARCH("책 검색"); + BOOK_SEARCH("책 검색"), + ROOM_SEARCH("방 검색"), + ; private final String searchType; @@ -24,6 +26,6 @@ public static SearchType from(String searchType) { return type; } } - throw new BusinessException(INVALID_SEARCH_TYPE); + throw new InvalidStateException(INVALID_SEARCH_TYPE); } } \ No newline at end of file diff --git a/src/main/java/konkuk/thip/recentSearch/domain/RecentSearchType.java b/src/main/java/konkuk/thip/recentSearch/domain/RecentSearchType.java new file mode 100644 index 000000000..6dd504000 --- /dev/null +++ b/src/main/java/konkuk/thip/recentSearch/domain/RecentSearchType.java @@ -0,0 +1,31 @@ +package konkuk.thip.recentSearch.domain; + +import konkuk.thip.common.exception.InvalidStateException; +import lombok.Getter; + +import static konkuk.thip.common.exception.code.ErrorCode.INVALID_SEARCH_TYPE; + +@Getter +public enum RecentSearchType { + + USER_SEARCH("USER","사용자 검색"), + BOOK_SEARCH("BOOK","책 검색"), + ROOM_SEARCH("ROOM","방 검색"); + + private final String param; + private final String type; + + RecentSearchType(String param, String type) { + this.param = param; + this.type = type; + } + + public static RecentSearchType from(String param) { + for (RecentSearchType recentSearchType : RecentSearchType.values()) { + if (recentSearchType.getParam().equals(param)) { + return recentSearchType; + } + } + throw new InvalidStateException(INVALID_SEARCH_TYPE); + } +} From b9cface49a3d8e69abc6c4e01dd0ed527078c53c Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Wed, 30 Jul 2025 20:40:58 +0900 Subject: [PATCH 03/14] =?UTF-8?q?[feat]=20=EC=B5=9C=EA=B7=BC=EA=B2=80?= =?UTF-8?q?=EC=83=89=EC=96=B4=20=EC=A1=B0=ED=9A=8C=20api=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#114)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/RecentSearchQueryController.java | 18 +++++++++++++++++ .../in/web/response/DummyResponse.java | 7 ------- .../web/response/RecentSearchGetResponse.java | 11 ++++++++++ .../RecentSearchQueryPersistenceAdapter.java | 9 +++++++++ .../RecentSearchQueryRepository.java | 3 +++ .../RecentSearchQueryRepositoryImpl.java | 19 +++++++++++++++++- .../application/port/in/DummyUseCase.java | 5 ----- .../port/in/RecentSearchGetUseCase.java | 9 +++++++++ .../port/out/RecentSearchQueryPort.java | 3 +++ .../service/RecentSearchService.java | 20 +++++++++++++++++-- 10 files changed, 89 insertions(+), 15 deletions(-) delete mode 100644 src/main/java/konkuk/thip/recentSearch/adapter/in/web/response/DummyResponse.java create mode 100644 src/main/java/konkuk/thip/recentSearch/adapter/in/web/response/RecentSearchGetResponse.java delete mode 100644 src/main/java/konkuk/thip/recentSearch/application/port/in/DummyUseCase.java create mode 100644 src/main/java/konkuk/thip/recentSearch/application/port/in/RecentSearchGetUseCase.java diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchQueryController.java b/src/main/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchQueryController.java index cb921ec47..03fd13a9b 100644 --- a/src/main/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchQueryController.java +++ b/src/main/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchQueryController.java @@ -1,10 +1,28 @@ package konkuk.thip.recentSearch.adapter.in.web; +import io.swagger.v3.oas.annotations.tags.Tag; +import konkuk.thip.common.dto.BaseResponse; +import konkuk.thip.common.security.annotation.UserId; +import konkuk.thip.recentSearch.adapter.in.web.response.RecentSearchGetResponse; +import konkuk.thip.recentSearch.application.port.in.RecentSearchGetUseCase; import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "Recent Search Query API", description = "최근 검색어 조회 API") @RestController @RequiredArgsConstructor public class RecentSearchQueryController { + private final RecentSearchGetUseCase recentSearchGetUseCase; + + @GetMapping("/recent-searches") + public BaseResponse showRecentSearches( + @RequestParam(value = "type") String type, + @UserId final Long userId + ) { + return BaseResponse.ok(RecentSearchGetResponse.of(recentSearchGetUseCase.getRecentSearches(type, userId))); + } + } diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/in/web/response/DummyResponse.java b/src/main/java/konkuk/thip/recentSearch/adapter/in/web/response/DummyResponse.java deleted file mode 100644 index f03394880..000000000 --- a/src/main/java/konkuk/thip/recentSearch/adapter/in/web/response/DummyResponse.java +++ /dev/null @@ -1,7 +0,0 @@ -package konkuk.thip.recentSearch.adapter.in.web.response; - -import lombok.Getter; - -@Getter -public class DummyResponse { -} diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/in/web/response/RecentSearchGetResponse.java b/src/main/java/konkuk/thip/recentSearch/adapter/in/web/response/RecentSearchGetResponse.java new file mode 100644 index 000000000..7dc63cc54 --- /dev/null +++ b/src/main/java/konkuk/thip/recentSearch/adapter/in/web/response/RecentSearchGetResponse.java @@ -0,0 +1,11 @@ +package konkuk.thip.recentSearch.adapter.in.web.response; + +import java.util.List; + +public record RecentSearchGetResponse( + List recentSearchList +) { + public static RecentSearchGetResponse of(List recentSearchList) { + return new RecentSearchGetResponse(recentSearchList); + } +} diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchQueryPersistenceAdapter.java b/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchQueryPersistenceAdapter.java index 2f9426342..f0e33e38b 100644 --- a/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchQueryPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchQueryPersistenceAdapter.java @@ -7,6 +7,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; import static konkuk.thip.recentSearch.adapter.out.jpa.SearchType.USER_SEARCH; @@ -23,4 +24,12 @@ public Optional findRecentSearchByKeywordAndUserId(String keyword, return recentSearchJpaRepository.findBySearchTermAndTypeAndUserId(keyword, USER_SEARCH, userId) .map(recentSearchMapper::toDomainEntity); } + + @Override + public List findRecentSearchesByTypeAndUserId(String type, Long userId) { + return recentSearchJpaRepository.findByTypeAndUserId(type, userId) + .stream() + .map(recentSearchMapper::toDomainEntity) + .toList(); + } } diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepository.java b/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepository.java index a764ef81d..4825a4115 100644 --- a/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepository.java +++ b/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepository.java @@ -3,8 +3,11 @@ import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchJpaEntity; import konkuk.thip.recentSearch.adapter.out.jpa.SearchType; +import java.util.List; import java.util.Optional; public interface RecentSearchQueryRepository { Optional findBySearchTermAndTypeAndUserId(String searchTerm, SearchType type, Long userId); + + List findByTypeAndUserId(String type, Long userId); } diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepositoryImpl.java b/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepositoryImpl.java index f5c0ed5ea..aabf97d17 100644 --- a/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepositoryImpl.java +++ b/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepositoryImpl.java @@ -6,8 +6,10 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; +import static konkuk.thip.common.entity.StatusType.ACTIVE; import static konkuk.thip.recentSearch.adapter.out.jpa.QRecentSearchJpaEntity.recentSearchJpaEntity; @Repository @@ -23,10 +25,25 @@ public Optional findBySearchTermAndTypeAndUserId(String s .where( recentSearchJpaEntity.searchTerm.eq(searchTerm), recentSearchJpaEntity.type.eq(type), - recentSearchJpaEntity.userJpaEntity.userId.eq(userId) + recentSearchJpaEntity.userJpaEntity.userId.eq(userId), + recentSearchJpaEntity.status.eq(ACTIVE) ) .fetchOne(); return Optional.ofNullable(result); } + + @Override + public List findByTypeAndUserId(String type, Long userId) { + return queryFactory + .selectFrom(recentSearchJpaEntity) + .where( + recentSearchJpaEntity.type.eq(SearchType.from(type)), + recentSearchJpaEntity.userJpaEntity.userId.eq(userId), + recentSearchJpaEntity.status.eq(ACTIVE) + ) + .orderBy(recentSearchJpaEntity.modifiedAt.desc()) + .limit(5) + .fetch(); + } } diff --git a/src/main/java/konkuk/thip/recentSearch/application/port/in/DummyUseCase.java b/src/main/java/konkuk/thip/recentSearch/application/port/in/DummyUseCase.java deleted file mode 100644 index 40602a14e..000000000 --- a/src/main/java/konkuk/thip/recentSearch/application/port/in/DummyUseCase.java +++ /dev/null @@ -1,5 +0,0 @@ -package konkuk.thip.recentSearch.application.port.in; - -public interface DummyUseCase { - -} diff --git a/src/main/java/konkuk/thip/recentSearch/application/port/in/RecentSearchGetUseCase.java b/src/main/java/konkuk/thip/recentSearch/application/port/in/RecentSearchGetUseCase.java new file mode 100644 index 000000000..633d5e123 --- /dev/null +++ b/src/main/java/konkuk/thip/recentSearch/application/port/in/RecentSearchGetUseCase.java @@ -0,0 +1,9 @@ +package konkuk.thip.recentSearch.application.port.in; + +import java.util.List; + +public interface RecentSearchGetUseCase { + + List getRecentSearches(String typeParam, Long userId); + +} diff --git a/src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchQueryPort.java b/src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchQueryPort.java index 1a15b29f9..7734bbd49 100644 --- a/src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchQueryPort.java +++ b/src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchQueryPort.java @@ -2,10 +2,13 @@ import konkuk.thip.recentSearch.domain.RecentSearch; +import java.util.List; import java.util.Optional; public interface RecentSearchQueryPort { Optional findRecentSearchByKeywordAndUserId(String keyword, Long userId); + List findRecentSearchesByTypeAndUserId(String type, Long userId); } + diff --git a/src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchService.java b/src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchService.java index f5933f406..f211cbee8 100644 --- a/src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchService.java +++ b/src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchService.java @@ -1,11 +1,27 @@ package konkuk.thip.recentSearch.application.service; -import konkuk.thip.recentSearch.application.port.in.DummyUseCase; +import konkuk.thip.recentSearch.application.port.in.RecentSearchGetUseCase; +import konkuk.thip.recentSearch.application.port.out.RecentSearchQueryPort; +import konkuk.thip.recentSearch.domain.RecentSearch; +import konkuk.thip.recentSearch.domain.RecentSearchType; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.util.List; + @Service @RequiredArgsConstructor -public class RecentSearchService implements DummyUseCase { +public class RecentSearchService implements RecentSearchGetUseCase { + + private final RecentSearchQueryPort recentSearchQueryPort; + + public List getRecentSearches(String typeParam, Long userId) { + RecentSearchType recentSearchType = RecentSearchType.from(typeParam); + List recentSearchList = recentSearchQueryPort.findRecentSearchesByTypeAndUserId(recentSearchType.getType(), userId); + + return recentSearchList.stream() + .map(RecentSearch::getSearchTerm) + .toList(); + } } From 4d5103ce19235fd7edb8f2eb6c0e9a325a7116cc Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Wed, 30 Jul 2025 20:55:02 +0900 Subject: [PATCH 04/14] =?UTF-8?q?[fix]=20=EC=9C=A0=EC=A0=80=20=ED=8C=94?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0/=EC=96=B8=ED=8C=94=EB=A1=9C=EC=9A=B0=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20(changeStatus=20?= =?UTF-8?q?=EC=95=84=EC=98=88=20=EC=A0=9C=EA=B1=B0)=20(#114)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/following/UserFollowService.java | 22 ++++++++----------- .../konkuk/thip/user/domain/Following.java | 17 ++++---------- .../java/konkuk/thip/user/domain/User.java | 10 +-------- 3 files changed, 14 insertions(+), 35 deletions(-) diff --git a/src/main/java/konkuk/thip/user/application/service/following/UserFollowService.java b/src/main/java/konkuk/thip/user/application/service/following/UserFollowService.java index c3be511e5..d47406f49 100644 --- a/src/main/java/konkuk/thip/user/application/service/following/UserFollowService.java +++ b/src/main/java/konkuk/thip/user/application/service/following/UserFollowService.java @@ -13,8 +13,7 @@ import java.util.Optional; -import static konkuk.thip.common.exception.code.ErrorCode.USER_ALREADY_UNFOLLOWED; -import static konkuk.thip.common.exception.code.ErrorCode.USER_CANNOT_FOLLOW_SELF; +import static konkuk.thip.common.exception.code.ErrorCode.*; @Service @RequiredArgsConstructor @@ -35,19 +34,16 @@ public Boolean changeFollowingState(UserFollowCommand followCommand) { Optional optionalFollowing = followingCommandPort.findByUserIdAndTargetUserId(userId, targetUserId); User targetUser = userCommandPort.findById(targetUserId); - if (optionalFollowing.isPresent()) { // 이미 팔로우 관계가 존재하는 경우 - Following following = optionalFollowing.get(); - boolean isFollowing = following.changeFollowingState(type); - targetUser.updateFollowerCount(isFollowing); - followingCommandPort.deleteFollowing(following, targetUser); - return isFollowing; - } else { // 팔로우 관계가 존재하지 않는 경우 - if (!type) { - throw new BusinessException(USER_ALREADY_UNFOLLOWED); // 언팔로우 요청인데 팔로우 관계가 존재하지 않으므로 이미 언팔로우 상태 - } + boolean isFollowRequest = Following.validateFollowingState(optionalFollowing.isPresent(), type); + + if (isFollowRequest) { // 팔로우 요청인 경우 targetUser.increaseFollowerCount(); followingCommandPort.save(Following.withoutId(userId, targetUserId), targetUser); - return true; // 새로 팔로우한 경우 + return true; + } else { // 언팔로우 요청인 경우 + targetUser.decreaseFollowerCount(); + followingCommandPort.deleteFollowing(optionalFollowing.get(), targetUser); + return false; } } diff --git a/src/main/java/konkuk/thip/user/domain/Following.java b/src/main/java/konkuk/thip/user/domain/Following.java index 6ffa7efb2..c96385a2d 100644 --- a/src/main/java/konkuk/thip/user/domain/Following.java +++ b/src/main/java/konkuk/thip/user/domain/Following.java @@ -27,21 +27,12 @@ public static Following withoutId(Long userId, Long followingUserId) { .build(); } - public boolean changeFollowingState(boolean isFollowRequest) { - StatusType currentStatus = getStatus(); - validateFollowingState(isFollowRequest, currentStatus); - - super.changeStatus(); - return isFollowRequest; - } - - private void validateFollowingState(boolean isFollowRequest, StatusType currentStatus) { - if (isFollowRequest && currentStatus == StatusType.ACTIVE) { // 팔로우 요청일 때 이미 팔로우 중인 경우 + public static boolean validateFollowingState(boolean isExistingFollowing, boolean isFollowRequest) { + if (isExistingFollowing && isFollowRequest) { // 이미 팔로우 관계가 존재하는 상태에서 팔로우 요청을 하는 경우 throw new InvalidStateException(USER_ALREADY_FOLLOWED); - } - - if (!isFollowRequest && currentStatus == StatusType.INACTIVE) { // 언팔로우 요청일 때 이미 언팔로우 중인 경우 + } else if (!isExistingFollowing && !isFollowRequest) { // 언팔로우 요청을 하는데 팔로우 관계가 존재하지 않는 경우 throw new InvalidStateException(USER_ALREADY_UNFOLLOWED); } + return isFollowRequest; } } diff --git a/src/main/java/konkuk/thip/user/domain/User.java b/src/main/java/konkuk/thip/user/domain/User.java index 6a7494782..5d0ee1c75 100644 --- a/src/main/java/konkuk/thip/user/domain/User.java +++ b/src/main/java/konkuk/thip/user/domain/User.java @@ -33,19 +33,11 @@ public static User withoutId(String nickname, String userRole, String oauth2Id, .build(); } - public void updateFollowerCount(boolean isFollowing) { - if (isFollowing) { - increaseFollowerCount(); - } else { - decreaseFollowerCount(); - } - } - public void increaseFollowerCount() { followerCount++; } - private void decreaseFollowerCount() { + public void decreaseFollowerCount() { if(followerCount == 0) { throw new InvalidStateException(ErrorCode.FOLLOW_COUNT_IS_ZERO); } From 47292586f3161618c61b3d64bceca2be98f2f59d Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Wed, 30 Jul 2025 20:55:20 +0900 Subject: [PATCH 05/14] =?UTF-8?q?[test]=20=EC=88=98=EC=A0=95=EB=90=9C=20?= =?UTF-8?q?=ED=8C=94=EB=A1=9C=EC=9A=B0/=EC=96=B8=ED=8C=94=EB=A1=9C?= =?UTF-8?q?=EC=9A=B0=20=EA=B2=80=EC=A6=9D=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20(#114)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/user/domain/FollowingTest.java | 99 +++++++------------ 1 file changed, 38 insertions(+), 61 deletions(-) diff --git a/src/test/java/konkuk/thip/user/domain/FollowingTest.java b/src/test/java/konkuk/thip/user/domain/FollowingTest.java index c4426ad7d..c33f84b46 100644 --- a/src/test/java/konkuk/thip/user/domain/FollowingTest.java +++ b/src/test/java/konkuk/thip/user/domain/FollowingTest.java @@ -1,93 +1,70 @@ package konkuk.thip.user.domain; -import konkuk.thip.common.entity.StatusType; import konkuk.thip.common.exception.InvalidStateException; +import konkuk.thip.common.exception.code.ErrorCode; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import static konkuk.thip.common.exception.code.ErrorCode.USER_ALREADY_FOLLOWED; -import static konkuk.thip.common.exception.code.ErrorCode.USER_ALREADY_UNFOLLOWED; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; +@DisplayName("[단위] Following 단위 테스트") class FollowingTest { @Nested - @DisplayName("팔로우 요청") - class Follow { + @DisplayName("팔로우 요청인 경우") + class FollowRequest { - @Test - @DisplayName("inactive 상태에서 follow 요청 → active로 변경") - void follow_from_inactive() { - Following following = Following.builder() - .userId(1L) - .followingUserId(2L) - .status(StatusType.INACTIVE) - .build(); + private final boolean isFollowRequest = true; - boolean result = following.changeFollowingState(true); + @Test + @DisplayName("이미 팔로우 중이면 예외 발생") + void alreadyFollowed_shouldThrowException() { + boolean isExistingFollowing = true; - assertThat(result).isTrue(); - assertThat(following.getStatus()).isEqualTo(StatusType.ACTIVE); + assertThatThrownBy(() -> + Following.validateFollowingState(isExistingFollowing, isFollowRequest)) + .isInstanceOf(InvalidStateException.class) + .hasMessageContaining(ErrorCode.USER_ALREADY_FOLLOWED.getMessage()); } @Test - @DisplayName("이미 active 상태에서 follow 요청 → 예외 발생") - void follow_from_active_should_throw() { - Following following = Following.builder() - .userId(1L) - .followingUserId(2L) - .status(StatusType.ACTIVE) - .build(); - - assertThatThrownBy(() -> following.changeFollowingState(true)) - .isInstanceOf(InvalidStateException.class) - .hasMessage(USER_ALREADY_FOLLOWED.getMessage()); + @DisplayName("팔로우 관계가 없으면 true 반환") + void notFollowed_shouldReturnTrue() { + boolean isExistingFollowing = false; + + boolean result = Following.validateFollowingState(isExistingFollowing, isFollowRequest); + + assertThat(result).isTrue(); } } @Nested - @DisplayName("언팔로우 요청") - class Unfollow { + @DisplayName("언팔로우 요청인 경우") + class UnfollowRequest { - @Test - @DisplayName("active 상태에서 unfollow 요청 → inactive로 변경") - void unfollow_from_active() { - Following following = Following.builder() - .userId(1L) - .followingUserId(2L) - .status(StatusType.ACTIVE) - .build(); + private final boolean isFollowRequest = false; - boolean result = following.changeFollowingState(false); + @Test + @DisplayName("팔로우 관계가 없으면 예외 발생") + void notFollowed_shouldThrowException() { + boolean isExistingFollowing = false; - assertThat(result).isFalse(); - assertThat(following.getStatus()).isEqualTo(StatusType.INACTIVE); + assertThatThrownBy(() -> + Following.validateFollowingState(isExistingFollowing, isFollowRequest)) + .isInstanceOf(InvalidStateException.class) + .hasMessageContaining(ErrorCode.USER_ALREADY_UNFOLLOWED.getMessage()); } @Test - @DisplayName("이미 inactive 상태에서 unfollow 요청 → 예외 발생") - void unfollow_from_inactive_should_throw() { - Following following = Following.builder() - .userId(1L) - .followingUserId(2L) - .status(StatusType.INACTIVE) - .build(); - - assertThatThrownBy(() -> following.changeFollowingState(false)) - .isInstanceOf(InvalidStateException.class) - .hasMessage(USER_ALREADY_UNFOLLOWED.getMessage()); - } - } + @DisplayName("팔로우 중이면 false 반환") + void alreadyFollowed_shouldReturnFalse() { + boolean isExistingFollowing = true; - @Test - @DisplayName("새로운 팔로우 생성 시 상태는 ACTIVE") - void create_following_should_be_active() { - Following following = Following.withoutId(1L, 2L); + boolean result = Following.validateFollowingState(isExistingFollowing, isFollowRequest); - assertThat(following.getUserId()).isEqualTo(1L); - assertThat(following.getFollowingUserId()).isEqualTo(2L); - assertThat(following.getStatus()).isEqualTo(StatusType.ACTIVE); + assertThat(result).isFalse(); + } } } \ No newline at end of file From bb005ff0913fc3b8c4a21f121b6630323cbc1cb7 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Wed, 30 Jul 2025 21:16:21 +0900 Subject: [PATCH 06/14] =?UTF-8?q?[fix]=20=EC=B5=9C=EA=B7=BC=EA=B2=80?= =?UTF-8?q?=EC=83=89=EC=96=B4=20=EC=A1=B0=ED=9A=8C=EC=97=90=EC=84=9C=20id?= =?UTF-8?q?=20=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#114)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/RecentSearchQueryController.java | 10 +++++++--- .../web/response/RecentSearchGetResponse.java | 10 ++++++++-- .../application/RecentSearchQueryMapper.java | 17 +++++++++++++++++ .../port/in/RecentSearchGetUseCase.java | 4 ++-- ...Service.java => RecentSearchGetService.java} | 16 +++++++++++----- 5 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 src/main/java/konkuk/thip/recentSearch/application/RecentSearchQueryMapper.java rename src/main/java/konkuk/thip/recentSearch/application/service/{RecentSearchService.java => RecentSearchGetService.java} (55%) diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchQueryController.java b/src/main/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchQueryController.java index 03fd13a9b..9b4714433 100644 --- a/src/main/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchQueryController.java +++ b/src/main/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchQueryController.java @@ -1,5 +1,7 @@ package konkuk.thip.recentSearch.adapter.in.web; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import konkuk.thip.common.dto.BaseResponse; import konkuk.thip.common.security.annotation.UserId; @@ -10,19 +12,21 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -@Tag(name = "Recent Search Query API", description = "최근 검색어 조회 API") +@Tag(name = "Recent Search Query API", description = "최근 검색어 조회 관련 API") @RestController @RequiredArgsConstructor public class RecentSearchQueryController { private final RecentSearchGetUseCase recentSearchGetUseCase; + @Operation(summary = "최근 검색어 조회", description = "사용자의 최근 검색어를 조회합니다. 최신순으로 최대 5개까지 조회됩니다.") @GetMapping("/recent-searches") public BaseResponse showRecentSearches( + @Parameter(description = "최근 검색어 유형 (사용자 검색 : USER / 방 검색 : ROOM / 책 검색 : BOOK)", example = "USER") @RequestParam(value = "type") String type, - @UserId final Long userId + @Parameter(hidden = true) @UserId final Long userId ) { - return BaseResponse.ok(RecentSearchGetResponse.of(recentSearchGetUseCase.getRecentSearches(type, userId))); + return BaseResponse.ok(recentSearchGetUseCase.getRecentSearches(type, userId)); } } diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/in/web/response/RecentSearchGetResponse.java b/src/main/java/konkuk/thip/recentSearch/adapter/in/web/response/RecentSearchGetResponse.java index 7dc63cc54..99cc1133c 100644 --- a/src/main/java/konkuk/thip/recentSearch/adapter/in/web/response/RecentSearchGetResponse.java +++ b/src/main/java/konkuk/thip/recentSearch/adapter/in/web/response/RecentSearchGetResponse.java @@ -3,9 +3,15 @@ import java.util.List; public record RecentSearchGetResponse( - List recentSearchList + List recentSearchList ) { - public static RecentSearchGetResponse of(List recentSearchList) { + + public record RecentSearchDto( + Long recentSearchId, + String searchTerm + ) { + } + public static RecentSearchGetResponse of(List recentSearchList) { return new RecentSearchGetResponse(recentSearchList); } } diff --git a/src/main/java/konkuk/thip/recentSearch/application/RecentSearchQueryMapper.java b/src/main/java/konkuk/thip/recentSearch/application/RecentSearchQueryMapper.java new file mode 100644 index 000000000..203a13a4d --- /dev/null +++ b/src/main/java/konkuk/thip/recentSearch/application/RecentSearchQueryMapper.java @@ -0,0 +1,17 @@ +package konkuk.thip.recentSearch.application; + +import konkuk.thip.recentSearch.adapter.in.web.response.RecentSearchGetResponse; +import konkuk.thip.recentSearch.domain.RecentSearch; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +import java.util.List; + +@Mapper(componentModel = "spring") +public interface RecentSearchQueryMapper { + + @Mapping(source = "id", target = "recentSearchId") + RecentSearchGetResponse.RecentSearchDto toDto(RecentSearch recentSearch); + + List toResponseList(List recentSearchQueryDtos); +} diff --git a/src/main/java/konkuk/thip/recentSearch/application/port/in/RecentSearchGetUseCase.java b/src/main/java/konkuk/thip/recentSearch/application/port/in/RecentSearchGetUseCase.java index 633d5e123..4bcabdb67 100644 --- a/src/main/java/konkuk/thip/recentSearch/application/port/in/RecentSearchGetUseCase.java +++ b/src/main/java/konkuk/thip/recentSearch/application/port/in/RecentSearchGetUseCase.java @@ -1,9 +1,9 @@ package konkuk.thip.recentSearch.application.port.in; -import java.util.List; +import konkuk.thip.recentSearch.adapter.in.web.response.RecentSearchGetResponse; public interface RecentSearchGetUseCase { - List getRecentSearches(String typeParam, Long userId); + RecentSearchGetResponse getRecentSearches(String typeParam, Long userId); } diff --git a/src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchService.java b/src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchGetService.java similarity index 55% rename from src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchService.java rename to src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchGetService.java index f211cbee8..31d66a54a 100644 --- a/src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchService.java +++ b/src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchGetService.java @@ -1,27 +1,33 @@ package konkuk.thip.recentSearch.application.service; +import konkuk.thip.recentSearch.adapter.in.web.response.RecentSearchGetResponse; +import konkuk.thip.recentSearch.application.RecentSearchQueryMapper; import konkuk.thip.recentSearch.application.port.in.RecentSearchGetUseCase; import konkuk.thip.recentSearch.application.port.out.RecentSearchQueryPort; import konkuk.thip.recentSearch.domain.RecentSearch; import konkuk.thip.recentSearch.domain.RecentSearchType; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @Service @RequiredArgsConstructor -public class RecentSearchService implements RecentSearchGetUseCase { +public class RecentSearchGetService implements RecentSearchGetUseCase { private final RecentSearchQueryPort recentSearchQueryPort; - public List getRecentSearches(String typeParam, Long userId) { + private final RecentSearchQueryMapper recentSearchQueryMapper; + + @Transactional(readOnly = true) + public RecentSearchGetResponse getRecentSearches(String typeParam, Long userId) { RecentSearchType recentSearchType = RecentSearchType.from(typeParam); List recentSearchList = recentSearchQueryPort.findRecentSearchesByTypeAndUserId(recentSearchType.getType(), userId); - return recentSearchList.stream() - .map(RecentSearch::getSearchTerm) - .toList(); + return RecentSearchGetResponse.of( + recentSearchQueryMapper.toResponseList(recentSearchList) + ); } } From f1c5a76bcd5fd88da03e16da2369d3b059eb2ead Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Wed, 30 Jul 2025 21:26:52 +0900 Subject: [PATCH 07/14] =?UTF-8?q?[feat]=20=EC=B5=9C=EA=B7=BC=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=EC=96=B4=20=EC=82=AD=EC=A0=9C=20api=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#114)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/common/exception/code/ErrorCode.java | 1 + .../swagger/SwaggerResponseDescription.java | 6 ++++ .../in/web/RecentSearchCommandController.java | 23 +++++++++++++++ ...RecentSearchCommandPersistenceAdapter.java | 8 ++++++ .../port/in/RecentSearchDeleteUseCase.java | 5 ++++ .../port/out/RecentSearchCommandPort.java | 13 +++++++++ .../service/RecentSearchDeleteService.java | 28 +++++++++++++++++++ 7 files changed, 84 insertions(+) create mode 100644 src/main/java/konkuk/thip/recentSearch/application/port/in/RecentSearchDeleteUseCase.java create mode 100644 src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchDeleteService.java diff --git a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java index 647673959..554eb236f 100644 --- a/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java +++ b/src/main/java/konkuk/thip/common/exception/code/ErrorCode.java @@ -73,6 +73,7 @@ public enum ErrorCode implements ResponseCode { */ INVALID_SEARCH_TYPE(HttpStatus.BAD_REQUEST, 90000,"알맞은 검색어 타입을 찾을 수 없습니다."), RECENT_SEARCH_NOT_FOUND(HttpStatus.NOT_FOUND, 90001, "존재하지 않는 RECENT SEARCH 입니다."), + RECENT_SEARCH_NOT_ADDED_BY_USER(HttpStatus.BAD_REQUEST, 90002, "사용자가 추가하지 않은 검색어는 삭제할 수 없습니다."), /** * 100000 : room error diff --git a/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java b/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java index ded60e9b6..11189f22a 100644 --- a/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java +++ b/src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java @@ -194,6 +194,12 @@ public enum SwaggerResponseDescription { JSON_PROCESSING_ERROR ))), + // Recent Search + RECENT_SEARCH_DELETE(new LinkedHashSet<>(Set.of( + RECENT_SEARCH_NOT_FOUND, + RECENT_SEARCH_NOT_ADDED_BY_USER + ))), + ; private final Set errorCodeList; SwaggerResponseDescription(Set errorCodeList) { diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchCommandController.java b/src/main/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchCommandController.java index 72a4180a4..d92097cfe 100644 --- a/src/main/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchCommandController.java +++ b/src/main/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchCommandController.java @@ -1,10 +1,33 @@ package konkuk.thip.recentSearch.adapter.in.web; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import konkuk.thip.common.dto.BaseResponse; +import konkuk.thip.common.security.annotation.UserId; +import konkuk.thip.common.swagger.annotation.ExceptionDescription; +import konkuk.thip.recentSearch.application.port.in.RecentSearchDeleteUseCase; import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; +import static konkuk.thip.common.swagger.SwaggerResponseDescription.RECENT_SEARCH_DELETE; + +@Tag(name = "Recent Search Command API", description = "최근 검색어 상태 변경 관련 API") @RestController @RequiredArgsConstructor public class RecentSearchCommandController { + private final RecentSearchDeleteUseCase recentSearchDeleteUseCase; + + @Operation(summary = "최근 검색어 삭제", description = "최근 검색어를 삭제합니다.") + @ExceptionDescription(RECENT_SEARCH_DELETE) + @DeleteMapping("/recent-searches/{recentSearchId}") + public BaseResponse deleteRecentSearch( + @PathVariable(value = "recentSearchId") final Long recentSearchId, + @UserId final Long userId + ) { + return BaseResponse.ok(recentSearchDeleteUseCase.deleteRecentSearch(recentSearchId, userId)); + } + } diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java b/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java index 2fb2664cc..693b95fd3 100644 --- a/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java @@ -11,6 +11,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; +import java.util.Optional; + import static konkuk.thip.common.exception.code.ErrorCode.RECENT_SEARCH_NOT_FOUND; import static konkuk.thip.common.exception.code.ErrorCode.USER_NOT_FOUND; @@ -23,6 +25,12 @@ public class RecentSearchCommandPersistenceAdapter implements RecentSearchComman private final RecentSearchMapper recentSearchMapper; + @Override + public Optional findById(Long id) { + return recentSearchJpaRepository.findById(id) + .map(recentSearchMapper::toDomainEntity); + } + @Override public void save(RecentSearch recentSearch) { diff --git a/src/main/java/konkuk/thip/recentSearch/application/port/in/RecentSearchDeleteUseCase.java b/src/main/java/konkuk/thip/recentSearch/application/port/in/RecentSearchDeleteUseCase.java new file mode 100644 index 000000000..3a708f6e0 --- /dev/null +++ b/src/main/java/konkuk/thip/recentSearch/application/port/in/RecentSearchDeleteUseCase.java @@ -0,0 +1,5 @@ +package konkuk.thip.recentSearch.application.port.in; + +public interface RecentSearchDeleteUseCase { + Void deleteRecentSearch(Long recentSearchId, Long userId); +} diff --git a/src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchCommandPort.java b/src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchCommandPort.java index ddb7a19fb..b58af450a 100644 --- a/src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchCommandPort.java +++ b/src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchCommandPort.java @@ -1,9 +1,22 @@ package konkuk.thip.recentSearch.application.port.out; +import konkuk.thip.common.exception.EntityNotFoundException; import konkuk.thip.recentSearch.domain.RecentSearch; +import java.util.Optional; + +import static konkuk.thip.common.exception.code.ErrorCode.RECENT_SEARCH_NOT_FOUND; + public interface RecentSearchCommandPort { + + Optional findById(Long id); + + default RecentSearch getByIdOrThrow(Long id) { + return findById(id) + .orElseThrow(() -> new EntityNotFoundException(RECENT_SEARCH_NOT_FOUND)); + } + void save(RecentSearch recentSearch); void delete(Long id); diff --git a/src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchDeleteService.java b/src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchDeleteService.java new file mode 100644 index 000000000..f703134c3 --- /dev/null +++ b/src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchDeleteService.java @@ -0,0 +1,28 @@ +package konkuk.thip.recentSearch.application.service; + +import konkuk.thip.common.exception.BusinessException; +import konkuk.thip.common.exception.code.ErrorCode; +import konkuk.thip.recentSearch.application.port.in.RecentSearchDeleteUseCase; +import konkuk.thip.recentSearch.application.port.out.RecentSearchCommandPort; +import konkuk.thip.recentSearch.domain.RecentSearch; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class RecentSearchDeleteService implements RecentSearchDeleteUseCase { + + private final RecentSearchCommandPort recentSearchCommandPort; + + @Transactional + public Void deleteRecentSearch(Long recentSearchId, Long userId) { + RecentSearch recentSearch = recentSearchCommandPort.getByIdOrThrow(recentSearchId); + if (!recentSearch.getUserId().equals(userId)) { + throw new BusinessException(ErrorCode.RECENT_SEARCH_NOT_ADDED_BY_USER); + } + + recentSearchCommandPort.delete(recentSearch.getId()); + return null; + } +} From 492657e83191a0c52f06856972a62e3d45951565 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Wed, 30 Jul 2025 21:56:37 +0900 Subject: [PATCH 08/14] =?UTF-8?q?[test]=20=EC=B5=9C=EA=B7=BC=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=EC=96=B4=20=EC=A1=B0=ED=9A=8C=20api=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20(#114)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/RecentSearchDeleteApiTest.java | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchDeleteApiTest.java diff --git a/src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchDeleteApiTest.java b/src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchDeleteApiTest.java new file mode 100644 index 000000000..4763061e5 --- /dev/null +++ b/src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchDeleteApiTest.java @@ -0,0 +1,114 @@ +package konkuk.thip.recentSearch.adapter.in.web; + +import konkuk.thip.common.exception.code.ErrorCode; +import konkuk.thip.common.util.TestEntityFactory; +import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchJpaEntity; +import konkuk.thip.recentSearch.adapter.out.jpa.SearchType; +import konkuk.thip.recentSearch.adapter.out.persistence.repository.RecentSearchJpaRepository; +import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; +import konkuk.thip.user.adapter.out.persistence.repository.alias.AliasJpaRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@ActiveProfiles("test") +@AutoConfigureMockMvc(addFilters = false) +@DisplayName("[통합] 최근 검색어 삭제 API 테스트") +class RecentSearchDeleteApiTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private UserJpaRepository userJpaRepository; + + @Autowired + private AliasJpaRepository aliasJpaRepository; + + @Autowired + private RecentSearchJpaRepository recentSearchJpaRepository; + + private Long currentUserId; + private Long otherUserId; + private Long recentSearchId; + + @BeforeEach + void setUp() { + AliasJpaEntity alias = aliasJpaRepository.save(TestEntityFactory.createLiteratureAlias()); + + // 요청 사용자 + UserJpaEntity currentUser = userJpaRepository.save(TestEntityFactory.createUser(alias, "요청자")); + currentUserId = currentUser.getUserId(); + + // 다른 사용자 + UserJpaEntity otherUser = userJpaRepository.save(TestEntityFactory.createUser(alias, "다른유저")); + otherUserId = otherUser.getUserId(); + + // currentUser가 추가한 최근 검색어 + RecentSearchJpaEntity entity = recentSearchJpaRepository.save( + RecentSearchJpaEntity.builder() + .searchTerm("삭제테스트") + .type(SearchType.USER_SEARCH) + .userJpaEntity(currentUser) + .build() + ); + recentSearchId = entity.getRecentSearchId(); + } + + @Test + @DisplayName("성공적으로 최근 검색어를 삭제한다") + void deleteRecentSearch_success() throws Exception { + // when + mockMvc.perform(delete("/recent-searches/{recentSearchId}", recentSearchId) + .requestAttr("userId", currentUserId) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + // then + List list = recentSearchJpaRepository.findAll(); + assertThat(list).isEmpty(); + } + + @Test + @DisplayName("다른 사용자가 추가한 최근 검색어는 삭제할 수 없다") + void deleteRecentSearch_fail_notOwner() throws Exception { + mockMvc.perform(delete("/recent-searches/{recentSearchId}", recentSearchId) + .requestAttr("userId", otherUserId) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(ErrorCode.RECENT_SEARCH_NOT_ADDED_BY_USER.getCode())); + + // DB에 여전히 남아있음 + assertThat(recentSearchJpaRepository.findById(recentSearchId)).isPresent(); + } + + @Test + @DisplayName("존재하지 않는 최근 검색어를 삭제하려 하면 예외가 발생한다") + void deleteRecentSearch_fail_notFound() throws Exception { + Long notExistingId = 9999L; + + mockMvc.perform(delete("/recent-searches/{recentSearchId}", notExistingId) + .requestAttr("userId", currentUserId) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()); + + // 기존 데이터는 그대로 + assertThat(recentSearchJpaRepository.findAll()).hasSize(1); + } +} \ No newline at end of file From 7b452f397fa9cf8bc030ff2ecb9f803042013a86 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Wed, 30 Jul 2025 21:56:43 +0900 Subject: [PATCH 09/14] =?UTF-8?q?[test]=20=EC=B5=9C=EA=B7=BC=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=EC=96=B4=20=EC=82=AD=EC=A0=9C=20api=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20(#114)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/RecentSearchGetApiTest.java | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchGetApiTest.java diff --git a/src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchGetApiTest.java b/src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchGetApiTest.java new file mode 100644 index 000000000..892f44aaa --- /dev/null +++ b/src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchGetApiTest.java @@ -0,0 +1,106 @@ +package konkuk.thip.recentSearch.adapter.in.web; + +import jakarta.persistence.EntityManager; +import konkuk.thip.common.util.TestEntityFactory; +import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchJpaEntity; +import konkuk.thip.recentSearch.adapter.out.jpa.SearchType; +import konkuk.thip.recentSearch.adapter.out.persistence.repository.RecentSearchJpaRepository; +import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity; +import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; +import konkuk.thip.user.adapter.out.persistence.repository.UserJpaRepository; +import konkuk.thip.user.adapter.out.persistence.repository.alias.AliasJpaRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.stream.IntStream; + +import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@ActiveProfiles("test") +@AutoConfigureMockMvc(addFilters = false) +@Transactional +@DisplayName("[통합] 최근 검색어 조회 API 테스트") +class RecentSearchGetApiTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private EntityManager em; + + @Autowired + private UserJpaRepository userJpaRepository; + + @Autowired + private AliasJpaRepository aliasJpaRepository; + + @Autowired + private RecentSearchJpaRepository recentSearchJpaRepository; + + private Long currentUserId; + + @BeforeEach + void setUp() { + // 사용자 및 별칭 생성 + AliasJpaEntity alias = aliasJpaRepository.save(TestEntityFactory.createLiteratureAlias()); + UserJpaEntity currentUser = userJpaRepository.save(TestEntityFactory.createUser(alias, "검색자")); + currentUserId = currentUser.getUserId(); + + // 최근 검색어 6개 저장 (6개 중 최신 5개만 조회될 예정) + IntStream.rangeClosed(1, 6).forEach(i -> { + RecentSearchJpaEntity saved = recentSearchJpaRepository.save( + RecentSearchJpaEntity.builder() + .searchTerm("검색어" + i) + .type(SearchType.USER_SEARCH) + .userJpaEntity(currentUser) + .build() + ); + + // JPQL update로 modifiedAt을 강제로 원하는 값으로 덮기 + em.createQuery("update RecentSearchJpaEntity r set r.modifiedAt = :time where r.id = :id") + .setParameter("time", LocalDateTime.now().minusMinutes(i)) + .setParameter("id", saved.getRecentSearchId()) + .executeUpdate(); + }); + + em.flush(); + em.clear(); + } + + @Test + @DisplayName("최근 검색어를 최신순으로 최대 5개까지 조회한다") + void getRecentSearches() throws Exception { + // when + ResultActions result = mockMvc.perform( + get("/recent-searches") + .param("type", "USER") + .requestAttr("userId", currentUserId) + ); + + // then + result.andExpect(status().isOk()) + .andExpect(jsonPath("$.data.recentSearchList", hasSize(5))) + .andExpect(jsonPath("$.data.recentSearchList[0].searchTerm").value("검색어1")) + .andExpect(jsonPath("$.data.recentSearchList[1].searchTerm").value("검색어2")) + .andExpect(jsonPath("$.data.recentSearchList[2].searchTerm").value("검색어3")) + .andExpect(jsonPath("$.data.recentSearchList[3].searchTerm").value("검색어4")) + .andExpect(jsonPath("$.data.recentSearchList[4].searchTerm").value("검색어5")); + + // DB에 저장된 최근 검색어 개수 검증 + assertEquals(6, recentSearchJpaRepository.findAll().size()); + } +} \ No newline at end of file From 083e8adfe3bf31f525b52f47014adcfd4b48560d Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Wed, 30 Jul 2025 22:08:48 +0900 Subject: [PATCH 10/14] =?UTF-8?q?[test]=20Transactional=EC=9D=84=20?= =?UTF-8?q?=ED=86=B5=ED=95=B4=20=EB=A1=A4=EB=B0=B1=20(#107)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../recentSearch/adapter/in/web/RecentSearchDeleteApiTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchDeleteApiTest.java b/src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchDeleteApiTest.java index 4763061e5..db55ed4ae 100644 --- a/src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchDeleteApiTest.java +++ b/src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchDeleteApiTest.java @@ -18,6 +18,7 @@ import org.springframework.http.MediaType; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -29,6 +30,7 @@ @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc(addFilters = false) +@Transactional @DisplayName("[통합] 최근 검색어 삭제 API 테스트") class RecentSearchDeleteApiTest { From f361b613bc3d89a4ec35af38b925573d4abc177a Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Wed, 30 Jul 2025 22:12:26 +0900 Subject: [PATCH 11/14] =?UTF-8?q?[test]=20UserFollowServiceTest=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#114)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/UserFollowServiceTest.java | 64 +++++++------------ 1 file changed, 22 insertions(+), 42 deletions(-) diff --git a/src/test/java/konkuk/thip/user/application/service/UserFollowServiceTest.java b/src/test/java/konkuk/thip/user/application/service/UserFollowServiceTest.java index 706bba7ad..1d0c41878 100644 --- a/src/test/java/konkuk/thip/user/application/service/UserFollowServiceTest.java +++ b/src/test/java/konkuk/thip/user/application/service/UserFollowServiceTest.java @@ -1,6 +1,5 @@ package konkuk.thip.user.application.service; -import konkuk.thip.common.entity.StatusType; import konkuk.thip.common.exception.BusinessException; import konkuk.thip.user.application.port.in.dto.UserFollowCommand; import konkuk.thip.user.application.port.out.FollowingCommandPort; @@ -40,44 +39,33 @@ void setUp() { class Follow { @Test - @DisplayName("기존 inactive row가 존재하면 active로 변경 + followerCount 증가") - void activate_existingFollowing() { + @DisplayName("팔로우 관계가 이미 존재하면 예외 발생") + void follow_alreadyExists() { // given Long userId = 1L, targetUserId = 2L; - Following inactiveFollowing = Following.builder() - .id(10L) - .userId(userId) - .followingUserId(targetUserId) - .status(StatusType.INACTIVE) - .build(); - - User user = createUserWithFollowingCount(0); - + Following existing = Following.withoutId(userId, targetUserId); when(followingCommandPort.findByUserIdAndTargetUserId(userId, targetUserId)) - .thenReturn(Optional.of(inactiveFollowing)); + .thenReturn(Optional.of(existing)); + + User user = createUserWithFollowerCount(0); when(userCommandPort.findById(targetUserId)).thenReturn(user); UserFollowCommand command = new UserFollowCommand(userId, targetUserId, true); - // when - Boolean result = userFollowService.changeFollowingState(command); - // then - assertThat(result).isTrue(); - assertThat(inactiveFollowing.getStatus()).isEqualTo(StatusType.ACTIVE); - assertThat(user.getFollowerCount()).isEqualTo(1); // followerCount 증가 확인 - verify(followingCommandPort).deleteFollowing(inactiveFollowing, user); + assertThatThrownBy(() -> userFollowService.changeFollowingState(command)) + .isInstanceOf(BusinessException.class); } @Test - @DisplayName("팔로우 관계가 존재하지 않으면 새로 생성 + followerCount 증가") - void create_newFollowing() { + @DisplayName("팔로우 관계가 없으면 새로 생성 + followerCount 증가") + void follow_newRelation() { // given Long userId = 1L, targetUserId = 2L; when(followingCommandPort.findByUserIdAndTargetUserId(userId, targetUserId)) .thenReturn(Optional.empty()); - User user = createUserWithFollowingCount(0); + User user = createUserWithFollowerCount(0); when(userCommandPort.findById(targetUserId)).thenReturn(user); UserFollowCommand command = new UserFollowCommand(userId, targetUserId, true); @@ -87,15 +75,13 @@ void create_newFollowing() { // then assertThat(result).isTrue(); - assertThat(user.getFollowerCount()).isEqualTo(1); // followerCount 증가 확인 + assertThat(user.getFollowerCount()).isEqualTo(1); // followerCount 증가 ArgumentCaptor captor = ArgumentCaptor.forClass(Following.class); verify(followingCommandPort).save(captor.capture(), eq(user)); - Following saved = captor.getValue(); assertThat(saved.getUserId()).isEqualTo(userId); assertThat(saved.getFollowingUserId()).isEqualTo(targetUserId); - assertThat(saved.getStatus()).isEqualTo(StatusType.ACTIVE); } } @@ -104,21 +90,16 @@ void create_newFollowing() { class Unfollow { @Test - @DisplayName("active row가 존재하면 inactive로 변경 + followerCount 감소") - void deactivate_existingFollowing() { + @DisplayName("팔로우 관계가 존재하면 삭제 + followerCount 감소") + void unfollow_existingRelation() { // given Long userId = 1L, targetUserId = 2L; - Following activeFollowing = Following.builder() - .id(10L) - .userId(userId) - .followingUserId(targetUserId) - .status(StatusType.ACTIVE) - .build(); + Following existing = Following.withoutId(userId, targetUserId); - User user = createUserWithFollowingCount(1); + User user = createUserWithFollowerCount(1); when(followingCommandPort.findByUserIdAndTargetUserId(userId, targetUserId)) - .thenReturn(Optional.of(activeFollowing)); + .thenReturn(Optional.of(existing)); when(userCommandPort.findById(targetUserId)).thenReturn(user); UserFollowCommand command = new UserFollowCommand(userId, targetUserId, false); @@ -128,9 +109,8 @@ void deactivate_existingFollowing() { // then assertThat(result).isFalse(); - assertThat(activeFollowing.getStatus()).isEqualTo(StatusType.INACTIVE); - assertThat(user.getFollowerCount()).isEqualTo(0); // followerCount 감소 확인 - verify(followingCommandPort).deleteFollowing(activeFollowing, user); + assertThat(user.getFollowerCount()).isEqualTo(0); // followerCount 감소 + verify(followingCommandPort).deleteFollowing(existing, user); } @Test @@ -143,7 +123,7 @@ void unfollow_withoutRelation() { UserFollowCommand command = new UserFollowCommand(userId, targetUserId, false); - // when & then + // then assertThatThrownBy(() -> userFollowService.changeFollowingState(command)) .isInstanceOf(BusinessException.class) .hasMessageContaining(USER_ALREADY_UNFOLLOWED.getMessage()); @@ -161,9 +141,9 @@ void cannot_follow_self() { .hasMessageContaining(USER_CANNOT_FOLLOW_SELF.getMessage()); } - private User createUserWithFollowingCount(int count) { + private User createUserWithFollowerCount(int count) { return User.builder() - .id(1L) + .id(100L) .nickname("tester") .userRole("USER") .oauth2Id("oauth-id") From 752f1c39607964055d97893f2b2ec101941d1f43 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Thu, 31 Jul 2025 01:55:37 +0900 Subject: [PATCH 12/14] =?UTF-8?q?[refactor]=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=EC=97=90=20RecentSearchType=20enum=20=EC=A3=BC=EC=9E=85=20(#11?= =?UTF-8?q?4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../out/jpa/RecentSearchJpaEntity.java | 4 +-- ...{SearchType.java => RecentSearchType.java} | 14 ++++----- .../recentSearch/domain/RecentSearch.java | 5 +-- .../recentSearch/domain/RecentSearchType.java | 31 ------------------- 4 files changed, 12 insertions(+), 42 deletions(-) rename src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/{SearchType.java => RecentSearchType.java} (64%) delete mode 100644 src/main/java/konkuk/thip/recentSearch/domain/RecentSearchType.java diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java b/src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java index 44cd1d21a..15b551e4f 100644 --- a/src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java +++ b/src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java @@ -25,7 +25,7 @@ public class RecentSearchJpaEntity extends BaseJpaEntity { @Enumerated(EnumType.STRING) @Column(nullable = false) - private SearchType type; + private RecentSearchType type; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") @@ -33,7 +33,7 @@ public class RecentSearchJpaEntity extends BaseJpaEntity { public void updateFrom(RecentSearch recentSearch) { this.searchTerm = recentSearch.getSearchTerm(); - this.type = SearchType.from(recentSearch.getType()); + this.type = recentSearch.getType(); this.setModifiedAt(recentSearch.getModifiedAt()); } } \ No newline at end of file diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/SearchType.java b/src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchType.java similarity index 64% rename from src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/SearchType.java rename to src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchType.java index 269347de7..2a301c3ae 100644 --- a/src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/SearchType.java +++ b/src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchType.java @@ -7,21 +7,21 @@ import static konkuk.thip.common.exception.code.ErrorCode.INVALID_SEARCH_TYPE; @Getter -public enum SearchType { +public enum RecentSearchType { - USER_SEARCH("사용자 검색"), - BOOK_SEARCH("책 검색"), - ROOM_SEARCH("방 검색"), + USER_SEARCH("USER"), + BOOK_SEARCH("BOOK"), + ROOM_SEARCH("ROOM"), ; private final String searchType; - SearchType(String searchType) { + RecentSearchType(String searchType) { this.searchType = searchType; } - public static SearchType from(String searchType) { - for (SearchType type : SearchType.values()) { + public static RecentSearchType from(String searchType) { + for (RecentSearchType type : RecentSearchType.values()) { if (type.getSearchType().equals(searchType)) { return type; } diff --git a/src/main/java/konkuk/thip/recentSearch/domain/RecentSearch.java b/src/main/java/konkuk/thip/recentSearch/domain/RecentSearch.java index 19dabeca7..0a9dfd80d 100644 --- a/src/main/java/konkuk/thip/recentSearch/domain/RecentSearch.java +++ b/src/main/java/konkuk/thip/recentSearch/domain/RecentSearch.java @@ -1,6 +1,7 @@ package konkuk.thip.recentSearch.domain; import konkuk.thip.common.entity.BaseDomainEntity; +import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchType; import lombok.Getter; import lombok.experimental.SuperBuilder; @@ -14,11 +15,11 @@ public class RecentSearch extends BaseDomainEntity { private String searchTerm; - private String type; + private RecentSearchType type; private Long userId; - public static RecentSearch withoutId(String searchTerm, String type, Long userId) { + public static RecentSearch withoutId(String searchTerm, RecentSearchType type, Long userId) { return RecentSearch.builder() .searchTerm(searchTerm) .type(type) diff --git a/src/main/java/konkuk/thip/recentSearch/domain/RecentSearchType.java b/src/main/java/konkuk/thip/recentSearch/domain/RecentSearchType.java deleted file mode 100644 index 6dd504000..000000000 --- a/src/main/java/konkuk/thip/recentSearch/domain/RecentSearchType.java +++ /dev/null @@ -1,31 +0,0 @@ -package konkuk.thip.recentSearch.domain; - -import konkuk.thip.common.exception.InvalidStateException; -import lombok.Getter; - -import static konkuk.thip.common.exception.code.ErrorCode.INVALID_SEARCH_TYPE; - -@Getter -public enum RecentSearchType { - - USER_SEARCH("USER","사용자 검색"), - BOOK_SEARCH("BOOK","책 검색"), - ROOM_SEARCH("ROOM","방 검색"); - - private final String param; - private final String type; - - RecentSearchType(String param, String type) { - this.param = param; - this.type = type; - } - - public static RecentSearchType from(String param) { - for (RecentSearchType recentSearchType : RecentSearchType.values()) { - if (recentSearchType.getParam().equals(param)) { - return recentSearchType; - } - } - throw new InvalidStateException(INVALID_SEARCH_TYPE); - } -} From 8638179e18ad685e7d7f8716ad86c18a7669f4f1 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Thu, 31 Jul 2025 01:56:16 +0900 Subject: [PATCH 13/14] =?UTF-8?q?[refactor]=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=8F=99=EC=A0=81=EC=9C=BC=EB=A1=9C=20LIM?= =?UTF-8?q?IT,=20type=20=EA=B2=B0=EC=A0=95=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#114)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../book/application/service/BookSearchService.java | 4 ++-- .../adapter/out/mapper/RecentSearchMapper.java | 5 ++--- .../RecentSearchQueryPersistenceAdapter.java | 11 +++++------ .../repository/RecentSearchQueryRepository.java | 6 +++--- .../repository/RecentSearchQueryRepositoryImpl.java | 10 +++++----- .../application/port/out/RecentSearchQueryPort.java | 5 +++-- .../application/service/RecentSearchGetService.java | 9 +++++---- .../service/manager/RecentSearchCreateManager.java | 9 ++++----- .../user/application/service/UserSearchService.java | 3 ++- .../book/adapter/in/web/BookQueryControllerTest.java | 6 +++--- .../adapter/in/web/RecentSearchDeleteApiTest.java | 4 ++-- .../adapter/in/web/RecentSearchGetApiTest.java | 4 ++-- .../thip/user/adapter/in/web/UserSearchApiTest.java | 4 ++-- 13 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/main/java/konkuk/thip/book/application/service/BookSearchService.java b/src/main/java/konkuk/thip/book/application/service/BookSearchService.java index 8e5b28b2a..cb1848a5e 100644 --- a/src/main/java/konkuk/thip/book/application/service/BookSearchService.java +++ b/src/main/java/konkuk/thip/book/application/service/BookSearchService.java @@ -28,7 +28,7 @@ import static konkuk.thip.book.adapter.out.api.naver.NaverApiUtil.PAGE_SIZE; import static konkuk.thip.common.exception.code.ErrorCode.*; -import static konkuk.thip.recentSearch.adapter.out.jpa.SearchType.BOOK_SEARCH; +import static konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchType.BOOK_SEARCH; @Service @RequiredArgsConstructor @@ -69,7 +69,7 @@ public NaverBookParseResult searchBooks(String keyword, int page, Long userId) { //최근검색어 추가 RecentSearch recentSearch = RecentSearch.builder() .searchTerm(keyword) - .type(BOOK_SEARCH.getSearchType()) + .type(BOOK_SEARCH) .userId(userId) .build(); recentSearchCommandPort.save(recentSearch); diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/out/mapper/RecentSearchMapper.java b/src/main/java/konkuk/thip/recentSearch/adapter/out/mapper/RecentSearchMapper.java index 8085d7856..e21f75cf1 100644 --- a/src/main/java/konkuk/thip/recentSearch/adapter/out/mapper/RecentSearchMapper.java +++ b/src/main/java/konkuk/thip/recentSearch/adapter/out/mapper/RecentSearchMapper.java @@ -1,7 +1,6 @@ package konkuk.thip.recentSearch.adapter.out.mapper; import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchJpaEntity; -import konkuk.thip.recentSearch.adapter.out.jpa.SearchType; import konkuk.thip.recentSearch.domain.RecentSearch; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import org.springframework.stereotype.Component; @@ -12,7 +11,7 @@ public class RecentSearchMapper { public RecentSearchJpaEntity toJpaEntity(RecentSearch recentSearch, UserJpaEntity userJpaEntity) { return RecentSearchJpaEntity.builder() .searchTerm(recentSearch.getSearchTerm()) - .type(SearchType.from(recentSearch.getType())) + .type(recentSearch.getType()) .userJpaEntity(userJpaEntity) .build(); } @@ -21,7 +20,7 @@ public RecentSearch toDomainEntity(RecentSearchJpaEntity recentSearchJpaEntity) return RecentSearch.builder() .id(recentSearchJpaEntity.getRecentSearchId()) .searchTerm(recentSearchJpaEntity.getSearchTerm()) - .type(recentSearchJpaEntity.getType().getSearchType()) + .type(recentSearchJpaEntity.getType()) .userId(recentSearchJpaEntity.getUserJpaEntity().getUserId()) .createdAt(recentSearchJpaEntity.getCreatedAt()) .modifiedAt(recentSearchJpaEntity.getModifiedAt()) diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchQueryPersistenceAdapter.java b/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchQueryPersistenceAdapter.java index f0e33e38b..3f049e8ac 100644 --- a/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchQueryPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchQueryPersistenceAdapter.java @@ -1,5 +1,6 @@ package konkuk.thip.recentSearch.adapter.out.persistence; +import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchType; import konkuk.thip.recentSearch.adapter.out.mapper.RecentSearchMapper; import konkuk.thip.recentSearch.adapter.out.persistence.repository.RecentSearchJpaRepository; import konkuk.thip.recentSearch.application.port.out.RecentSearchQueryPort; @@ -10,8 +11,6 @@ import java.util.List; import java.util.Optional; -import static konkuk.thip.recentSearch.adapter.out.jpa.SearchType.USER_SEARCH; - @Repository @RequiredArgsConstructor public class RecentSearchQueryPersistenceAdapter implements RecentSearchQueryPort { @@ -20,14 +19,14 @@ public class RecentSearchQueryPersistenceAdapter implements RecentSearchQueryPor private final RecentSearchMapper recentSearchMapper; @Override - public Optional findRecentSearchByKeywordAndUserId(String keyword, Long userId) { - return recentSearchJpaRepository.findBySearchTermAndTypeAndUserId(keyword, USER_SEARCH, userId) + public Optional findRecentSearchByKeywordAndUserId(String keyword, Long userId, RecentSearchType type) { + return recentSearchJpaRepository.findBySearchTermAndTypeAndUserId(keyword, type, userId) .map(recentSearchMapper::toDomainEntity); } @Override - public List findRecentSearchesByTypeAndUserId(String type, Long userId) { - return recentSearchJpaRepository.findByTypeAndUserId(type, userId) + public List findRecentSearchesByTypeAndUserId(RecentSearchType type, Long userId, int limit) { + return recentSearchJpaRepository.findByTypeAndUserId(type, userId, limit) .stream() .map(recentSearchMapper::toDomainEntity) .toList(); diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepository.java b/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepository.java index 4825a4115..309713668 100644 --- a/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepository.java +++ b/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepository.java @@ -1,13 +1,13 @@ package konkuk.thip.recentSearch.adapter.out.persistence.repository; import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchJpaEntity; -import konkuk.thip.recentSearch.adapter.out.jpa.SearchType; +import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchType; import java.util.List; import java.util.Optional; public interface RecentSearchQueryRepository { - Optional findBySearchTermAndTypeAndUserId(String searchTerm, SearchType type, Long userId); + Optional findBySearchTermAndTypeAndUserId(String searchTerm, RecentSearchType type, Long userId); - List findByTypeAndUserId(String type, Long userId); + List findByTypeAndUserId(RecentSearchType type, Long userId, int limit); } diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepositoryImpl.java b/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepositoryImpl.java index aabf97d17..7bd65aef4 100644 --- a/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepositoryImpl.java +++ b/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchQueryRepositoryImpl.java @@ -2,7 +2,7 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchJpaEntity; -import konkuk.thip.recentSearch.adapter.out.jpa.SearchType; +import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchType; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -19,7 +19,7 @@ public class RecentSearchQueryRepositoryImpl implements RecentSearchQueryReposit private final JPAQueryFactory queryFactory; @Override - public Optional findBySearchTermAndTypeAndUserId(String searchTerm, SearchType type, Long userId) { + public Optional findBySearchTermAndTypeAndUserId(String searchTerm, RecentSearchType type, Long userId) { RecentSearchJpaEntity result = queryFactory .selectFrom(recentSearchJpaEntity) .where( @@ -34,16 +34,16 @@ public Optional findBySearchTermAndTypeAndUserId(String s } @Override - public List findByTypeAndUserId(String type, Long userId) { + public List findByTypeAndUserId(RecentSearchType type, Long userId, int limit) { return queryFactory .selectFrom(recentSearchJpaEntity) .where( - recentSearchJpaEntity.type.eq(SearchType.from(type)), + recentSearchJpaEntity.type.eq(type), recentSearchJpaEntity.userJpaEntity.userId.eq(userId), recentSearchJpaEntity.status.eq(ACTIVE) ) .orderBy(recentSearchJpaEntity.modifiedAt.desc()) - .limit(5) + .limit(limit) .fetch(); } } diff --git a/src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchQueryPort.java b/src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchQueryPort.java index 7734bbd49..04c09d662 100644 --- a/src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchQueryPort.java +++ b/src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchQueryPort.java @@ -1,5 +1,6 @@ package konkuk.thip.recentSearch.application.port.out; +import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchType; import konkuk.thip.recentSearch.domain.RecentSearch; import java.util.List; @@ -7,8 +8,8 @@ public interface RecentSearchQueryPort { - Optional findRecentSearchByKeywordAndUserId(String keyword, Long userId); + Optional findRecentSearchByKeywordAndUserId(String keyword, Long userId, RecentSearchType type); - List findRecentSearchesByTypeAndUserId(String type, Long userId); + List findRecentSearchesByTypeAndUserId(RecentSearchType type, Long userId, int limit); } diff --git a/src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchGetService.java b/src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchGetService.java index 31d66a54a..33e4b8962 100644 --- a/src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchGetService.java +++ b/src/main/java/konkuk/thip/recentSearch/application/service/RecentSearchGetService.java @@ -1,11 +1,11 @@ package konkuk.thip.recentSearch.application.service; import konkuk.thip.recentSearch.adapter.in.web.response.RecentSearchGetResponse; +import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchType; import konkuk.thip.recentSearch.application.RecentSearchQueryMapper; import konkuk.thip.recentSearch.application.port.in.RecentSearchGetUseCase; import konkuk.thip.recentSearch.application.port.out.RecentSearchQueryPort; import konkuk.thip.recentSearch.domain.RecentSearch; -import konkuk.thip.recentSearch.domain.RecentSearchType; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,13 +17,14 @@ public class RecentSearchGetService implements RecentSearchGetUseCase { private final RecentSearchQueryPort recentSearchQueryPort; - private final RecentSearchQueryMapper recentSearchQueryMapper; + private static final int MAX_RECENT_SEARCHES = 5; + @Transactional(readOnly = true) public RecentSearchGetResponse getRecentSearches(String typeParam, Long userId) { - RecentSearchType recentSearchType = RecentSearchType.from(typeParam); - List recentSearchList = recentSearchQueryPort.findRecentSearchesByTypeAndUserId(recentSearchType.getType(), userId); + RecentSearchType type = RecentSearchType.from(typeParam); + List recentSearchList = recentSearchQueryPort.findRecentSearchesByTypeAndUserId(type, userId, MAX_RECENT_SEARCHES); return RecentSearchGetResponse.of( recentSearchQueryMapper.toResponseList(recentSearchList) diff --git a/src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java b/src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java index a8ab988e8..3a06c3e3d 100644 --- a/src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java +++ b/src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java @@ -1,5 +1,6 @@ package konkuk.thip.recentSearch.application.service.manager; +import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchType; import konkuk.thip.recentSearch.application.port.out.RecentSearchCommandPort; import konkuk.thip.recentSearch.application.port.out.RecentSearchQueryPort; import konkuk.thip.recentSearch.domain.RecentSearch; @@ -12,15 +13,13 @@ @RequiredArgsConstructor public class RecentSearchCreateManager { - private static final String USER_SEARCH_TERM = "사용자 검색"; - private final RecentSearchCommandPort recentSearchCommandPort; private final RecentSearchQueryPort recentSearchQueryPort; - public void saveRecentSearchByUser(Long userId, String keyword) { + public void saveRecentSearchByUser(Long userId, String keyword, RecentSearchType type) { // 동일 조건 (userId + keyword + type) 검색 기록이 이미 있는지 확인 - recentSearchQueryPort.findRecentSearchByKeywordAndUserId(keyword, userId) + recentSearchQueryPort.findRecentSearchByKeywordAndUserId(keyword, userId, type) .ifPresentOrElse( existingRecentSearch -> { // 이미 존재하면 createdAt만 갱신 @@ -29,7 +28,7 @@ public void saveRecentSearchByUser(Long userId, String keyword) { }, () -> { // 없으면 새로 저장 - RecentSearch userRecentSearch = RecentSearch.withoutId(keyword, USER_SEARCH_TERM, userId); + RecentSearch userRecentSearch = RecentSearch.withoutId(keyword, type, userId); recentSearchCommandPort.save(userRecentSearch); } ); diff --git a/src/main/java/konkuk/thip/user/application/service/UserSearchService.java b/src/main/java/konkuk/thip/user/application/service/UserSearchService.java index f93335e7f..e2aed557e 100644 --- a/src/main/java/konkuk/thip/user/application/service/UserSearchService.java +++ b/src/main/java/konkuk/thip/user/application/service/UserSearchService.java @@ -1,5 +1,6 @@ package konkuk.thip.user.application.service; +import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchType; import konkuk.thip.recentSearch.application.service.manager.RecentSearchCreateManager; import konkuk.thip.user.adapter.in.web.response.UserSearchResponse; import konkuk.thip.user.application.mapper.UserQueryMapper; @@ -29,7 +30,7 @@ public UserSearchResponse searchUsers(UserSearchQuery userSearchQuery) { )); // 최근 검색어 저장 - recentSearchCreateManager.saveRecentSearchByUser(userSearchQuery.userId(), userSearchQuery.keyword()); + recentSearchCreateManager.saveRecentSearchByUser(userSearchQuery.userId(), userSearchQuery.keyword(), RecentSearchType.USER_SEARCH); return UserSearchResponse.of(userDtoList); } diff --git a/src/test/java/konkuk/thip/book/adapter/in/web/BookQueryControllerTest.java b/src/test/java/konkuk/thip/book/adapter/in/web/BookQueryControllerTest.java index 142d5dc29..52794d323 100644 --- a/src/test/java/konkuk/thip/book/adapter/in/web/BookQueryControllerTest.java +++ b/src/test/java/konkuk/thip/book/adapter/in/web/BookQueryControllerTest.java @@ -4,7 +4,7 @@ import konkuk.thip.common.security.util.JwtUtil; import konkuk.thip.common.util.TestEntityFactory; import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchJpaEntity; -import konkuk.thip.recentSearch.adapter.out.jpa.SearchType; +import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchType; import konkuk.thip.recentSearch.adapter.out.persistence.repository.RecentSearchJpaRepository; import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; @@ -64,7 +64,7 @@ void setUp() { RecentSearchJpaEntity recentSearch = recentSearchJpaRepository.save(RecentSearchJpaEntity.builder() .searchTerm("테스트검색어") - .type(SearchType.BOOK_SEARCH) + .type(RecentSearchType.BOOK_SEARCH) .userJpaEntity(user) .build()); @@ -186,7 +186,7 @@ void searchBooks_savesRecentSearch() throws Exception { assertThat(recentSearch).isNotNull(); assertThat(recentSearch.getSearchTerm()).isEqualTo(keyword); - assertThat(recentSearch.getType()).isEqualTo(SearchType.BOOK_SEARCH); + assertThat(recentSearch.getType()).isEqualTo(RecentSearchType.BOOK_SEARCH); assertThat(recentSearch.getUserJpaEntity().getUserId()).isEqualTo(user.getUserId()); } } diff --git a/src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchDeleteApiTest.java b/src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchDeleteApiTest.java index db55ed4ae..54dc00a47 100644 --- a/src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchDeleteApiTest.java +++ b/src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchDeleteApiTest.java @@ -3,7 +3,7 @@ import konkuk.thip.common.exception.code.ErrorCode; import konkuk.thip.common.util.TestEntityFactory; import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchJpaEntity; -import konkuk.thip.recentSearch.adapter.out.jpa.SearchType; +import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchType; import konkuk.thip.recentSearch.adapter.out.persistence.repository.RecentSearchJpaRepository; import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; @@ -66,7 +66,7 @@ void setUp() { RecentSearchJpaEntity entity = recentSearchJpaRepository.save( RecentSearchJpaEntity.builder() .searchTerm("삭제테스트") - .type(SearchType.USER_SEARCH) + .type(RecentSearchType.USER_SEARCH) .userJpaEntity(currentUser) .build() ); diff --git a/src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchGetApiTest.java b/src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchGetApiTest.java index 892f44aaa..6abea1462 100644 --- a/src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchGetApiTest.java +++ b/src/test/java/konkuk/thip/recentSearch/adapter/in/web/RecentSearchGetApiTest.java @@ -3,7 +3,7 @@ import jakarta.persistence.EntityManager; import konkuk.thip.common.util.TestEntityFactory; import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchJpaEntity; -import konkuk.thip.recentSearch.adapter.out.jpa.SearchType; +import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchType; import konkuk.thip.recentSearch.adapter.out.persistence.repository.RecentSearchJpaRepository; import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; @@ -65,7 +65,7 @@ void setUp() { RecentSearchJpaEntity saved = recentSearchJpaRepository.save( RecentSearchJpaEntity.builder() .searchTerm("검색어" + i) - .type(SearchType.USER_SEARCH) + .type(RecentSearchType.USER_SEARCH) .userJpaEntity(currentUser) .build() ); diff --git a/src/test/java/konkuk/thip/user/adapter/in/web/UserSearchApiTest.java b/src/test/java/konkuk/thip/user/adapter/in/web/UserSearchApiTest.java index 379d090ce..de764e02d 100644 --- a/src/test/java/konkuk/thip/user/adapter/in/web/UserSearchApiTest.java +++ b/src/test/java/konkuk/thip/user/adapter/in/web/UserSearchApiTest.java @@ -2,7 +2,7 @@ import konkuk.thip.common.util.TestEntityFactory; import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchJpaEntity; -import konkuk.thip.recentSearch.adapter.out.jpa.SearchType; +import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchType; import konkuk.thip.recentSearch.adapter.out.persistence.repository.RecentSearchJpaRepository; import konkuk.thip.user.adapter.out.jpa.AliasJpaEntity; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; @@ -88,7 +88,7 @@ void searchUsersAndSaveRecentSearch() throws Exception { assertEquals(1, recentSearches.size()); RecentSearchJpaEntity saved = recentSearches.get(0); assertEquals(keyword, saved.getSearchTerm()); - assertEquals(SearchType.USER_SEARCH, saved.getType()); + assertEquals(RecentSearchType.USER_SEARCH, saved.getType()); assertEquals(currentUserId, saved.getUserJpaEntity().getUserId()); } } \ No newline at end of file From 379df8d9a6b25af4c8a0a5d2f808dac250713174 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Thu, 31 Jul 2025 03:09:51 +0900 Subject: [PATCH 14/14] =?UTF-8?q?[refactor]=20modifiedAt=EC=9D=84=20?= =?UTF-8?q?=EA=B0=95=EC=A0=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20jpql=20=EC=82=AC=EC=9A=A9=20(#114?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thip/common/entity/BaseDomainEntity.java | 9 --------- .../konkuk/thip/common/entity/BaseJpaEntity.java | 9 --------- .../thip/feed/adapter/out/jpa/FeedJpaEntity.java | 7 ------- .../adapter/out/jpa/RecentSearchJpaEntity.java | 7 ------- .../RecentSearchCommandPersistenceAdapter.java | 10 ++++------ .../repository/RecentSearchJpaRepository.java | 6 ++++++ .../port/out/RecentSearchCommandPort.java | 2 +- .../service/manager/RecentSearchCreateManager.java | 14 ++------------ .../thip/recentSearch/domain/RecentSearch.java | 6 ------ 9 files changed, 13 insertions(+), 57 deletions(-) diff --git a/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java b/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java index e5bc93deb..613f2f07c 100644 --- a/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java +++ b/src/main/java/konkuk/thip/common/entity/BaseDomainEntity.java @@ -1,7 +1,6 @@ package konkuk.thip.common.entity; import lombok.Getter; -import lombok.Setter; import lombok.experimental.SuperBuilder; import java.time.LocalDateTime; @@ -12,16 +11,8 @@ public class BaseDomainEntity { private LocalDateTime createdAt; - @Setter private LocalDateTime modifiedAt; private StatusType status; - protected void changeStatus() { - if (this.status == StatusType.ACTIVE) { - this.status = StatusType.INACTIVE; - } else { - this.status = StatusType.ACTIVE; - } - } } diff --git a/src/main/java/konkuk/thip/common/entity/BaseJpaEntity.java b/src/main/java/konkuk/thip/common/entity/BaseJpaEntity.java index 4d8b06bdb..b1f619ef1 100644 --- a/src/main/java/konkuk/thip/common/entity/BaseJpaEntity.java +++ b/src/main/java/konkuk/thip/common/entity/BaseJpaEntity.java @@ -5,10 +5,8 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; -import com.google.common.annotations.VisibleForTesting; import jakarta.persistence.*; import lombok.Getter; -import lombok.Setter; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @@ -27,7 +25,6 @@ public abstract class BaseJpaEntity { @Column(name = "created_at",nullable = false, updatable = false) private LocalDateTime createdAt; - @Setter @LastModifiedDate @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonSerialize(using = LocalDateSerializer.class) @@ -35,13 +32,7 @@ public abstract class BaseJpaEntity { @Column(name = "modified_at",nullable = false) private LocalDateTime modifiedAt; - @Setter @Enumerated(EnumType.STRING) @Column(nullable = false) private StatusType status = StatusType.ACTIVE; - - @VisibleForTesting - protected void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } } diff --git a/src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java b/src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java index 7580d422f..cd184408e 100644 --- a/src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java +++ b/src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java @@ -1,7 +1,6 @@ package konkuk.thip.feed.adapter.out.jpa; -import com.google.common.annotations.VisibleForTesting; import jakarta.persistence.*; import konkuk.thip.book.adapter.out.jpa.BookJpaEntity; import konkuk.thip.feed.domain.Feed; @@ -12,7 +11,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; import java.util.List; @Entity @@ -51,9 +49,4 @@ public void updateFrom(Feed feed) { this.likeCount = feed.getLikeCount(); this.commentCount = feed.getCommentCount(); } - - @VisibleForTesting - public void setCreatedAt(LocalDateTime newCreatedAt) { - super.setCreatedAt(newCreatedAt); - } } \ No newline at end of file diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java b/src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java index 15b551e4f..adc701bcf 100644 --- a/src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java +++ b/src/main/java/konkuk/thip/recentSearch/adapter/out/jpa/RecentSearchJpaEntity.java @@ -3,7 +3,6 @@ import jakarta.persistence.*; import konkuk.thip.common.entity.BaseJpaEntity; -import konkuk.thip.recentSearch.domain.RecentSearch; import konkuk.thip.user.adapter.out.jpa.UserJpaEntity; import lombok.*; @@ -30,10 +29,4 @@ public class RecentSearchJpaEntity extends BaseJpaEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private UserJpaEntity userJpaEntity; - - public void updateFrom(RecentSearch recentSearch) { - this.searchTerm = recentSearch.getSearchTerm(); - this.type = recentSearch.getType(); - this.setModifiedAt(recentSearch.getModifiedAt()); - } } \ No newline at end of file diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java b/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java index 693b95fd3..d57ca22a4 100644 --- a/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java +++ b/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java @@ -52,11 +52,9 @@ public void delete(Long id) { } @Override - public void update(RecentSearch recentSearch) { - RecentSearchJpaEntity recentSearchJpaEntity = recentSearchJpaRepository.findById(recentSearch.getId()) - .orElseThrow(() -> new EntityNotFoundException(RECENT_SEARCH_NOT_FOUND)); - - recentSearchJpaEntity.updateFrom(recentSearch); - recentSearchJpaRepository.save(recentSearchJpaEntity); + public void touch(RecentSearch recentSearch) { + recentSearchJpaRepository.updateModifiedAt(recentSearch.getId()); } + + } diff --git a/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchJpaRepository.java b/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchJpaRepository.java index 3e464b006..71a707798 100644 --- a/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchJpaRepository.java +++ b/src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/repository/RecentSearchJpaRepository.java @@ -2,8 +2,14 @@ import konkuk.thip.recentSearch.adapter.out.jpa.RecentSearchJpaEntity; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository public interface RecentSearchJpaRepository extends JpaRepository, RecentSearchQueryRepository { + @Modifying + @Query("UPDATE RecentSearchJpaEntity r SET r.modifiedAt = CURRENT_TIMESTAMP WHERE r.recentSearchId = :recentSearchId") + void updateModifiedAt(@Param("recentSearchId") Long recentSearchId); } diff --git a/src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchCommandPort.java b/src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchCommandPort.java index b58af450a..8c2d5e09d 100644 --- a/src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchCommandPort.java +++ b/src/main/java/konkuk/thip/recentSearch/application/port/out/RecentSearchCommandPort.java @@ -20,5 +20,5 @@ default RecentSearch getByIdOrThrow(Long id) { void save(RecentSearch recentSearch); void delete(Long id); - void update(RecentSearch recentSearch); + void touch(RecentSearch recentSearch); // modifiedAt 갱신용 메서드 } diff --git a/src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java b/src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java index 3a06c3e3d..ee0c51b23 100644 --- a/src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java +++ b/src/main/java/konkuk/thip/recentSearch/application/service/manager/RecentSearchCreateManager.java @@ -7,8 +7,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import java.time.LocalDateTime; - @Service @RequiredArgsConstructor public class RecentSearchCreateManager { @@ -21,16 +19,8 @@ public void saveRecentSearchByUser(Long userId, String keyword, RecentSearchType // 동일 조건 (userId + keyword + type) 검색 기록이 이미 있는지 확인 recentSearchQueryPort.findRecentSearchByKeywordAndUserId(keyword, userId, type) .ifPresentOrElse( - existingRecentSearch -> { - // 이미 존재하면 createdAt만 갱신 - existingRecentSearch.updateModifiedAt(LocalDateTime.now()); - recentSearchCommandPort.update(existingRecentSearch); - }, - () -> { - // 없으면 새로 저장 - RecentSearch userRecentSearch = RecentSearch.withoutId(keyword, type, userId); - recentSearchCommandPort.save(userRecentSearch); - } + recentSearchCommandPort::touch, // 있으면 modifiedAt 갱신 + () -> recentSearchCommandPort.save(RecentSearch.withoutId(keyword, type, userId)) // 없으면 새로 저장 ); } } diff --git a/src/main/java/konkuk/thip/recentSearch/domain/RecentSearch.java b/src/main/java/konkuk/thip/recentSearch/domain/RecentSearch.java index 0a9dfd80d..35d9a8ffc 100644 --- a/src/main/java/konkuk/thip/recentSearch/domain/RecentSearch.java +++ b/src/main/java/konkuk/thip/recentSearch/domain/RecentSearch.java @@ -5,8 +5,6 @@ import lombok.Getter; import lombok.experimental.SuperBuilder; -import java.time.LocalDateTime; - @Getter @SuperBuilder public class RecentSearch extends BaseDomainEntity { @@ -26,8 +24,4 @@ public static RecentSearch withoutId(String searchTerm, RecentSearchType type, L .userId(userId) .build(); } - - public void updateModifiedAt(LocalDateTime localDateTime) { - this.setModifiedAt(localDateTime); - } }