diff --git a/src/main/java/team/wego/wegobackend/user/application/FollowService.java b/src/main/java/team/wego/wegobackend/user/application/FollowService.java index 7b9847e..e8bb825 100644 --- a/src/main/java/team/wego/wegobackend/user/application/FollowService.java +++ b/src/main/java/team/wego/wegobackend/user/application/FollowService.java @@ -1,18 +1,18 @@ package team.wego.wegobackend.user.application; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import team.wego.wegobackend.notification.application.SseEmitterService; -import team.wego.wegobackend.notification.application.dto.NotificationType; -import team.wego.wegobackend.notification.application.dto.response.NotificationResponse; -import team.wego.wegobackend.notification.domain.Notification; -import team.wego.wegobackend.notification.repository.NotificationRepository; import team.wego.wegobackend.user.application.dto.response.FollowListResponse; import team.wego.wegobackend.user.application.dto.response.FollowResponse; +import team.wego.wegobackend.user.application.dto.response.FollowerListResponse; +import team.wego.wegobackend.user.application.dto.response.WrapperFollowerResponse; import team.wego.wegobackend.user.application.event.FollowEvent; import team.wego.wegobackend.user.domain.Follow; import team.wego.wegobackend.user.domain.User; @@ -34,10 +34,6 @@ public class FollowService { private final UserRepository userRepository; - private final NotificationRepository notificationRepository; - - private final SseEmitterService sseEmitterService; - private final ApplicationEventPublisher eventPublisher; public void follow(String followNickname, Long followerId) { @@ -101,4 +97,30 @@ public FollowListResponse followList(Long userId, Long cursor, Integer size) { return new FollowListResponse(list, nextCursor); } + + @Transactional(readOnly = true) + public FollowerListResponse followerList(Long userId, Long cursor, Integer size) { + + if (!userRepository.existsById(userId)) { + throw new UserNotFoundException(); + } + + List list = followRepository.findFollowerList(userId, cursor, size); + + // 맞팔로우 여부 조회: 내가 팔로우한 사람들의 ID 조회 + Set followingUserIds = followRepository.findFolloweeIdsByFollowerId(userId); + + List result = new ArrayList<>(); + + for(FollowResponse follower : list) { + boolean isFollow = followingUserIds.contains(follower.getUserId()); + follower.getUserId(), follower.getNickname(), isFollow); + WrapperFollowerResponse response = new WrapperFollowerResponse(follower, isFollow); + result.add(response); + } + + Long nextCursor = list.isEmpty() ? null : list.getLast().getFollowId(); + + return new FollowerListResponse(result, nextCursor); + } } \ No newline at end of file diff --git a/src/main/java/team/wego/wegobackend/user/application/dto/response/FollowerListResponse.java b/src/main/java/team/wego/wegobackend/user/application/dto/response/FollowerListResponse.java new file mode 100644 index 0000000..e1f2a88 --- /dev/null +++ b/src/main/java/team/wego/wegobackend/user/application/dto/response/FollowerListResponse.java @@ -0,0 +1,7 @@ +package team.wego.wegobackend.user.application.dto.response; + +import java.util.List; + +public record FollowerListResponse(List items, Long nextCursor) { + +} diff --git a/src/main/java/team/wego/wegobackend/user/application/dto/response/WrapperFollowerResponse.java b/src/main/java/team/wego/wegobackend/user/application/dto/response/WrapperFollowerResponse.java new file mode 100644 index 0000000..8ad78db --- /dev/null +++ b/src/main/java/team/wego/wegobackend/user/application/dto/response/WrapperFollowerResponse.java @@ -0,0 +1,24 @@ +package team.wego.wegobackend.user.application.dto.response; + +import lombok.Getter; + +@Getter +public class WrapperFollowerResponse { + + private final Long followId; //cursor + private final Long userId; + private final String profileImage; + private final String nickname; + private final String profileMessage; + private final boolean isFollow; + + public WrapperFollowerResponse(FollowResponse followResponse, boolean isFollow) { + this.followId = followResponse.getFollowId(); + this.userId = followResponse.getUserId(); + this.profileImage = followResponse.getProfileImage(); + this.nickname = followResponse.getNickname(); + this.profileMessage = followResponse.getProfileMessage(); + this.isFollow = isFollow; + } + +} diff --git a/src/main/java/team/wego/wegobackend/user/presentation/UserController.java b/src/main/java/team/wego/wegobackend/user/presentation/UserController.java index 1353344..82d8377 100644 --- a/src/main/java/team/wego/wegobackend/user/presentation/UserController.java +++ b/src/main/java/team/wego/wegobackend/user/presentation/UserController.java @@ -29,6 +29,7 @@ import team.wego.wegobackend.user.application.dto.request.ProfileUpdateRequest; import team.wego.wegobackend.user.application.dto.response.AvailabilityResponse; import team.wego.wegobackend.user.application.dto.response.FollowListResponse; +import team.wego.wegobackend.user.application.dto.response.FollowerListResponse; import team.wego.wegobackend.user.application.dto.response.UserInfoResponse; @Slf4j @@ -174,6 +175,23 @@ public ResponseEntity> followList( .body(ApiResponse.success(200, response)); } + /** + * 팔로워 리스트 조회 + */ + @GetMapping("/{userId}/follower") + public ResponseEntity> followerList( + @PathVariable Long userId, //다른 유저 조회를 위한 파라메터 + @RequestParam(required = false) Long cursor, + @RequestParam(defaultValue = "20") @Min(1) @Max(100) Integer size + ) { + + FollowerListResponse response = followService.followerList(userId, cursor, size); + + return ResponseEntity + .status(HttpStatus.OK) + .body(ApiResponse.success(200, response)); + } + /** * 이메일 중복검사 */ diff --git a/src/main/java/team/wego/wegobackend/user/repository/FollowRepository.java b/src/main/java/team/wego/wegobackend/user/repository/FollowRepository.java index 46eb34c..142c35b 100644 --- a/src/main/java/team/wego/wegobackend/user/repository/FollowRepository.java +++ b/src/main/java/team/wego/wegobackend/user/repository/FollowRepository.java @@ -1,7 +1,10 @@ package team.wego.wegobackend.user.repository; import java.util.Optional; +import java.util.Set; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import team.wego.wegobackend.user.domain.Follow; public interface FollowRepository extends JpaRepository, FollowRepositoryCustom { @@ -12,4 +15,7 @@ public interface FollowRepository extends JpaRepository, FollowRep void deleteByFollowerId(Long userId); void deleteByFolloweeId(Long userId); + + @Query("SELECT f.followee.id FROM Follow f WHERE f.follower.id = :followerId") + Set findFolloweeIdsByFollowerId(@Param("followerId") Long followerId); } diff --git a/src/main/java/team/wego/wegobackend/user/repository/FollowRepositoryCustom.java b/src/main/java/team/wego/wegobackend/user/repository/FollowRepositoryCustom.java index d9b7087..9651172 100644 --- a/src/main/java/team/wego/wegobackend/user/repository/FollowRepositoryCustom.java +++ b/src/main/java/team/wego/wegobackend/user/repository/FollowRepositoryCustom.java @@ -13,6 +13,12 @@ List findFollowingList( int size ); + List findFollowerList( + Long followerId, + Long cursorFollowId, + int size + ); + List findFollowersForNotify(Long followeeId, Long cursorFollowId, int size); } diff --git a/src/main/java/team/wego/wegobackend/user/repository/FollowRepositoryImpl.java b/src/main/java/team/wego/wegobackend/user/repository/FollowRepositoryImpl.java index 438eed6..3d7217c 100644 --- a/src/main/java/team/wego/wegobackend/user/repository/FollowRepositoryImpl.java +++ b/src/main/java/team/wego/wegobackend/user/repository/FollowRepositoryImpl.java @@ -19,66 +19,95 @@ public class FollowRepositoryImpl implements FollowRepositoryCustom { @Override public List findFollowingList( - Long followerId, - Long cursorFollowId, - int size + Long followerId, + Long cursorFollowId, + int size ) { QFollow follow = QFollow.follow; QUser user = QUser.user; return jpaQueryFactory - .select(new QFollowResponse( - follow.id, - user.id, - user.profileImage, - user.nickName, - user.profileMessage - )) - .from(follow) - .join(follow.followee, user) - .where( - follow.follower.id.eq(followerId), - itCursor(cursorFollowId) - ) - .orderBy(follow.id.desc()) //최신 팔로우 순 - .limit(size) - .fetch(); + .select(new QFollowResponse( + follow.id, + user.id, + user.profileImage, + user.nickName, + user.profileMessage + )) + .from(follow) + .join(follow.followee, user) + .where( + follow.follower.id.eq(followerId), + itCursor(cursorFollowId) + ) + .orderBy(follow.id.desc()) //최신 팔로우 순 + .limit(size) + .fetch(); + } + + @Override + public List findFollowerList( + Long followId, + Long cursorFollowId, + int size + ) { + + QFollow follow = QFollow.follow; + QUser user = QUser.user; + + return jpaQueryFactory + .select(new QFollowResponse( + follow.id, + user.id, + user.profileImage, + user.nickName, + user.profileMessage + )) + .from(follow) + .join(follow.follower, user) + .where( + follow.followee.id.eq(followId), + itCursor(cursorFollowId) + ) + .orderBy(follow.id.desc()) //최신 팔로우 순 + .limit(size) + .fetch(); } private BooleanExpression itCursor(Long cursorFollowId) { return cursorFollowId == null - ? null - : QFollow.follow.id.lt(cursorFollowId); + ? null + : QFollow.follow.id.lt(cursorFollowId); } @Override public List findFollowersForNotify( - Long followeeId, - Long cursorFollowId, - int size + Long followeeId, + Long cursorFollowId, + int size ) { QFollow follow = QFollow.follow; QUser user = QUser.user; return jpaQueryFactory - .select(Projections.constructor( - FollowerNotifyRow.class, - follow.id, - user.id, - user.nickName, - user.profileImage - )) - .from(follow) - .join(follow.follower, user) - .where( - follow.followee.id.eq(followeeId), - cursorFollowId == null ? null : follow.id.lt(cursorFollowId), - user.notificationEnabled.isTrue(), - user.deleted.isFalse() - ) - .orderBy(follow.id.desc()) - .limit(size) - .fetch(); + .select(Projections.constructor( + FollowerNotifyRow.class, + follow.id, + user.id, + user.nickName, + user.profileImage + )) + .from(follow) + .join(follow.follower, user) + .where( + follow.followee.id.eq(followeeId), + cursorFollowId == null ? null : follow.id.lt(cursorFollowId), + user.notificationEnabled.isTrue(), + user.deleted.isFalse() + ) + .orderBy(follow.id.desc()) + .limit(size) + .fetch(); } } diff --git a/src/test/http/user/user-api.http b/src/test/http/user/user-api.http index ad185ca..5a04312 100644 --- a/src/test/http/user/user-api.http +++ b/src/test/http/user/user-api.http @@ -68,7 +68,7 @@ Content-Type: application/json } ### 팔로우 요청 -POST http://localhost:8080/api/v1/users/follow?followNickname=user0 +POST http://localhost:8080/api/v1/users/follow?followNickname=ttest Authorization: Bearer {{accessToken}} ### 팔로우 취소 @@ -91,6 +91,20 @@ GET http://localhost:8080/api/v1/users/1/follow?size=10 ### 팔로우 리스트 조회 (cursor) GET http://localhost:8080/api/v1/users/1/follow?cursor={{nextCursor}}&size=10 +> {% + client.global.set("nextCursor", response.body.data.nextCursor); +%} + +### 팔로워 리스트 조회 (초기값) +GET http://localhost:8080/api/v1/users/102/follower?size=10 + +> {% + client.global.set("nextCursor", response.body.data.nextCursor); +%} + +### 팔로워 리스트 조회 (cursor) +GET http://localhost:8080/api/v1/users/1/follower?cursor={{nextCursor}}&size=10 + > {% client.global.set("nextCursor", response.body.data.nextCursor); %} \ No newline at end of file