diff --git a/src/main/java/com/listywave/user/application/dto/search/UserSearchResponse.java b/src/main/java/com/listywave/user/application/dto/search/UserSearchResponse.java index 49663e76..4336f562 100644 --- a/src/main/java/com/listywave/user/application/dto/search/UserSearchResponse.java +++ b/src/main/java/com/listywave/user/application/dto/search/UserSearchResponse.java @@ -1,24 +1,53 @@ package com.listywave.user.application.dto.search; import java.util.List; -import lombok.Builder; +import java.util.Map; -@Builder public record UserSearchResponse( - List users, + List users, Long totalCount, Boolean hasNext ) { - public static UserSearchResponse of( - List users, - Long totalCount, - Boolean hasNext + public static UserSearchResponse createWithoutLogin(List userSearchResults, Long totalCount, Boolean hasNext) { + return new UserSearchResponse( + userSearchResults.stream().map(UserDto::from).toList(), + totalCount, + hasNext + ); + } + + public static UserSearchResponse createWithLogin(Map 팔로우_유무, Long totalCount, Boolean hasNext) { + return new UserSearchResponse( + 팔로우_유무.entrySet().stream().map(UserDto::fromEntry).toList(), + totalCount, + hasNext + ); + } + + public record UserDto( + Long id, + String nickname, + String profileImageUrl, + boolean isFollowing // 검색하는 자가 검색 대상인 유저를 팔로우하고 있는 지에 대한 여부입니다. ) { - return UserSearchResponse.builder() - .users(users) - .totalCount(totalCount) - .hasNext(hasNext) - .build(); + + public static UserDto from(UserSearchResult userSearchResult) { + return new UserDto( + userSearchResult.id, + userSearchResult.nickname, + userSearchResult.profileImageUrl, + false + ); + } + + public static UserDto fromEntry(Map.Entry entry) { + return new UserDto( + entry.getKey().id, + entry.getKey().nickname, + entry.getKey().profileImageUrl, + entry.getValue() + ); + } } } diff --git a/src/main/java/com/listywave/user/application/service/UserService.java b/src/main/java/com/listywave/user/application/service/UserService.java index 5c63abcc..55b945ca 100644 --- a/src/main/java/com/listywave/user/application/service/UserService.java +++ b/src/main/java/com/listywave/user/application/service/UserService.java @@ -22,6 +22,10 @@ import com.listywave.user.repository.user.UserRepository; import com.listywave.user.repository.user.elastic.UserElasticRepository; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Pageable; @@ -58,17 +62,25 @@ public UserInfoResponse getUserInfo(Long targetUserId, Long loginUserId) { @Transactional(readOnly = true) public UserSearchResponse searchUser(Long loginUserId, String search, Pageable pageable) { + Slice searchResult = userRepository.findAllBySearch(search, pageable, loginUserId); + Long count = userRepository.countBySearch(search, loginUserId); + if (loginUserId == null) { - return createUserSearchResponse(null, search, pageable); + return UserSearchResponse.createWithoutLogin(searchResult.getContent(), count, searchResult.hasNext()); } - User user = userRepository.getById(loginUserId); - return createUserSearchResponse(user.getId(), search, pageable); - } - private UserSearchResponse createUserSearchResponse(Long loginUserId, String search, Pageable pageable) { - Long count = userRepository.countBySearch(search, loginUserId); - Slice result = userRepository.findAllBySearch(search, pageable, loginUserId); - return UserSearchResponse.of(result.getContent(), count, result.hasNext()); + User 검색하는_유저 = userRepository.getById(loginUserId); + List 검색_결과_유저_ID_리스트 = searchResult.getContent().stream() + .map(UserSearchResult::getId) + .toList(); + Set 검색하는_유저가_팔로우하고_있는_검색_결과_유저_ID_리스트 = followRepository.검색하는_유저가_검색_결과_유저_중_팔로우하고_있는_유저만을_조회한다(검색하는_유저, 검색_결과_유저_ID_리스트); + Map 회원_검색_결과와_팔로우_여부 = searchResult.getContent().stream() + .collect(Collectors.toMap( + Function.identity(), + userSearchResult -> 검색하는_유저가_팔로우하고_있는_검색_결과_유저_ID_리스트.contains(userSearchResult.getId()) + )); + + return UserSearchResponse.createWithLogin(회원_검색_결과와_팔로우_여부, count, searchResult.hasNext()); } public FollowingsResponse getFollowings(Long followerUserId, String search) { diff --git a/src/main/java/com/listywave/user/repository/follow/FollowRepository.java b/src/main/java/com/listywave/user/repository/follow/FollowRepository.java index 68378a85..cbbc0d9e 100644 --- a/src/main/java/com/listywave/user/repository/follow/FollowRepository.java +++ b/src/main/java/com/listywave/user/repository/follow/FollowRepository.java @@ -4,15 +4,24 @@ import com.listywave.user.application.domain.User; import com.listywave.user.repository.follow.custom.CustomFollowRepository; import java.util.List; +import java.util.Set; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface FollowRepository extends JpaRepository, CustomFollowRepository { - List getAllByFollowerUser(User followerUser); + List getAllByFollowerUser(User 팔로우_하는_유저); - List getAllByFollowingUser(User followingUser); + List getAllByFollowingUser(User 팔로우_당하는_유저); - void deleteByFollowingUserAndFollowerUser(User following, User follower); + void deleteByFollowingUserAndFollowerUser(User 팔로우_하는_유저, User 팔로우_당하는_유저); - boolean existsByFollowerUserAndFollowingUser(User followerUser, User followingUser); + boolean existsByFollowerUserAndFollowingUser(User 팔로우_하는_유저, User 팔로우_당하는_유저); + + @Query(""" + select f.followingUser.id + from Follow f + where f.followerUser = :검색하는_유저 and f.followingUser.isDelete = false and f.followingUser.id in :검색_대상_유저_ID_리스트 + """) + Set 검색하는_유저가_검색_결과_유저_중_팔로우하고_있는_유저만을_조회한다(User 검색하는_유저, List 검색_대상_유저_ID_리스트); } diff --git a/src/main/java/com/listywave/user/repository/user/custom/CustomUserRepository.java b/src/main/java/com/listywave/user/repository/user/custom/CustomUserRepository.java index 50c7ca72..e1a21809 100644 --- a/src/main/java/com/listywave/user/repository/user/custom/CustomUserRepository.java +++ b/src/main/java/com/listywave/user/repository/user/custom/CustomUserRepository.java @@ -2,6 +2,7 @@ import com.listywave.user.application.domain.User; import com.listywave.user.application.dto.search.UserSearchResult; +import jakarta.annotation.Nullable; import java.util.List; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -10,9 +11,9 @@ public interface CustomUserRepository { List getRecommendUsers(List myFollowingUsers, User user); - Long countBySearch(String search, Long loginUserId); + Long countBySearch(String search, @Nullable Long loginUserId); - Slice findAllBySearch(String search, Pageable pageable, Long loginUserId); + Slice findAllBySearch(String search, Pageable pageable, @Nullable Long loginUserId); void deleteNDaysAgo(int n); } diff --git a/src/main/java/com/listywave/user/repository/user/custom/impl/CustomUserRepositoryImpl.java b/src/main/java/com/listywave/user/repository/user/custom/impl/CustomUserRepositoryImpl.java index 824e5288..7c981d89 100644 --- a/src/main/java/com/listywave/user/repository/user/custom/impl/CustomUserRepositoryImpl.java +++ b/src/main/java/com/listywave/user/repository/user/custom/impl/CustomUserRepositoryImpl.java @@ -11,6 +11,7 @@ import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.Expressions; import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.annotation.Nullable; import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; @@ -53,7 +54,7 @@ private BooleanExpression userIdNotEqual(User me) { } @Override - public Long countBySearch(String search, Long loginUserId) { + public Long countBySearch(String search, @Nullable Long loginUserId) { if (search.isEmpty()) { return 0L; } @@ -73,7 +74,7 @@ private BooleanExpression userIdNe(Long loginUserId) { } @Override - public Slice findAllBySearch(String search, Pageable pageable, Long loginUserId) { + public Slice findAllBySearch(String search, Pageable pageable, @Nullable Long loginUserId) { if (search.isEmpty()) { return new SliceImpl<>(List.of(), pageable, false); } diff --git a/src/test/java/com/listywave/acceptance/follow/FollowAcceptanceTestHelper.java b/src/test/java/com/listywave/acceptance/follow/FollowAcceptanceTestHelper.java index 57d93256..6b3ec477 100644 --- a/src/test/java/com/listywave/acceptance/follow/FollowAcceptanceTestHelper.java +++ b/src/test/java/com/listywave/acceptance/follow/FollowAcceptanceTestHelper.java @@ -8,56 +8,56 @@ public abstract class FollowAcceptanceTestHelper { - public static ExtractableResponse 팔로우_요청_API(String accessToken, Long userId) { + public static ExtractableResponse 팔로우_요청_API(String 팔로우를_하는_유저의_액세스토큰, Long 팔로우_대상_유저_ID) { return given() - .header(AUTHORIZATION, "Bearer " + accessToken) - .when().post("/follow/{userId}", userId) + .header(AUTHORIZATION, "Bearer " + 팔로우를_하는_유저의_액세스토큰) + .when().post("/follow/{userId}", 팔로우_대상_유저_ID) .then().log().all() .extract(); } - public static ExtractableResponse 팔로우_취소_API(String accessToken, Long userId) { + public static ExtractableResponse 팔로우_취소_API(String 팔로우_취소를_하는_유저의_액세스토큰, Long 팔로우_취소_대상_유저_ID) { return given() - .header(AUTHORIZATION, "Bearer " + accessToken) - .when().delete("/follow/{userId}", userId) + .header(AUTHORIZATION, "Bearer " + 팔로우_취소를_하는_유저의_액세스토큰) + .when().delete("/follow/{userId}", 팔로우_취소_대상_유저_ID) .then().log().all() .extract(); } - public static ExtractableResponse 팔로워_목록_조회_API(Long userId) { + public static ExtractableResponse 팔로워_목록_조회_API(Long 조회하려는_유저_ID) { return given() - .when().get("/users/{userId}/followers", userId) + .when().get("/users/{userId}/followers", 조회하려는_유저_ID) .then().log().all() .extract(); } - public static ExtractableResponse 팔로워_검색_API(Long userId, String search) { + public static ExtractableResponse 팔로워_검색_API(Long 검색하려는_유저_ID, String 검색어) { return given() - .queryParam("search", search) - .when().get("/users/{userId}/followers", userId) + .queryParam("search", 검색어) + .when().get("/users/{userId}/followers", 검색하려는_유저_ID) .then().log().all() .extract(); } - public static ExtractableResponse 팔로잉_목록_조회_API(Long userId) { + public static ExtractableResponse 팔로잉_목록_조회_API(Long 조회하려는_유저_ID) { return given() - .when().get("/users/{userId}/followings", userId) + .when().get("/users/{userId}/followings", 조회하려는_유저_ID) .then().log().all() .extract(); } - public static ExtractableResponse 팔로잉_검색_API(Long userId, String search) { + public static ExtractableResponse 팔로잉_검색_API(Long 검색하려는_유저_ID, String 검색어) { return given() - .queryParam("search", search) - .when().get("/users/{userId}/followings", userId) + .queryParam("search", 검색어) + .when().get("/users/{userId}/followings", 검색하려는_유저_ID) .then().log().all() .extract(); } - public static ExtractableResponse 팔로워_삭제_API(String accessToken, Long followerId) { + public static ExtractableResponse 팔로워_삭제_API(String 삭제를_수행하려는_유저의_액세스토큰, Long 삭제의_대상이_되는_유저의_ID) { return given() - .header(AUTHORIZATION, "Bearer " + accessToken) - .when().delete("/followers/{userId}", followerId) + .header(AUTHORIZATION, "Bearer " + 삭제를_수행하려는_유저의_액세스토큰) + .when().delete("/followers/{userId}", 삭제의_대상이_되는_유저의_ID) .then().log().all() .extract(); } diff --git a/src/test/java/com/listywave/acceptance/user/UserAcceptanceTest.java b/src/test/java/com/listywave/acceptance/user/UserAcceptanceTest.java index ac5402d3..8f0c9091 100644 --- a/src/test/java/com/listywave/acceptance/user/UserAcceptanceTest.java +++ b/src/test/java/com/listywave/acceptance/user/UserAcceptanceTest.java @@ -26,10 +26,13 @@ import com.listywave.acceptance.common.AcceptanceTest; import com.listywave.list.application.dto.response.ListCreateResponse; +import com.listywave.user.application.domain.User; import com.listywave.user.application.dto.UserInfoResponse; import com.listywave.user.application.dto.UsersRecommendedResponse; import com.listywave.user.application.dto.search.UserSearchResponse; import io.restassured.common.mapper.TypeRef; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -112,11 +115,35 @@ class 회원_검색 { 회원을_저장한다(유진()); // when - var 결과 = 비회원이_사용자_검색(동호.getNickname()).as(UserSearchResponse.class); + UserSearchResponse 결과 = 비회원이_사용자_검색(동호.getNickname()).as(UserSearchResponse.class); // then - assertThat(결과.totalCount()).isOne(); - assertThat(결과.users().get(0).getNickname()).isEqualTo(동호.getNickname()); + assertAll( + () -> assertThat(결과.totalCount()).isOne(), + () -> assertThat(결과.users().get(0).nickname()).isEqualTo(동호.getNickname()), + () -> assertThat(결과.users().get(0).isFollowing()).isFalse() + ); + } + + @Test + void 검색_대상_회원이_내가_현재_팔로우하고_있는_유저인_경우() { + // given + User 동호 = 회원을_저장한다(동호()); + User 정수 = 회원을_저장한다(정수()); + String 정수의_액세스_토큰 = 액세스_토큰을_발급한다(정수); + + 팔로우_요청_API(정수의_액세스_토큰, 동호.getId()); + + // when + ExtractableResponse 응답 = 회원이_사용자_검색(정수의_액세스_토큰, "kdkdhoho"); + UserSearchResponse 결과 = 응답.as(UserSearchResponse.class); + + // then + assertAll( + () -> assertThat(결과.totalCount()).isOne(), + () -> assertThat(결과.users().get(0).id()).isEqualTo(동호.getId()), + () -> assertThat(결과.users().get(0).isFollowing()).isTrue() + ); } @Test