diff --git a/src/main/java/com/listywave/user/application/dto/FollowersResponse.java b/src/main/java/com/listywave/user/application/dto/FollowersResponse.java new file mode 100644 index 00000000..56709835 --- /dev/null +++ b/src/main/java/com/listywave/user/application/dto/FollowersResponse.java @@ -0,0 +1,54 @@ +package com.listywave.user.application.dto; + +import com.listywave.user.application.domain.User; +import java.util.Collections; +import java.util.List; +import lombok.Builder; + +@Builder +public record FollowersResponse( + List followers, + int totalCount, + Long cursorId, + boolean hasNext +) { + + public static FollowersResponse of(List users, int totalCount, boolean hasNext) { + return FollowersResponse.builder() + .followers(FollowerInfo.toList(users)) + .totalCount(totalCount) + .cursorId(users.get(users.size() - 1).getId()) + .hasNext(hasNext) + .build(); + } + + public static FollowersResponse empty() { + return FollowersResponse.builder() + .followers(Collections.emptyList()) + .totalCount(0) + .hasNext(false) + .build(); + } +} + +@Builder +record FollowerInfo( + Long id, + String nickname, + String profileImageUrl +) { + + public static List toList(List users) { + return users.stream() + .map(FollowerInfo::of) + .toList(); + } + + public static FollowerInfo of(User user) { + return FollowerInfo.builder() + .id(user.getId()) + .nickname(user.getNickname()) + .profileImageUrl(user.getProfileImageUrl()) + .build(); + } +} 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 ec0fe8ed..4c1a7893 100644 --- a/src/main/java/com/listywave/user/application/service/UserService.java +++ b/src/main/java/com/listywave/user/application/service/UserService.java @@ -8,6 +8,7 @@ import com.listywave.user.application.domain.User; import com.listywave.user.application.dto.AllUserListsResponse; import com.listywave.user.application.dto.AllUserResponse; +import com.listywave.user.application.dto.FollowersResponse; import com.listywave.user.application.dto.FollowingsResponse; import com.listywave.user.application.dto.RecommendUsersResponse; import com.listywave.user.application.dto.UserInfoResponse; @@ -103,7 +104,26 @@ public void unfollow(Long followingUserId, String accessToken) { followRepository.deleteByFollowingUserAndFollowerUser(followingUser, followerUser); } - + + public FollowersResponse getFollowers(Long userId, int size, int cursorId) { + User followingUser = userRepository.getById(userId); + + List follows = followRepository.findAllByFollowingUser(followingUser, size, cursorId); + List followerUsers = follows.stream() + .map(Follow::getFollowerUser) + .toList(); + + if (followerUsers.isEmpty()) { + return FollowersResponse.empty(); + } + + int totalCount = followRepository.countByFollowingUser(followingUser); + if (followerUsers.size() > size) { + return FollowersResponse.of(followerUsers.subList(0, size), totalCount, true); + } + return FollowersResponse.of(followerUsers, totalCount, false); + } + @Transactional(readOnly = true) public List getRecommendUsers() { List recommendUsers = userRepository.getRecommendUsers(); diff --git a/src/main/java/com/listywave/user/presentation/UserController.java b/src/main/java/com/listywave/user/presentation/UserController.java index 4c7f7597..7078958d 100644 --- a/src/main/java/com/listywave/user/presentation/UserController.java +++ b/src/main/java/com/listywave/user/presentation/UserController.java @@ -5,6 +5,7 @@ import com.listywave.list.application.domain.CategoryType; import com.listywave.user.application.dto.AllUserListsResponse; import com.listywave.user.application.dto.AllUserResponse; +import com.listywave.user.application.dto.FollowersResponse; import com.listywave.user.application.dto.FollowingsResponse; import com.listywave.user.application.dto.RecommendUsersResponse; import com.listywave.user.application.dto.UserInfoResponse; @@ -59,6 +60,16 @@ ResponseEntity getFollowings(@PathVariable(name = "userId") return ResponseEntity.ok(response); } + @GetMapping("/users/{userId}/followers") + ResponseEntity getFollowers( + @PathVariable(name = "userId") Long userId, + @RequestParam(name = "size", defaultValue = "20") int size, + @RequestParam(name = "cursorId", defaultValue = "0") int cursorId + ) { + FollowersResponse response = userService.getFollowers(userId, size, cursorId); + return ResponseEntity.ok(response); + } + @PostMapping("/follow/{userId}") ResponseEntity follow( @PathVariable(value = "userId") Long followingUserId, 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 98968970..f6e38c30 100644 --- a/src/main/java/com/listywave/user/repository/follow/FollowRepository.java +++ b/src/main/java/com/listywave/user/repository/follow/FollowRepository.java @@ -2,12 +2,15 @@ import com.listywave.user.application.domain.Follow; import com.listywave.user.application.domain.User; +import com.listywave.user.repository.follow.custom.CustomFollowRepository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; -public interface FollowRepository extends JpaRepository { +public interface FollowRepository extends JpaRepository, CustomFollowRepository { List getAllByFollowerUser(User user); void deleteByFollowingUserAndFollowerUser(User following, User follower); + + int countByFollowingUser(User user); } diff --git a/src/main/java/com/listywave/user/repository/follow/custom/CustomFollowRepository.java b/src/main/java/com/listywave/user/repository/follow/custom/CustomFollowRepository.java new file mode 100644 index 00000000..62b5d757 --- /dev/null +++ b/src/main/java/com/listywave/user/repository/follow/custom/CustomFollowRepository.java @@ -0,0 +1,10 @@ +package com.listywave.user.repository.follow.custom; + +import com.listywave.user.application.domain.Follow; +import com.listywave.user.application.domain.User; +import java.util.List; + +public interface CustomFollowRepository { + + List findAllByFollowingUser(User user, int size, int cursorId); +} diff --git a/src/main/java/com/listywave/user/repository/follow/custom/impl/CustomFollowRepositoryImpl.java b/src/main/java/com/listywave/user/repository/follow/custom/impl/CustomFollowRepositoryImpl.java new file mode 100644 index 00000000..a5e04189 --- /dev/null +++ b/src/main/java/com/listywave/user/repository/follow/custom/impl/CustomFollowRepositoryImpl.java @@ -0,0 +1,28 @@ +package com.listywave.user.repository.follow.custom.impl; + +import static com.listywave.user.application.domain.QFollow.follow; + +import com.listywave.user.application.domain.Follow; +import com.listywave.user.application.domain.User; +import com.listywave.user.repository.follow.custom.CustomFollowRepository; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class CustomFollowRepositoryImpl implements CustomFollowRepository { + + private final JPAQueryFactory queryFactory; + + @Override + public List findAllByFollowingUser(User followingUser, int size, int cursorId) { + return queryFactory.selectFrom(follow) + .where( + follow.followingUser.eq(followingUser), + follow.followingUser.id.gt(cursorId) + ) + .orderBy(follow.createdDate.desc()) + .limit(size + 1) + .fetch(); + } +}