Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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<FollowResponse> list = followRepository.findFollowerList(userId, cursor, size);

// 맞팔로우 여부 조회: 내가 팔로우한 사람들의 ID 조회
Set<Long> followingUserIds = followRepository.findFolloweeIdsByFollowerId(userId);

List<WrapperFollowerResponse> 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);
}
Comment on lines +115 to +120
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

불필요한 코드 라인을 제거하세요.

Line 117에 의미 없는 코드가 남아있습니다. 이 라인은 어떤 동작도 수행하지 않으며, 디버깅 과정에서 완전히 삭제되지 않은 것으로 보입니다.

🔎 수정 제안
 for(FollowResponse follower : list) {
     boolean isFollow = followingUserIds.contains(follower.getUserId());
-        follower.getUserId(), follower.getNickname(), isFollow);
     WrapperFollowerResponse response = new WrapperFollowerResponse(follower, isFollow);
     result.add(response);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for(FollowResponse follower : list) {
boolean isFollow = followingUserIds.contains(follower.getUserId());
follower.getUserId(), follower.getNickname(), isFollow);
WrapperFollowerResponse response = new WrapperFollowerResponse(follower, isFollow);
result.add(response);
}
for(FollowResponse follower : list) {
boolean isFollow = followingUserIds.contains(follower.getUserId());
WrapperFollowerResponse response = new WrapperFollowerResponse(follower, isFollow);
result.add(response);
}
🤖 Prompt for AI Agents
In src/main/java/team/wego/wegobackend/user/application/FollowService.java
around lines 115 to 120, there is a leftover meaningless statement on line 117
(an expression like follower.getUserId(), follower.getNickname(), isFollow);
that does nothing and should be removed; delete that stray line and ensure the
loop only constructs WrapperFollowerResponse and adds it to result, then
reformat/compile to confirm no unused imports or warnings remain.


Long nextCursor = list.isEmpty() ? null : list.getLast().getFollowId();

return new FollowerListResponse(result, nextCursor);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package team.wego.wegobackend.user.application.dto.response;

import java.util.List;

public record FollowerListResponse(List<WrapperFollowerResponse> items, Long nextCursor) {

}
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -174,6 +175,23 @@ public ResponseEntity<ApiResponse<FollowListResponse>> followList(
.body(ApiResponse.success(200, response));
}

/**
* 팔로워 리스트 조회
*/
@GetMapping("/{userId}/follower")
public ResponseEntity<ApiResponse<FollowerListResponse>> 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));
}

/**
* 이메일 중복검사
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Follow, Long>, FollowRepositoryCustom {
Expand All @@ -12,4 +15,7 @@ public interface FollowRepository extends JpaRepository<Follow, Long>, FollowRep
void deleteByFollowerId(Long userId);

void deleteByFolloweeId(Long userId);

@Query("SELECT f.followee.id FROM Follow f WHERE f.follower.id = :followerId")
Set<Long> findFolloweeIdsByFollowerId(@Param("followerId") Long followerId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ List<FollowResponse> findFollowingList(
int size
);

List<FollowResponse> findFollowerList(
Long followerId,
Long cursorFollowId,
int size
);
Comment on lines +16 to +20
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

매개변수 네이밍 개선 필요.

인터페이스 메서드의 매개변수명이 의미를 정확히 전달하지 못합니다:

  • Line 17: followerId는 실제로 팔로워를 조회할 대상 사용자(followee)의 ID입니다.
  • 구현체(FollowRepositoryImpl)에서도 follow.followee.id와 비교하고 있습니다.
  • 더 명확한 이름은 followeeId입니다.

이는 FollowRepositoryImpl의 같은 메서드와 함께 수정되어야 합니다.

🔎 제안하는 수정
 List<FollowResponse> findFollowerList(
-    Long followerId,
+    Long followeeId,
     Long cursorFollowId,
     int size
 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
List<FollowResponse> findFollowerList(
Long followerId,
Long cursorFollowId,
int size
);
List<FollowResponse> findFollowerList(
Long followeeId,
Long cursorFollowId,
int size
);
🤖 Prompt for AI Agents
In
src/main/java/team/wego/wegobackend/user/repository/FollowRepositoryCustom.java
around lines 16 to 20, the parameter name followerId is misleading because it
actually represents the followee (the user being followed); rename the parameter
to followeeId in the interface signature and update any Javadoc/comments
accordingly, and then update the corresponding method signature and usages in
FollowRepositoryImpl to use followeeId so names are consistent between interface
and implementation.


List<FollowerNotifyRow> findFollowersForNotify(Long followeeId, Long cursorFollowId, int size);

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,66 +19,95 @@ public class FollowRepositoryImpl implements FollowRepositoryCustom {

@Override
public List<FollowResponse> 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<FollowResponse> 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();
}
Comment on lines +49 to 76
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

매개변수 네이밍 개선 필요.

메서드 로직은 올바르지만, 매개변수명이 의미를 명확히 전달하지 못합니다:

  • Line 51: followId는 실제로 팔로워 목록을 조회할 대상 사용자(followee)의 ID를 나타냅니다.
  • Line 70: follow.followee.id.eq(followId)를 보면 이것이 followee ID임을 알 수 있습니다.
  • 더 명확한 이름은 followeeId입니다.

이 변경은 FollowRepositoryCustom 인터페이스에도 적용되어야 합니다.

🔎 제안하는 수정
 @Override
 public List<FollowResponse> findFollowerList(
-    Long followId,
+    Long followeeId,
     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),
+            follow.followee.id.eq(followeeId),
             itCursor(cursorFollowId)
         )
         .orderBy(follow.id.desc())
         .limit(size)
         .fetch();
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public List<FollowResponse> 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();
}
@Override
public List<FollowResponse> findFollowerList(
Long followeeId,
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(followeeId),
itCursor(cursorFollowId)
)
.orderBy(follow.id.desc()) //최신 팔로우 순
.limit(size)
.fetch();
}
🤖 Prompt for AI Agents
In src/main/java/team/wego/wegobackend/user/repository/FollowRepositoryImpl.java
around lines 49 to 76, rename the method parameter followId to followeeId (and
update its usages inside the method, e.g., follow.followee.id.eq(followeeId)) to
accurately reflect that it is the target user's ID; also update the
corresponding declaration in FollowRepositoryCustom (and any
implementations/usages) to use followeeId to keep signatures consistent and
avoid compile errors.


private BooleanExpression itCursor(Long cursorFollowId) {
return cursorFollowId == null
? null
: QFollow.follow.id.lt(cursorFollowId);
? null
: QFollow.follow.id.lt(cursorFollowId);
}

@Override
public List<FollowerNotifyRow> 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();
}
}
16 changes: 15 additions & 1 deletion src/test/http/user/user-api.http
Original file line number Diff line number Diff line change
Expand Up @@ -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}}

### 팔로우 취소
Expand All @@ -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);
%}
Comment on lines +98 to 110
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

테스트 케이스의 userId 불일치.

페이지네이션 테스트에서 서로 다른 userId를 사용하고 있습니다:

  • Line 99: userId=102 (초기 조회)
  • Line 106: userId=1 (커서 기반 조회)

페이지네이션이 올바르게 동작하려면 동일한 사용자의 팔로워 목록을 조회해야 합니다.

🔎 제안하는 수정
 ### 팔로워 리스트 조회 (cursor)
-GET http://localhost:8080/api/v1/users/1/follower?cursor={{nextCursor}}&size=10
+GET http://localhost:8080/api/v1/users/102/follower?cursor={{nextCursor}}&size=10
🤖 Prompt for AI Agents
In src/test/http/user/user-api.http around lines 98 to 110, the pagination test
uses different userIds between the initial follower request (userId=102) and the
cursor-based request (userId=1); update the second GET to use the same userId as
the first (change userId=1 to userId=102) so both requests target the same
user's follower list and the saved nextCursor applies correctly.