Conversation
요약 (Walkthrough)인증된 사용자의 ID를 컨트롤러에서 추출하여 서비스 계층으로 전달하고, 사용자의 관심 종목과 일치하는 동호회를 우선적으로 정렬하는 로직을 추가하여 동호회 목록 조회 엔드포인트에 사용자 맞춤형 개인화 기능을 구현합니다. 변경 사항 (Changes)
시퀀스 다이어그램 (Sequence Diagram)sequenceDiagram
actor Client
participant Controller as ClubController
participant Service as ClubService
participant UserService as UserService
participant Repository as ClubRepository
Client->>Controller: getClubs(cursor, size)<br/>with Authentication
Controller->>Controller: Extract userId from<br/>@AuthenticationPrincipal
Controller->>Service: getClubsByScroll(cursor, size, userId)
Service->>UserService: Get user interest types
UserService-->>Service: Return user interests
Service->>Repository: Query clubs by cursor & size
Repository-->>Service: Return club entities
Service->>Service: Sort clubs by<br/>interest match
Service-->>Controller: Return ClubScrollResponse
Controller-->>Client: Return BaseResponse<br/>with sorted clubs
코드 검토 소요 시간 (Estimated Code Review Effort)🎯 3 (Moderate) | ⏱️ ~20 분 관련 가능 PR (Possibly Related PRs)
토끼의 시 (Poem)
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/java/com/be/sportizebe/domain/club/service/ClubServiceImpl.java (1)
149-161:⚠️ Potential issue | 🔴 Critical정렬 후
nextCursor를 계산하면 무한스크롤이 깨집니다.여기서는 관심사 우선 정렬이 끝난 뒤 마지막 항목의
clubId를 다음 커서로 사용하고 있습니다. 그러면 현재 페이지 안에 더 작은id의 클럽이 앞쪽으로 이동한 경우, 다음 요청에서 중복 조회되거나 일부 항목이 건너뛰어집니다. 커서는 정렬 전id DESC기준 페이지 슬라이스의 마지막Club.id로 계산하고, 화면 노출용 순서만 별도 리스트에서 재정렬해야 합니다.수정 예시
- List<Club> clubs = new java.util.ArrayList<>(clubRepository.findClubsByCursor(cursor, pageable)); + List<Club> clubs = new java.util.ArrayList<>(clubRepository.findClubsByCursor(cursor, pageable)); boolean hasNext = clubs.size() > size; if (hasNext) { clubs = clubs.subList(0, size); } + + Long nextCursor = clubs.isEmpty() + ? null + : clubs.get(clubs.size() - 1).getId(); // 유저 관심 종목 조회 후 일치하는 동호회 상단 정렬 List<com.be.sportizebe.common.enums.SportType> interestTypes = userRepository.findById(userId) .map(user -> user.getInterestType()) .orElse(java.util.Collections.emptyList()); clubs.sort(java.util.Comparator.comparingInt(club -> interestTypes.contains(club.getClubType()) ? 0 : 1)); List<ClubListItemResponse> items = clubs.stream() .map(club -> { int memberCount = clubMemberRepository.countByClubId(club.getId()); return ClubListItemResponse.from(club, memberCount); }) .toList(); - - Long nextCursor = items.isEmpty() - ? null - : items.get(items.size() - 1).clubId(); return new ClubScrollResponse(items, nextCursor, hasNext);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/be/sportizebe/domain/club/service/ClubServiceImpl.java` around lines 149 - 161, Compute nextCursor from the original page slice before reordering by interest priority: capture the last Club.id from the unsorted clubs list (the slice returned by the repository which is ordered by id DESC) and assign that to nextCursor, then perform the interestTypes-based sort and map to ClubListItemResponse; update references in ClubServiceImpl (variables: clubs, nextCursor, items) so nextCursor uses clubs.get(...).getId() before the Comparator.sort is applied.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/java/com/be/sportizebe/domain/club/controller/ClubController.java`:
- Around line 67-72: The public GET endpoint ClubController.getClubs currently
calls userAuthInfo.getId() which can be null for unauthenticated requests; make
this null-safe by checking userAuthInfo before calling getId() and pass a
nullable Long (e.g., userId = userAuthInfo != null ? userAuthInfo.getId() :
null) into clubService.getClubsByScroll, and update the service method
signature/handling (clubService.getClubsByScroll) to accept and treat a null
userId as an anonymous request (use non-personalized sorting/behavior).
Alternatively, if you intend to require authentication, lock down the endpoint
in SecurityConfig instead (but do not leave the direct getId() call unchanged).
In `@src/main/java/com/be/sportizebe/domain/club/service/ClubServiceImpl.java`:
- Around line 134-150: Current implementation in ClubServiceImpl sorts
interestTypes in-memory after calling findClubsByCursor, so DB paging
(findClubsByCursor in ClubRepository) still slices by id DESC and can omit
interest-matching clubs from the page; fix by pushing the interest-priority
ordering into the repository query or by changing the cursor to include the
match flag: modify ClubRepository.findClubsByCursor (or add a new method like
findClubsByCursorWithInterest) to accept the user's interestTypes or userId and
apply ORDER BY (CASE WHEN club_type IN (:interestTypes) THEN 0 ELSE 1 END), id
DESC (or equivalent JPQL/SQL), or redesign the cursor to be (isMatch, id) so
paging respects interest matching; update ClubServiceImpl to call the new
repository method instead of sorting post-fetch.
---
Outside diff comments:
In `@src/main/java/com/be/sportizebe/domain/club/service/ClubServiceImpl.java`:
- Around line 149-161: Compute nextCursor from the original page slice before
reordering by interest priority: capture the last Club.id from the unsorted
clubs list (the slice returned by the repository which is ordered by id DESC)
and assign that to nextCursor, then perform the interestTypes-based sort and map
to ClubListItemResponse; update references in ClubServiceImpl (variables: clubs,
nextCursor, items) so nextCursor uses clubs.get(...).getId() before the
Comparator.sort is applied.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 56faf46b-ad1a-4b6a-8991-407929491b24
📒 Files selected for processing (3)
src/main/java/com/be/sportizebe/domain/club/controller/ClubController.javasrc/main/java/com/be/sportizebe/domain/club/service/ClubService.javasrc/main/java/com/be/sportizebe/domain/club/service/ClubServiceImpl.java
| public ResponseEntity<BaseResponse<ClubScrollResponse>> getClubs( | ||
| @RequestParam(required = false) Long cursor, | ||
| @RequestParam(defaultValue = "20") int size | ||
| @RequestParam(defaultValue = "20") int size, | ||
| @AuthenticationPrincipal UserAuthInfo userAuthInfo | ||
| ) { | ||
| ClubScrollResponse response = clubService.getClubsByScroll(cursor, size); | ||
| ClubScrollResponse response = clubService.getClubsByScroll(cursor, size, userAuthInfo.getId()); |
There was a problem hiding this comment.
공개 조회 엔드포인트에서 바로 NPE 가 날 수 있습니다.
GET /api/** 는 src/main/java/com/be/sportizebe/global/security/SecurityConfig.java:36-44 에서 permitAll() 이고, src/main/java/com/be/sportizebe/global/jwt/JwtAuthenticationFilter.java:32-72 도 토큰이 없으면 principal 을 채우지 않습니다. 그래서 여기의 userAuthInfo 는 null 일 수 있는데, 바로 getId() 를 호출해서 비인증 조회가 500 으로 끝납니다. 이 엔드포인트를 인증 필수로 바꾸거나, principal 이 없으면 비개인화 정렬로 내려가도록 controller/service 를 함께 null-safe 하게 처리해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/com/be/sportizebe/domain/club/controller/ClubController.java`
around lines 67 - 72, The public GET endpoint ClubController.getClubs currently
calls userAuthInfo.getId() which can be null for unauthenticated requests; make
this null-safe by checking userAuthInfo before calling getId() and pass a
nullable Long (e.g., userId = userAuthInfo != null ? userAuthInfo.getId() :
null) into clubService.getClubsByScroll, and update the service method
signature/handling (clubService.getClubsByScroll) to accept and treat a null
userId as an anonymous request (use non-personalized sorting/behavior).
Alternatively, if you intend to require authentication, lock down the endpoint
in SecurityConfig instead (but do not leave the direct getId() call unchanged).
| Pageable pageable = PageRequest.of(0, size + 1); | ||
|
|
||
| List<Club> clubs = clubRepository.findClubsByCursor(cursor, pageable); | ||
| List<Club> clubs = new java.util.ArrayList<>(clubRepository.findClubsByCursor(cursor, pageable)); | ||
|
|
||
| boolean hasNext = clubs.size() > size; | ||
|
|
||
| if (hasNext) { | ||
| clubs = clubs.subList(0, size); | ||
| } | ||
|
|
||
| // 유저 관심 종목 조회 후 일치하는 동호회 상단 정렬 | ||
| List<com.be.sportizebe.common.enums.SportType> interestTypes = userRepository.findById(userId) | ||
| .map(user -> user.getInterestType()) | ||
| .orElse(java.util.Collections.emptyList()); | ||
|
|
||
| clubs.sort(java.util.Comparator.comparingInt(club -> | ||
| interestTypes.contains(club.getClubType()) ? 0 : 1)); |
There was a problem hiding this comment.
현재 로직은 “관심 종목 우선 정렬”을 전체 목록 기준으로 보장하지 못합니다.
findClubsByCursor 는 src/main/java/com/be/sportizebe/domain/club/repository/ClubRepository.java:14-19 에서 먼저 id DESC 로 페이지를 잘라 오고, 그 결과 집합 안에서만 다시 정렬합니다. 그래서 관심 종목 클럽이 더 오래된 데이터에 있으면 첫 페이지로 끌어올릴 수 없습니다. 요구사항이 실제 정렬 기준이라면 정렬을 DB 쿼리 단계로 내리거나, 커서 자체를 (관심사 매치 여부, id) 기준으로 다시 설계해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/com/be/sportizebe/domain/club/service/ClubServiceImpl.java`
around lines 134 - 150, Current implementation in ClubServiceImpl sorts
interestTypes in-memory after calling findClubsByCursor, so DB paging
(findClubsByCursor in ClubRepository) still slices by id DESC and can omit
interest-matching clubs from the page; fix by pushing the interest-priority
ordering into the repository query or by changing the cursor to include the
match flag: modify ClubRepository.findClubsByCursor (or add a new method like
findClubsByCursorWithInterest) to accept the user's interestTypes or userId and
apply ORDER BY (CASE WHEN club_type IN (:interestTypes) THEN 0 ELSE 1 END), id
DESC (or equivalent JPQL/SQL), or redesign the cursor to be (isMatch, id) so
paging respects interest matching; update ClubServiceImpl to call the new
repository method instead of sorting post-fetch.
#️⃣ Issue Number
📝 요약(Summary)
GET /api/clubs) 시 로그인한 유저의 관심 종목(interestType)과 일치하는 동호회를 상단에 우선 노출하도록 개선기존에는 단순 id 내림차순 정렬이었으나, 관심 종목 기반 정렬을 추가
🛠️ PR 유형
어떤 변경 사항이 있나요?
📸스크린샷 (선택)
💬 공유사항 to 리뷰어
✅ PR Checklist
PR이 다음 요구 사항을 충족하는지 확인하세요.
Summary by CodeRabbit