Skip to content

[FEAT] 팔로워 목록 조회 API 개발#204

Merged
Be-HinD merged 2 commits intomainfrom
feat/follower-list
Jan 4, 2026
Merged

[FEAT] 팔로워 목록 조회 API 개발#204
Be-HinD merged 2 commits intomainfrom
feat/follower-list

Conversation

@Be-HinD
Copy link
Member

@Be-HinD Be-HinD commented Jan 4, 2026

📝 Pull Request

📌 PR 종류

해당하는 항목에 체크해주세요.

  • 기능 추가 (Feature)
  • 버그 수정 (Fix)
  • 문서 수정 (Docs)
  • 코드 리팩터링 (Refactor)
  • 테스트 추가 (Test)
  • 기타 변경 (Chore)

✨ 변경 내용

이번 PR에서 어떤 변경이 이루어졌는지 명확하게 작성해주세요.
팔로워 목록 조회 API가 추가되었습니다.
기존 팔로잉 목록 조회 응답에서 맞팔로우 여부를 나타내는 isFollow 필드 및 관련 로직이 추가되었습니다.


🔍 관련 이슈

해당 PR이 해결하는 이슈가 있다면 연결해주세요.


🧪 테스트

변경된 기능에 대한 테스트 범위 또는 테스트 결과를 작성해주세요.

  • 유닛 테스트 추가 / 수정
  • 통합 테스트 검증
  • 수동 테스트 완료
image

🚨 확인해야 할 사항 (Checklist)

PR을 제출하기 전에 아래 항목들을 확인해주세요.

  • 코드 포매팅 완료
  • 불필요한 파일/코드 제거
  • 로직 검증 완료
  • 프로젝트 빌드 성공
  • 린트/정적 분석 통과 (해당 시)

🙋 기타 참고 사항

리뷰어가 참고하면 좋을 만한 추가 설명이 있다면 적어주세요.

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능
    • 팔로워 목록 조회 API 추가: 팔로워 목록을 커서 기반 페이지네이션으로 조회할 수 있으며, 각 팔로워에 대해 상호 팔로우 여부를 함께 확인할 수 있습니다.

✏️ Tip: You can customize this high-level summary in your review settings.

@Be-HinD Be-HinD self-assigned this Jan 4, 2026
@Be-HinD Be-HinD added the ✨enhancement New feature or request label Jan 4, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 4, 2026

Walkthrough

팔로워 목록 조회 기능을 추가합니다. 새로운 API 엔드포인트는 지정된 사용자의 팔로워를 페이지네이션과 함께 반환하며, 각 팔로워에 대한 상호 팔로우 상태 정보를 포함합니다.

Changes

Cohort / File(s) 변경 사항
데이터 전송 객체
src/main/java/team/wego/wegobackend/user/application/dto/response/FollowerListResponse.java, src/main/java/team/wego/wegobackend/user/application/dto/response/WrapperFollowerResponse.java
팔로워 목록 응답을 위한 새 DTO 레코드 및 클래스 추가. FollowerListResponse는 항목 목록과 다음 커서를 포함하고, WrapperFollowerResponse는 팔로워 정보와 상호 팔로우 여부 플래그를 포함합니다.
리포지토리 계층
src/main/java/team/wego/wegobackend/user/repository/FollowRepository.java, src/main/java/team/wego/wegobackend/user/repository/FollowRepositoryCustom.java, src/main/java/team/wego/wegobackend/user/repository/FollowRepositoryImpl.java
팔로워 목록 조회 기능 추가: findFolloweeIdsByFollowerId() 메서드로 사용자의 팔로우 ID 집합 조회, findFollowerList() 메서드로 커서 기반 페이지네이션을 통한 팔로워 목록 조회.
서비스 계층
src/main/java/team/wego/wegobackend/user/application/FollowService.java
알림 관련 의존성 제거 및 새로운 followerList() 메서드 추가. 사용자 존재 검증, 팔로워 목록 조회, 상호 팔로우 여부 계산 및 응답 구성을 처리합니다.
컨트롤러 계층
src/main/java/team/wego/wegobackend/user/presentation/UserController.java
GET /api/v1/users/{userId}/follower 엔드포인트 추가. 선택적 커서 및 크기 파라미터(기본값 20, 범위 1-100)를 지원하고 HTTP 200으로 응답합니다.
테스트 및 문서
src/test/http/user/user-api.http
팔로워 목록 조회 API 예제 추가. 초기 요청 및 커서 기반 페이지네이션 예제 포함.

Sequence Diagram(s)

sequenceDiagram
    participant Client as 클라이언트
    participant Controller as UserController
    participant Service as FollowService
    participant Repo as FollowRepository/<br/>Custom
    participant DB as 데이터베이스

    Client->>Controller: GET /api/v1/users/{userId}/follower<br/>(cursor, size)
    Controller->>Service: followerList(userId, cursor, size)
    Service->>Service: 사용자 존재 검증
    Service->>Repo: findFollowerList(userId, cursor, size)
    Repo->>DB: SELECT 팔로워 정보 with 페이지네이션
    DB-->>Repo: List&#60;FollowResponse&#62;
    Repo-->>Service: 팔로워 목록
    Service->>Repo: findFolloweeIdsByFollowerId(userId)
    Repo->>DB: SELECT 팔로우 ID 집합
    DB-->>Repo: Set&#60;Long&#62;
    Repo-->>Service: 팔로우 ID 집합
    Service->>Service: 각 팔로워의<br/>상호 팔로우 여부 계산
    Service->>Service: WrapperFollowerResponse 구성<br/>(isFollow 플래그 포함)
    Service->>Service: 다음 커서 계산
    Service-->>Controller: FollowerListResponse
    Controller-->>Client: ApiResponse&#60;FollowerListResponse&#62;<br/>(HTTP 200)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 팔로워 목록을 정렬하네,
커서로 넘기며 우아하게,
상호 팔로우는 한눈에 쏙!
페이지네이션이 깔끔하고,
API는 빠르고 명쾌하네~ 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 풀 리퀘스트의 주요 변경사항인 팔로워 목록 조회 API 개발을 명확하게 설명하고 있습니다.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
src/main/java/team/wego/wegobackend/user/repository/FollowRepositoryImpl.java (1)

21-47: 기존 팔로잉 목록 조회 로직 확인 완료.

구현이 올바르며, 새로 추가된 findFollowerList 메서드와 구조가 거의 동일합니다.

💡 선택적 리팩토링: 중복 코드 제거

두 메서드(findFollowingListfindFollowerList)가 거의 동일한 구조를 가지고 있습니다. 향후 유지보수성을 위해 공통 로직을 추출하는 것을 고려해보세요.

private List<FollowResponse> findFollowList(
    Long userId,
    Long cursorFollowId,
    int size,
    boolean isFollowing // true면 팔로잉, false면 팔로워
) {
    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(isFollowing ? follow.followee : follow.follower, user)
        .where(
            isFollowing 
                ? follow.follower.id.eq(userId)
                : follow.followee.id.eq(userId),
            itCursor(cursorFollowId)
        )
        .orderBy(follow.id.desc())
        .limit(size)
        .fetch();
}
src/main/java/team/wego/wegobackend/user/application/dto/response/WrapperFollowerResponse.java (1)

5-24: Java Record 사용을 고려해보세요.

현재 구현은 정상적으로 동작하지만, Java 16+의 record 문법을 사용하면 더 간결하게 작성할 수 있습니다. Record는 불변 데이터 클래스에 적합하며 보일러플레이트 코드를 줄여줍니다.

🔎 Record로 리팩토링하는 제안
-@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;
-    }
-
-}
+public record WrapperFollowerResponse(
+    Long followId,    //cursor
+    Long userId,
+    String profileImage,
+    String nickname,
+    String profileMessage,
+    boolean isFollow
+) {
+    public WrapperFollowerResponse(FollowResponse followResponse, boolean isFollow) {
+        this(
+            followResponse.getFollowId(),
+            followResponse.getUserId(),
+            followResponse.getProfileImage(),
+            followResponse.getNickname(),
+            followResponse.getProfileMessage(),
+            isFollow
+        );
+    }
+}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 37bce00 and a8216d1.

📒 Files selected for processing (8)
  • src/main/java/team/wego/wegobackend/user/application/FollowService.java
  • src/main/java/team/wego/wegobackend/user/application/dto/response/FollowerListResponse.java
  • src/main/java/team/wego/wegobackend/user/application/dto/response/WrapperFollowerResponse.java
  • src/main/java/team/wego/wegobackend/user/presentation/UserController.java
  • src/main/java/team/wego/wegobackend/user/repository/FollowRepository.java
  • src/main/java/team/wego/wegobackend/user/repository/FollowRepositoryCustom.java
  • src/main/java/team/wego/wegobackend/user/repository/FollowRepositoryImpl.java
  • src/test/http/user/user-api.http
🔇 Additional comments (7)
src/test/http/user/user-api.http (1)

71-71: 테스트 데이터 및 커서 전파 로직 확인 완료.

팔로우 목록의 커서 전파 로직이 올바르게 추가되었습니다.

Also applies to: 94-96

src/main/java/team/wego/wegobackend/user/application/dto/response/FollowerListResponse.java (1)

1-7: 팔로워 목록 응답 DTO 구조 확인 완료.

itemsnextCursor를 포함한 record 구조가 적절하며, 커서 기반 페이지네이션 패턴을 올바르게 따르고 있습니다.

src/main/java/team/wego/wegobackend/user/presentation/UserController.java (1)

32-32: 팔로워 목록 조회 엔드포인트 구현 확인 완료.

새로운 REST 엔드포인트가 올바르게 구현되었습니다:

  • 기존 followList 엔드포인트와 일관된 패턴을 따릅니다.
  • 페이지네이션 파라미터 검증이 적절합니다 (@Min(1) @Max(100)).
  • 서비스 계층으로의 위임이 명확합니다.

Also applies to: 178-193

src/main/java/team/wego/wegobackend/user/application/FollowService.java (3)

104-106: 사용자 존재 검증 로직이 올바릅니다.

팔로워 목록 조회 전에 사용자 존재 여부를 확인하는 것은 적절한 검증입니다.


108-111: 맞팔로우 확인 로직이 효율적입니다.

팔로워 목록과 팔로이 ID 집합을 분리하여 조회한 후 contains 메서드로 확인하는 방식은 O(1) 조회 성능을 제공합니다.


122-124: 커서 기반 페이지네이션 구현이 적절합니다.

기존 followList 메서드와 동일한 패턴으로 커서를 계산하여 일관성 있는 API 동작을 제공합니다.

src/main/java/team/wego/wegobackend/user/repository/FollowRepository.java (1)

19-20: JPQL 쿼리의 엔티티 관계가 올바르게 정의되어 있습니다.

Follow 엔티티의 관계 정보를 확인했으며, followerfollowee 필드가 @ManyToOne 관계로 올바르게 정의되어 있습니다. JPQL 쿼리 SELECT f.followee.id FROM Follow f WHERE f.follower.id = :followerId는 엔티티 구조와 정확하게 일치하므로 추가 수정이 필요하지 않습니다.

Comment on lines +115 to +120
for(FollowResponse follower : list) {
boolean isFollow = followingUserIds.contains(follower.getUserId());
follower.getUserId(), follower.getNickname(), isFollow);
WrapperFollowerResponse response = new WrapperFollowerResponse(follower, isFollow);
result.add(response);
}
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.

Comment on lines +16 to +20
List<FollowResponse> findFollowerList(
Long followerId,
Long cursorFollowId,
int size
);
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.

Comment on lines +49 to 76
@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();
}
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.

Comment on lines +98 to 110
### 팔로워 리스트 조회 (초기값)
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
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.

@Be-HinD Be-HinD merged commit c3327fb into main Jan 4, 2026
1 check passed
@Be-HinD Be-HinD deleted the feat/follower-list branch January 4, 2026 10:17
@github-project-automation github-project-automation bot moved this from Backlog to Done in WeGo-Together Backend Jan 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant