Skip to content

[FEAT] 팔로우 요청 API 개발#83

Merged
Be-HinD merged 3 commits intomainfrom
feat/follow
Dec 11, 2025
Merged

[FEAT] 팔로우 요청 API 개발#83
Be-HinD merged 3 commits intomainfrom
feat/follow

Conversation

@Be-HinD
Copy link
Member

@Be-HinD Be-HinD commented Dec 11, 2025

📝 Pull Request

📌 PR 종류

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

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

✨ 변경 내용

팔로우 등록을 위한 요청 API 추가


🔍 관련 이슈

Closes #60


🧪 테스트

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

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

🚨 확인해야 할 사항 (Checklist)

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

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

🙋 기타 참고 사항

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

Summary by CodeRabbit

  • 새 기능

    • 사용자 팔로우 기능 추가 — 특정 사용자를 팔로우해 관계를 생성할 수 있습니다.
    • 팔로우 API 엔드포인트 및 관련 검증(중복/자기 자신 차단) 도입.
  • 개선 사항

    • 인증 토큰 처리 방식 개선 — 토큰에서 사용자 ID·권한 정보를 보다 명확하게 처리합니다.
    • 팔로우 관련 예외 및 사용자 메시지 추가로 오류 상황을 명확히 안내합니다.
  • 테스트

    • 팔로우 흐름을 검증하는 API 테스트 시나리오 추가.

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

@Be-HinD Be-HinD self-assigned this Dec 11, 2025
@Be-HinD Be-HinD added the ✨enhancement New feature or request label Dec 11, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 11, 2025

Walkthrough

사용자 간 팔로우 기능이 추가되었습니다: Follow 엔티티, FollowService, FollowRepository, 컨트롤러 엔드포인트 및 관련 예외와 에러코드가 도입되었고, JWT 토큰 유틸리티의 일부 반환 타입이 변경되었습니다.

Changes

코호트 / 파일(s) 변경 요약
예외 및 에러 코드
src/main/java/team/wego/wegobackend/common/exception/AppErrorCode.java
에러코드 추가: NOT_SAME_FOLLOW, ALREADY_EXIST_FOLLOW
JWT 토큰 유틸리티
src/main/java/team/wego/wegobackend/common/security/jwt/JwtTokenProvider.java
반환 타입 변경: getTokenUserId()Long, getRoleFromToken()String (claims 조회 타입 조정)
팔로우 도메인
src/main/java/team/wego/wegobackend/user/domain/Follow.java
Follow JPA 엔티티 추가 (id, follower, followee, 복합 유니크 제약)
User 엔티티 관계
src/main/java/team/wego/wegobackend/user/domain/User.java
양방향 관계 추가: followings, followers 컬렉션 필드 추가
서비스 계층
src/main/java/team/wego/wegobackend/user/application/FollowService.java
FollowService 추가: 자기 팔로우 검사, 중복 검사, 사용자 조회 후 팔로우 저장 (트랜잭션)
예외 클래스
src/main/java/team/wego/wegobackend/user/exception/SameFollowException.java,
src/main/java/team/wego/wegobackend/user/exception/ExistFollowException.java
팔로우 관련 전용 예외 추가 (각각 AppErrorCode 사용)
리포지토리
src/main/java/team/wego/wegobackend/user/repository/FollowRepository.java
FollowRepository 추가: existsByFollowerIdAndFolloweeId(...) 메서드
컨트롤러 및 API 문서
src/main/java/team/wego/wegobackend/user/presentation/UserController.java,
src/main/java/team/wego/wegobackend/user/presentation/UserControllerDocs.java
POST /api/v1/users/follow/{userId} 엔드포인트 추가 (인증 사용자로 팔로우 호출)
테스트 시나리오
src/test/http/user/user-api.http
팔로우 시나리오 추가: 테스트 유저 생성 및 팔로우 요청

Sequence Diagram

sequenceDiagram
    participant Client as Client
    participant Controller as UserController
    participant Service as FollowService
    participant UserRepo as UserRepository
    participant FollowRepo as FollowRepository
    participant DB as Database

    Client->>Controller: POST /api/v1/users/follow/{userId} (JWT)
    Controller->>Service: follow(followId, followerId)
    alt followerId == followId
        Service-->>Controller: throw SameFollowException (400)
        Controller-->>Client: 400 Bad Request
    else existsByFollowerIdAndFolloweeId == true
        Service->>FollowRepo: existsByFollowerIdAndFolloweeId(followerId, followId)
        FollowRepo->>DB: SELECT ...
        DB-->>FollowRepo: count > 0
        Service-->>Controller: throw ExistFollowException (400)
        Controller-->>Client: 400 Bad Request
    else 정상 흐름
        Service->>UserRepo: findById(followerId)
        UserRepo->>DB: SELECT user
        DB-->>UserRepo: follower User
        Service->>UserRepo: findById(followId)
        UserRepo->>DB: SELECT user
        DB-->>UserRepo: followee User
        Service->>FollowRepo: save(new Follow(follower, followee))
        FollowRepo->>DB: INSERT INTO follows
        DB-->>FollowRepo: OK
        Service-->>Controller: 성공
        Controller-->>Client: 201 Created
    end
Loading

Estimated code review effort

🎯 3 (보통) | ⏱️ ~30분

  • 주의 검토 항목:
    • JwtTokenProvider의 반환 타입 변경에 따른 호출부 호환성 점검
    • Follow 엔티티의 복합 유니크 제약이 DB 마이그레이션/기존 데이터와 충돌 없는지
    • FollowService의 예외 흐름과 트랜잭션 범위, lazy 로딩에 따른 N+1 가능성

Possibly related PRs

Poem

🐰 팔로우 한 걸음, 또 한 걸음,
데이터베이스에 새 길을 그렸네,
자신은 피해, 중복은 걸러,
서비스가 손을 맞잡고,
토큰 따라 친구 찾아가네 🌱

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning JWT 토큰 제공자의 getTokenUserId와 getRoleFromToken 메서드의 반환 타입 변경이 팔로우 기능 범위 외의 변경입니다. JWT 토큰 제공자의 반환 타입 변경 사항을 별도의 PR로 분리하거나, 이 변경이 팔로우 기능에 필수적인 이유를 설명하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 팔로우 요청 API 개발이라는 주요 변경 사항을 명확하게 요약하고 있으며, 모든 파일 변경 사항과 일치합니다.
Linked Issues check ✅ Passed PR이 연결된 issue #60의 모든 코딩 요구사항을 충족합니다: Follow Entity 추가, User 관계 매핑, 팔로우 API 개발 완료.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/follow

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: 2

🧹 Nitpick comments (8)
src/main/java/team/wego/wegobackend/user/presentation/UserControllerDocs.java (1)

46-50: 팔로우 API 문서 시그니처는 적절하나 @Valid는 불필요해 보입니다

  • 인증 주체 + userId 조합으로 팔로우 요청을 받는 시그니처는 자연스럽습니다.
  • 다만 Long 타입 @PathVariable에 붙은 @Valid는 별도의 제약 어노테이션이 없으면 동작하지 않으므로 제거해도 될 것 같습니다.
-        @AuthenticationPrincipal CustomUserDetails userDetails,
-        @Valid @PathVariable("userId") Long userId
+        @AuthenticationPrincipal CustomUserDetails userDetails,
+        @PathVariable("userId") Long userId
src/main/java/team/wego/wegobackend/user/domain/User.java (1)

72-77: User–Follow 양방향 매핑은 적절하지만 사용 패턴을 명확히 해두는 것이 좋습니다

  • mappedBy = "follower" / "followee" 설정과 컬렉션 초기화까지 JPA 매핑 관점에서는 무난합니다.
  • 다만 이후에 컬렉션을 직접 조작할 계획이라면 addFollowing(Follow follow) 같은 편의 메서드로 양쪽 연관관계 일관성을 보장하는 패턴을 두는 것을 고려해 볼 수 있습니다.
  • 또, 엔티티를 직접 JSON 응답에 노출할 경우 팔로우 컬렉션 때문에 순환 참조가 생길 수 있으므로, 지금처럼 DTO를 통해서만 외부에 노출하는 방향을 계속 유지하는 것이 안전합니다.
src/main/java/team/wego/wegobackend/user/repository/FollowRepository.java (1)

6-9: existsByFollowerIdAndFolloweeId 파라미터 이름 정합성 제안

메서드 시그니처 자체는 Spring Data JPA 파생 쿼리 규칙에 맞지만, 두 번째 파라미터 이름이 followingId라서 메서드명(...FolloweeId)과 살짝 어긋납니다. 가독성을 위해 아래처럼 맞춰 두는 것을 추천합니다.

-    boolean existsByFollowerIdAndFolloweeId(Long followerId, Long followingId);
+    boolean existsByFollowerIdAndFolloweeId(Long followerId, Long followeeId);
src/main/java/team/wego/wegobackend/common/exception/AppErrorCode.java (1)

29-30: 팔로우 관련 에러 코드 추가는 적절하지만 상수명 통일을 고려해볼 만합니다

  • HTTP 상태와 메시지는 도메인 요구사항과 잘 맞습니다.
  • 다만 이미 ALREADY_EXISTS_USER가 있는 상태에서 ALREADY_EXIST_FOLLOW는 철자 패턴이 달라 약간 혼란을 줄 수 있습니다. 아직 사용처가 많지 않다면, 아래처럼 정리해 두면 나중에 읽기 더 수월할 것 같습니다.
-    NOT_SAME_FOLLOW(HttpStatus.BAD_REQUEST, "회원 : 자기 자신을 팔로우할 수 없습니다."),
-    ALREADY_EXIST_FOLLOW(HttpStatus.BAD_REQUEST, "회원 : 이미 팔로우 중입니다."),
+    SELF_FOLLOW_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "회원 : 자기 자신을 팔로우할 수 없습니다."),
+    ALREADY_EXISTS_FOLLOW(HttpStatus.BAD_REQUEST, "회원 : 이미 팔로우 중입니다."),

(당연히 관련 예외 클래스와 사용처 상수명도 함께 변경해야 합니다.)

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

106-106: 불필요한 @Valid 애노테이션을 제거하세요.

@PathVariable Long userId@Valid를 사용하는 것은 불필요합니다. @Valid는 복잡한 객체에 대한 Bean Validation을 트리거하는데, 단순한 Long 타입에는 효과가 없습니다.

-        @Valid @PathVariable("userId") Long userId
+        @PathVariable("userId") Long userId
src/main/java/team/wego/wegobackend/user/application/FollowService.java (1)

3-3: 사용하지 않는 import를 제거하세요.

import java.util.List;는 코드에서 사용되지 않습니다.

-import java.util.List;
src/main/java/team/wego/wegobackend/user/domain/Follow.java (2)

17-17: 사용하지 않는 import를 제거하세요.

import team.wego.wegobackend.common.security.Role;는 이 엔티티 클래스에서 사용되지 않습니다.

-import team.wego.wegobackend.common.security.Role;

40-44: 생성자 파라미터 이름을 필드명과 일치시키세요.

Line 41에서 두 번째 파라미터가 follow로 명명되어 있지만, Line 43에서는 followee 필드에 할당됩니다. 이러한 불일치는 코드 가독성과 유지보수성을 저해합니다.

     @Builder
-    public Follow(User follower, User follow) {
+    public Follow(User follower, User followee) {
         this.follower = follower;
-        this.followee = follow;
+        this.followee = followee;
     }
📜 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 62d27f5 and 796b448.

📒 Files selected for processing (11)
  • src/main/java/team/wego/wegobackend/common/exception/AppErrorCode.java (1 hunks)
  • src/main/java/team/wego/wegobackend/common/security/jwt/JwtTokenProvider.java (1 hunks)
  • src/main/java/team/wego/wegobackend/user/application/FollowService.java (1 hunks)
  • src/main/java/team/wego/wegobackend/user/domain/Follow.java (1 hunks)
  • src/main/java/team/wego/wegobackend/user/domain/User.java (2 hunks)
  • src/main/java/team/wego/wegobackend/user/exception/ExistFollowException.java (1 hunks)
  • src/main/java/team/wego/wegobackend/user/exception/SameFollowException.java (1 hunks)
  • src/main/java/team/wego/wegobackend/user/presentation/UserController.java (4 hunks)
  • src/main/java/team/wego/wegobackend/user/presentation/UserControllerDocs.java (1 hunks)
  • src/main/java/team/wego/wegobackend/user/repository/FollowRepository.java (1 hunks)
  • src/test/http/user/user-api.http (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/team/wego/wegobackend/user/application/FollowService.java (4)
src/main/java/team/wego/wegobackend/user/presentation/UserController.java (1)
  • Slf4j (29-121)
src/main/java/team/wego/wegobackend/common/security/CustomUserDetailsService.java (1)
  • Service (15-28)
src/main/java/team/wego/wegobackend/user/exception/ExistFollowException.java (1)
  • ExistFollowException (6-11)
src/main/java/team/wego/wegobackend/user/exception/SameFollowException.java (1)
  • SameFollowException (7-12)
🔇 Additional comments (5)
src/main/java/team/wego/wegobackend/common/security/jwt/JwtTokenProvider.java (1)

75-76: JWT 클레임 타입 변경 방향 괜찮습니다

userIdLong, roleString으로 직접 꺼내도록 통일된 점이 토큰 생성 로직과도 일관적이라 좋습니다. 호출부에서 컴파일 타임에 타입이 드러나서 이후 사용도 더 명확해질 것 같습니다.

Also applies to: 83-85

src/test/http/user/user-api.http (1)

56-68: 팔로우 플로우 HTTP 시나리오 구성이 자연스럽습니다

기존에 로그인한 유저(1번)가 새로 가입한 유저(2번)를 /users/follow/2로 팔로우하는 흐름이 잘 드러납니다. 다만 DB 초기 상태에서 1, 2번 ID가 순서대로 생성된다는 전제이므로, 반복 실행 시에는 테스트용 DB 리셋이 필요하다는 점만 인지하고 사용하면 될 것 같습니다.

src/main/java/team/wego/wegobackend/user/exception/SameFollowException.java (1)

1-12: 자기 자신 팔로우 방지 예외 매핑이 명확합니다

SameFollowExceptionAppErrorCode.NOT_SAME_FOLLOW로 직접 매핑되어 있어 서비스 레이어에서 의도가 잘 드러납니다. 별다른 추가 로직 없이도 사용하기에 충분해 보입니다.

src/main/java/team/wego/wegobackend/user/exception/ExistFollowException.java (1)

1-11: 중복 팔로우 예외 정의가 간결하고 일관적입니다

ExistFollowExceptionAppErrorCode.ALREADY_EXIST_FOLLOW에만 의존하도록 잘 묶여 있어서 서비스 레이어에서 의미를 파악하기 쉽습니다. SameFollowException과 패턴도 맞아 일관성이 좋습니다.

src/main/java/team/wego/wegobackend/user/domain/Follow.java (1)

19-23: LGTM: Unique constraint가 적절하게 구성되었습니다.

follower_idfollowee_id의 조합에 대한 unique constraint는 중복 팔로우를 데이터베이스 레벨에서 방지하여 데이터 무결성을 보장합니다. 애플리케이션 레벨의 검증(FollowServiceexistsByFollowerIdAndFolloweeId 체크)과 함께 이중 안전장치를 제공합니다.


private final UserRepository userRepository;

public void follow(Long followId, Long followerId) {
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

파라미터 이름이 혼란스럽습니다.

메서드 시그니처 follow(Long followId, Long followerId)에서 파라미터 순서와 이름이 직관적이지 않습니다. Line 31의 사용을 보면 followerId가 팔로우하는 사용자이고 followId가 팔로우 대상인데, 일반적인 명명 규칙에서는 행위자(follower)가 먼저 오는 것이 자연스럽습니다.

다음과 같이 시그니처를 변경하여 가독성을 개선하세요:

-    public void follow(Long followId, Long followerId) {
+    public void follow(Long followerId, Long followeeId) {
-        if(followerId.equals(followId)) {
+        if(followerId.equals(followeeId)) {
             throw new SameFollowException();
         }
 
-        if (followRepository.existsByFollowerIdAndFolloweeId(followerId, followId)) {
+        if (followRepository.existsByFollowerIdAndFolloweeId(followerId, followeeId)) {
             throw new ExistFollowException();
         }
 
         User follower = userRepository.findById(followerId)
             .orElseThrow(UserNotFoundException::new);
 
-        User follow = userRepository.findById(followId)
+        User followee = userRepository.findById(followeeId)
             .orElseThrow(UserNotFoundException::new);
 
         followRepository.save(Follow.builder()
             .follower(follower)
-            .follow(follow)
+            .follow(followee)
             .build());
     }

참고: 이 변경을 적용하면 UserController의 호출부도 함께 수정해야 합니다.

📝 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
public void follow(Long followId, Long followerId) {
public void follow(Long followerId, Long followeeId) {
if(followerId.equals(followeeId)) {
throw new SameFollowException();
}
if (followRepository.existsByFollowerIdAndFolloweeId(followerId, followeeId)) {
throw new ExistFollowException();
}
User follower = userRepository.findById(followerId)
.orElseThrow(UserNotFoundException::new);
User followee = userRepository.findById(followeeId)
.orElseThrow(UserNotFoundException::new);
followRepository.save(Follow.builder()
.follower(follower)
.follow(followee)
.build());
}
🤖 Prompt for AI Agents
In src/main/java/team/wego/wegobackend/user/application/FollowService.java
around line 26, the method signature follow(Long followId, Long followerId) is
confusing because the actor (follower) should appear first; change the signature
to place the follower parameter first and rename parameters to be explicit (for
example follow(Long followerId, Long followeeId) or follow(Long followerId, Long
targetUserId)), update all usages inside the service accordingly, and then
update the UserController call site(s) and any other callers to use the new
parameter order and names so behavior remains correct.

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: 0

🧹 Nitpick comments (3)
src/main/java/team/wego/wegobackend/user/presentation/UserController.java (3)

10-10: 사용하지 않는 import를 제거하는 것을 고려하세요.

DeleteMapping이 import되었지만 현재 TODO 주석에서만 참조되고 실제로 사용되지 않습니다. 삭제 API를 구현할 때 추가하는 것이 좋습니다.


116-120: 향후 기능 구현을 위한 TODO 주석

팔로우 삭제, 팔로우 목록 조회, 팔로워 목록 조회를 위한 TODO 주석이 있습니다. 이러한 기능들은 Issue #60의 범위를 넘어서지만 팔로우 시스템을 완성하는 데 필요합니다.

이러한 기능들의 구현을 도와드리거나 추적을 위한 새 이슈를 생성하시겠습니까?


103-114: 이전 리뷰의 치명적 이슈가 수정되었습니다!

Line 109의 인자 순서가 올바르게 수정되었습니다. followService.follow(userId, userDetails.getId())는 서비스 메서드 시그니처와 일치합니다 (팔로우 대상이 첫 번째, 팔로우하는 사용자가 두 번째).

서비스 계층의 에러 처리도 완벽하게 구현되었습니다:

  • 자기 자신을 팔로우하는 경우: SameFollowException 처리 ✓
  • 이미 팔로우한 사용자를 다시 팔로우하는 경우: ExistFollowException 처리 ✓
  • 존재하지 않는 팔로우 대상/팔로우자: UserNotFoundException 처리 ✓

추가 개선 사항:

Line 106의 @Valid 어노테이션은 제거하세요. @Valid는 제약 조건 어노테이션이 있는 복잡한 DTO 객체를 검증하는 데 사용되며, 단순 타입인 Long에는 불필요합니다:

-        @Valid @PathVariable("userId") Long userId
+        @PathVariable("userId") Long userId
📜 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 796b448 and 72cf781.

📒 Files selected for processing (1)
  • src/main/java/team/wego/wegobackend/user/presentation/UserController.java (4 hunks)
🔇 Additional comments (1)
src/main/java/team/wego/wegobackend/user/presentation/UserController.java (1)

24-24: 잘 구현되었습니다!

FollowService import와 필드 주입이 올바르게 구현되었습니다.

Also applies to: 36-36

@Be-HinD Be-HinD merged commit 521e785 into main Dec 11, 2025
1 check passed
@Be-HinD Be-HinD deleted the feat/follow branch December 11, 2025 08:23
@github-project-automation github-project-automation bot moved this from Backlog to Done in WeGo-Together Backend Dec 11, 2025
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.

[FEAT] 사용자 팔로우 API 개발

1 participant