Skip to content

Conversation

@Hyun0828
Copy link
Collaborator

@Hyun0828 Hyun0828 commented Aug 12, 2025

PR 타입(하나 이상의 PR 타입을 선택해주세요)

  • 기능 추가
  • 기능 삭제
  • 버그 수정
  • 의존성, 환경 변수, 빌드 관련 코드 업데이트
  • 리팩터링

반영 브랜치

dev -> main

작업 내용

사용자 이름 새싹으로 수정, 업무왕 API

테스트 결과

Summary by CodeRabbit

  • New Features
    • 상위 활동 멤버에게 ‘할 일 정복자’ 배지 부여가 지원됩니다.
    • 댓글/알림에서 역할 표기를 제거하고 닉네임 뒤에 “ 새싹”으로 통일해 표시합니다.
  • Bug Fixes
    • 본인 게시물/댓글에 대한 좋아요·댓글 알림이 발생하지 않도록 보완했습니다.
    • 작업 생성 요청 처리의 오타를 수정해 생성이 안정적으로 동작합니다.

@coderabbitai
Copy link

coderabbitai bot commented Aug 12, 2025

Walkthrough

주요 변경은 신규 “할 일 배지” 부여 엔드포인트 추가, Task 통계 질의/서비스 확장, Task 생성 흐름 메서드명/컨버터 시그니처 정리, 알림/댓글 전반에서 Role 제거 및 닉네임 접미사 고정(" 새싹") 적용, 그리고 관련 DTO/리포지토리 JPQL 투영 및 서비스 호출부 시그니처 일괄 수정입니다.

Changes

Cohort / File(s) Change Summary
Badge: Task 배지 부여 기능
src/.../badge/controller/PotBadgeMemberController.java, src/.../badge/service/BadgeService.java, src/.../badge/service/BadgeServiceImpl.java
POST /badges/{potId}/task-badge 엔드포인트 추가; BadgeService에 assignTaskBadgeToTopMembers 추가 및 getDefaultBadge→getBadge(Long)로 변경; 구현체에서 TaskQueryService 도입, 상위 2명 선별 후 배지(2L) 부여 로직 추가.
Task 모듈 업데이트
src/.../task/controller/TaskController.java, src/.../task/service/TaskCommandService.java, src/.../task/service/TaskCommandServiceImpl.java, src/.../task/converter/TaskBoardConverter.java, src/.../task/repository/TaskRepository.java, src/.../task/repository/TaskboardRepository.java, src/.../task/service/TaskQueryService.java, src/.../task/service/TaskQueryServiceImpl.java, src/.../task/entity/Taskboard.java
creatTask→createTask로 메서드명 정정; TaskBoardConverter가 User를 인자로 받아 Taskboard 빌드 시 설정; TaskboardRepository에 countByPotId 추가; TaskRepository에 상위 2명 PotMember 조회 쿼리 추가; TaskQueryService(+Impl)에 위 메서드들 추가; 내부 헬퍼 시그니처 void로 변경 및 응답 participants 설정; 사소한 정리(toList 등).
알림/댓글: Role 제거 및 “ 새싹” 접미사 고정
src/.../notification/service/NotificationCommandService.java, src/.../notification/converter/NotificationConverter.java, src/.../notification/dto/NotificationDto.java, src/.../notification/dto/NotificationResponseDto.java, src/.../notification/repository/FeedLikeNotificationRepository.java, src/.../notification/repository/FeedCommentNotificationRepository.java, src/.../notification/repository/PotApplicationNotificationRepository.java, src/.../notification/repository/PotCommentNotificationRepository.java, src/.../feed/service/FeedCommandServiceImpl.java, src/.../feed/service/FeedCommentCommandService.java, src/.../feed/converter/FeedCommentConverter.java, src/.../feed/dto/FeedCommentResponseDto.java, src/.../feed/dto/FeedCommentDto.java, src/.../feed/dto/FeedCommentInfoDto.java, src/.../feed/repository/FeedCommentRepository.java, src/.../pot/service/potApplication/PotApplicationCommandServiceImpl.java, src/.../pot/service/potComment/PotCommentCommandService.java, src/.../pot/converter/PotCommentConverter.java, src/.../pot/dto/PotCommentResponseDto.java, src/.../pot/dto/PotCommentDto.java, src/.../pot/repository/PotCommentRepository.java
Notification 생성 메서드들에서 Role 파라미터 제거 및 시그니처 수정; Converter/DTO에서 role 필드 제거 및 userName에 " 새싹" 접미사 적용; 관련 JPQL new DTO 투영에서 role 인자 제거(생성자 시그니처 변경); 서비스 호출부 일괄 수정; 일부 알림 생성 억제 조건 추가(작성자 본인 등).
기타 정리
src/.../pot/service/pot/PotCommandServiceImpl.java, src/.../user/service/UserCommandServiceImpl.java
불필요한 공백 제거(무행동 변경); 닉네임 생성 반환값에서 " 새싹" 접미사 제거.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Controller as PotBadgeMemberController
  participant BadgeSvc as BadgeService
  participant TaskQuery as TaskQueryService
  participant Repo as Repositories

  Client->>Controller: POST /badges/{potId}/task-badge
  Controller->>BadgeSvc: assignTaskBadgeToTopMembers(potId)
  BadgeSvc->>TaskQuery: getTop2TaskCountByPotMemberId(potMemberIds)
  TaskQuery->>Repo: Query top2 by task count
  Repo-->>TaskQuery: List<PotMember>(top2)
  BadgeSvc->>Repo: Load badge(2L), save PotMemberBadge for top2
  BadgeSvc-->>Controller: void
  Controller-->>Client: 200 ApiResponse.onSuccess(null)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

✨feat, :art: refactor

Suggested reviewers

  • leesumin0526

Poem

(•̀ᴗ•́)و ̑̑ hoppity-hop, 코드 박자에 맞춰 뛰네
새싹 닉네임, 반짝! 배지는 위로 향해
두 발 앞서 Task 왕, 톱2에 꽁충!
알림은 가벼워졌고, 흐름은 청춘
오늘도 PR 정원에, 토끼는 쿵짝쿵 🌱🐇

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

🔭 Outside diff range comments (5)
src/main/java/stackpot/stackpot/user/service/UserCommandServiceImpl.java (1)

279-298: '새싹' 접미사 중복 방지: trimNickname에 '새싹' 제거 로직 포함 제안

사용자 입력에 이미 ‘새싹’이 포함된 경우(수동 입력/붙여넣기 등), 표시 계층에서 다시 ‘ 새싹’을 붙이면 “새싹 새싹” 중복이 발생할 수 있습니다. 저장 전 정규화에서 ‘새싹’도 제거하도록 보강을 권장합니다.

다음과 같이 보완해 주세요(핵심만 발췌):

private String trimNickname(String nickname) {
    log.info("닉네임 생성 전 닉네임: {}", nickname);
    nickname = nickname.trim();

    // 표시 접미/금지 단어까지 포함해 정규화
    String[] suffixesToStrip = {"버섯", "양파", "브로콜리", "당근", "새싹"};

    for (String s : suffixesToStrip) {
        if (nickname.contains(" " + s)) {
            return nickname.replace(" " + s, "").trim();
        } else if (nickname.endsWith(s)) {
            return nickname.substring(0, nickname.length() - s.length()).trim();
        }
    }
    log.info("닉네임 생성 후 닉네임: {}", nickname);
    return nickname;
}
src/main/java/stackpot/stackpot/notification/converter/NotificationConverter.java (1)

6-6: 사용하지 않는 import 제거 필요

Role 클래스가 더 이상 사용되지 않으므로 이 import 문을 제거해야 합니다.

-import stackpot.stackpot.user.entity.enums.Role;
src/main/java/stackpot/stackpot/pot/converter/PotCommentConverter.java (1)

6-6: 사용하지 않는 import 제거 필요

Role 클래스가 더 이상 사용되지 않으므로 이 import 문을 제거해야 합니다.

-import stackpot.stackpot.user.entity.enums.Role;
src/main/java/stackpot/stackpot/feed/converter/FeedCommentConverter.java (1)

6-6: 사용하지 않는 import 제거 필요

Role 클래스가 더 이상 사용되지 않으므로 이 import 문을 제거해야 합니다.

-import stackpot.stackpot.user.entity.enums.Role;
src/main/java/stackpot/stackpot/task/service/TaskCommandServiceImpl.java (1)

39-64: createTask 트랜잭션 누락 — 부분 저장 가능성

Taskboard 저장 이후 Task 다건 저장이 이어지므로 원자성 보장을 위해 트랜잭션이 필요합니다. 동일 클래스 내 다른 메서드에는 @Transactional이 있으나 createTask에는 없습니다.

-    @Override
-    public MyPotTaskResponseDto createTask(Long potId, MyPotTaskRequestDto.create request) {
+    @Override
+    @Transactional
+    public MyPotTaskResponseDto createTask(Long potId, MyPotTaskRequestDto.create request) {

추가로, 수정 API(modifyTask)에서는 참가자를 findAllById로 조회하고 있어 다른 팟의 멤버가 섞일 수 있습니다. createTask와 동일하게 팟 기준 필터링(findByPotId 후 filter) 또는 전용 Repository 메서드로 제한하는 것을 권장합니다.

🧹 Nitpick comments (15)
src/main/java/stackpot/stackpot/user/service/UserCommandServiceImpl.java (1)

493-511: 프롬프트 내용과 라인 246 변경은 합리적으로 일치합니다

프롬프트가 “수식어만” 요구하고 ‘새싹’ 자체 사용을 금지하므로, 저장/응답에서 접미사를 붙이지 않는 현재 변경과 방향성이 일치합니다.

프롬프트 내 Markdown 굵게 표기(**)는 LLM/요약기 구현에 따라 불필요 토큰 증가/혼동이 될 수 있습니다. 일반 문장으로 간결화하면 안정성이 올라갑니다.

src/main/java/stackpot/stackpot/notification/converter/NotificationConverter.java (1)

21-21: 하드코딩된 "새싹" 문자열을 상수로 추출 고려

여러 곳에서 반복되는 " 새싹" 문자열을 클래스 레벨 상수로 추출하면 유지보수성이 향상됩니다. 추후 이 값을 변경해야 할 경우 한 곳에서만 수정하면 됩니다.

 @Component
 public class NotificationConverter {
 
     private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy년 M월 d일 H:mm");
+    private static final String USER_SUFFIX = " 새싹";
 
     public NotificationResponseDto.UnReadNotificationDto toUnReadNotificationDto(NotificationDto.UnReadNotificationDto unReadNotificationDto) {
         return NotificationResponseDto.UnReadNotificationDto.builder()
                 .notificationId(unReadNotificationDto.getNotificationId())
                 .potOrFeedId(unReadNotificationDto.getPotOrFeedId())
-                .userName(unReadNotificationDto.getUserName() + " 새싹")
+                .userName(unReadNotificationDto.getUserName() + USER_SUFFIX)
                 .type(unReadNotificationDto.getType())
                 .content(unReadNotificationDto.getContent())
                 .createdAt(unReadNotificationDto.getCreatedAt().format(DATE_FORMATTER))
                 .build();
     }
 
     public NotificationResponseDto.UnReadNotificationDto toUnReadNotificationDto(
             Long notificationId, Long potOrFeedId, String userName, String type, String content, LocalDateTime createdAt) {
         return NotificationResponseDto.UnReadNotificationDto.builder()
                 .notificationId(notificationId)
                 .potOrFeedId(potOrFeedId)
-                .userName(userName + " 새싹")
+                .userName(userName + USER_SUFFIX)
                 .type(type)
                 .content(content)
                 .createdAt(createdAt.format(DATE_FORMATTER))
                 .build();
     }

Also applies to: 33-33

src/main/java/stackpot/stackpot/pot/converter/PotCommentConverter.java (1)

22-22: 하드코딩된 "새싹" 문자열을 상수로 추출 고려

여러 메서드에서 반복되는 " 새싹" 문자열을 클래스 레벨 상수로 추출하면 유지보수성이 향상됩니다.

 @Component
 public class PotCommentConverter {
 
     private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy년 M월 d일 H:mm");
+    private static final String USER_SUFFIX = " 새싹";
 
     public PotCommentResponseDto.PotCommentCreateDto toPotCommentCreateDto(Long userId, String userName, Boolean isWriter,
                                                                            Long commentId, String comment, LocalDateTime createdAt) {
         return PotCommentResponseDto.PotCommentCreateDto.builder()
                 .userId(userId)
-                .userName(userName + " 새싹")
+                .userName(userName + USER_SUFFIX)
                 .isWriter(isWriter)
                 .commentId(commentId)
                 .comment(comment)
                 .createdAt(createdAt)
                 .build();
     }
 
     public PotCommentResponseDto.PotReplyCommentCreateDto toPotReplyCommentCreateDto(Long userId, String userName, Boolean isWriter,
                                                                                      Long commentId, String comment, Long parentCommentId,
                                                                                      LocalDateTime createdAt) {
         return PotCommentResponseDto.PotReplyCommentCreateDto.builder()
                 .userId(userId)
-                .userName(userName + " 새싹")
+                .userName(userName + USER_SUFFIX)
                 .isWriter(isWriter)
                 .commentId(commentId)
                 .comment(comment)
                 .parentCommentId(parentCommentId)
                 .createdAt(createdAt)
                 .build();
     }
 
     // ... 중간 코드 생략 ...
 
     public PotCommentResponseDto.AllPotCommentDto toAllPotCommentDto(PotCommentDto.PotCommentInfoDto dto, Long currentUserId) {
         return PotCommentResponseDto.AllPotCommentDto.builder()
                 .userId(dto.getUserId())
-                .userName(dto.getUserName() + " 새싹")
+                .userName(dto.getUserName() + USER_SUFFIX)
                 .isCommentWriter(Objects.equals(dto.getUserId(), currentUserId))
                 .isPotWriter(Objects.equals(dto.getPotWriterId(), dto.getUserId()))
                 .commentId(dto.getCommentId())
                 .comment(dto.getComment())
                 .parentCommentId(dto.getParentCommentId())
                 .createdAt(dto.getCreatedAt().format(DATE_FORMATTER))
                 .children(new ArrayList<>())
                 .build();
     }

Also applies to: 35-35, 53-53

src/main/java/stackpot/stackpot/feed/converter/FeedCommentConverter.java (2)

21-21: 하드코딩된 "새싹" 문자열을 상수로 추출 고려

여러 메서드에서 반복되는 " 새싹" 문자열을 클래스 레벨 상수로 추출하면 유지보수성이 향상됩니다. 또한 다른 Converter 클래스들과 공통 상수를 공유하는 것도 고려해볼 수 있습니다.

 @Component
 public class FeedCommentConverter {
 
     private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy년 M월 d일 H:mm");
+    private static final String USER_SUFFIX = " 새싹";
 
     public FeedCommentResponseDto.AllFeedCommentDto toAllFeedCommentDto(FeedCommentDto.FeedCommentInfoDto dto, Long currentUserId) {
         return FeedCommentResponseDto.AllFeedCommentDto.builder()
                 .userId(dto.getUserId())
-                .userName(dto.getUserName() + " 새싹")
+                .userName(dto.getUserName() + USER_SUFFIX)
                 .isCommentWriter(Objects.equals(dto.getUserId(), currentUserId))
                 .isFeedWriter(Objects.equals(dto.getFeedWriterId(), dto.getUserId()))
                 .commentId(dto.getCommentId())
                 .comment(dto.getComment())
                 .parentCommentId(dto.getParentCommentId())
                 .createdAt(dto.getCreatedAt().format(DATE_FORMATTER))
                 .children(new ArrayList<>())
                 .build();
     }
 
     public FeedCommentResponseDto.FeedCommentCreateDto toFeedCommentCreateDto(Long userId, String userName, Boolean isWriter,
                                                                               Long commentId, String comment, LocalDateTime createdAt) {
         return FeedCommentResponseDto.FeedCommentCreateDto.builder()
                 .userId(userId)
-                .userName(userName + " 새싹")
+                .userName(userName + USER_SUFFIX)
                 .isWriter(isWriter)
                 .commentId(commentId)
                 .comment(comment)
                 .createdAt(createdAt)
                 .build();
     }
 
     public FeedCommentResponseDto.FeedReplyCommentCreateDto toFeedReplyCommentCreateDto(Long userId, String userName, Boolean isWriter,
                                                                                         Long commentId, String comment, Long parentCommentId,
                                                                                         LocalDateTime createdAt) {
         return FeedCommentResponseDto.FeedReplyCommentCreateDto.builder()
                 .userId(userId)
-                .userName(userName + " 새싹")
+                .userName(userName + USER_SUFFIX)
                 .isWriter(isWriter)
                 .commentId(commentId)
                 .comment(comment)
                 .parentCommentId(parentCommentId)
                 .createdAt(createdAt)
                 .build();
     }

Also applies to: 36-36, 49-49


16-16: 공통 상수 관리 전략 고려

여러 Converter 클래스에서 동일한 DateTimeFormatter와 사용자 접미사를 사용하고 있습니다. 이러한 공통 상수들을 중앙화된 Constants 클래스로 관리하는 것을 고려해보세요.

공통 상수 관리를 위한 클래스 생성 예시:

public class ConverterConstants {
    public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy년 M월 d일 H:mm");
    public static final String USER_SUFFIX = " 새싹";
}
src/main/java/stackpot/stackpot/notification/service/NotificationCommandService.java (1)

29-29: 사용하지 않는 import 정리 필요

Line 29에서 Role enum을 import하고 있지만, 코드 전체에서 더 이상 사용되지 않습니다.

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

-import stackpot.stackpot.user.entity.enums.Role;
src/main/java/stackpot/stackpot/task/repository/TaskRepository.java (1)

29-35: 메서드명에 ‘Top2’ 하드코딩은 혼동 유발

실제 제한은 Pageable로 제어되므로 메서드명에서 숫자를 고정하지 않는 편이 유지보수에 유리합니다. 예: getTopTaskCountByPotMemberId(...)

src/main/java/stackpot/stackpot/task/converter/TaskBoardConverter.java (2)

26-34: Null-safe 보강 제안: 필수 필드 누락 시 조기 검증

deadLine/status/user는 DB 제약(nullable=false 등)과 연계되어 NPE/제약 위반 가능성이 있습니다. Converter 수준에서 선제 검증하거나, 도메인 기본값(예: status=OPEN)을 부여하는 쪽을 권장합니다.

예시:

 public class TaskBoardConverter {
-    public Taskboard toTaskboard(Pot pot, MyPotTaskRequestDto.create request, User user) {
+    public Taskboard toTaskboard(Pot pot, MyPotTaskRequestDto.create request, User user) {
+        Objects.requireNonNull(pot, "pot must not be null");
+        Objects.requireNonNull(user, "user must not be null");
+        Objects.requireNonNull(request.getDeadline(), "deadline must not be null");
+        Objects.requireNonNull(request.getTaskboardStatus(), "status must not be null");
         return Taskboard.builder()
                 .title(request.getTitle())
                 .description(request.getDescription())
                 .deadLine(request.getDeadline())
                 .status(request.getTaskboardStatus())
                 .pot(pot)
                 .user(user)
                 .build();
     }

26-34: ‘닉네임 접미사 “새싹” 고정’ 요구사항과의 정합성 재점검 필요

본 파일 전반에서 RoleNameMapper/role 기반 닉네임 접미사가 여전히 사용되고 있습니다(예: creatorNickname/participants 등). 본 PR 목적(닉네임 접미사 “ 새싹” 고정)과 상충할 수 있으니, 해당 DTO 변환 로직 일괄 수정 범위를 재검토해 주세요.

원하시면 본 Converter와 관련 DTO에 대해 “닉네임 + ' 새싹'” 형태로 일괄 변경하는 패치 제안을 드리겠습니다.

src/main/java/stackpot/stackpot/task/service/TaskQueryService.java (1)

17-17: 메서드명의 ‘Top2’ 하드코딩은 확장성 저하

호출부에서 PageRequest.of(0, 2)로 제약을 주고 있으므로, 인터페이스명을 일반화하거나 limit를 파라미터로 받는 시그니처로 확장하는 것을 검토하세요.

src/main/java/stackpot/stackpot/badge/service/BadgeService.java (1)

8-8: 업무왕 배지 부여: 중복 부여/멱등성 보장 확인

이미 배지를 보유한 멤버에 대한 중복 삽입 방지(UK 제약 또는 선조회)와 트랜잭션 경계에서의 일관성 보장을 확인해 주세요.

src/main/java/stackpot/stackpot/badge/controller/PotBadgeMemberController.java (1)

37-44: 엔드포인트 경로/변수 네이밍 일관성 및 표현력 개선 제안

  • 같은 컨트롤러 내에서 GET /pots/{pot_id}(snake_case)와 POST /{potId}(camelCase)를 혼용하고 있습니다. 경로 변수 네이밍 컨벤션을 통일하는 것을 권장합니다.
  • 배지 부여 액션을 보다 명확히 하기 위해 POST /{potId} 대신 POST /{potId}/todo-badge 등 구체적인 리소스 경로를 사용하는 것을 제안합니다. 이미 /{potId}/task-badge가 있으므로 일관성이 좋아지고, 추후 POST 액션 충돌 가능성도 줄어듭니다.

예시 수정안:

-    @PostMapping("/{potId}")
+    @PostMapping("/{potId}/todo-badge")
src/main/java/stackpot/stackpot/task/service/TaskQueryServiceImpl.java (1)

75-75: 컨버터 메서드 네이밍 일관성

동일 클래스에서 toDtotoDTO가 혼재합니다. 팀 컨벤션에 맞춰 일관된 네이밍을 사용하는 것이 가독성과 유지보수에 유리합니다.

src/main/java/stackpot/stackpot/badge/service/BadgeServiceImpl.java (1)

63-71: 배지 ID 하드코딩 제거 권장

getBadge(1L)은 매직 넘버로 의도가 불분명합니다. 상수/열거형/코드명 기반 조회로 의미를 드러내세요.

예시:

  • 클래스 상수 도입: private static final long TODO_BADGE_ID = 1L;
  • 또는 코드명 기반: badgeRepository.findByCode("TODO")

필드 상수 예시(클래스 상단에 추가):

private static final long TODO_BADGE_ID = 1L;
private static final long TASK_BADGE_ID = 2L;

사용부:

- Badge badge = getBadge(1L);
+ Badge badge = getBadge(TODO_BADGE_ID);
src/main/java/stackpot/stackpot/task/service/TaskCommandServiceImpl.java (1)

118-126: 빈 참여자 처리 최적화

참여자 목록이 빈 경우에도 saveAll(emptyList) 호출이 이루어집니다. 무해하지만 불필요한 호출을 줄일 수 있습니다.

-        taskRepository.saveAll(tasks);
+        if (!tasks.isEmpty()) {
+            taskRepository.saveAll(tasks);
+        }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e64fbb and 8a84853.

📒 Files selected for processing (35)
  • src/main/java/stackpot/stackpot/badge/controller/PotBadgeMemberController.java (1 hunks)
  • src/main/java/stackpot/stackpot/badge/service/BadgeService.java (1 hunks)
  • src/main/java/stackpot/stackpot/badge/service/BadgeServiceImpl.java (4 hunks)
  • src/main/java/stackpot/stackpot/feed/converter/FeedCommentConverter.java (2 hunks)
  • src/main/java/stackpot/stackpot/feed/dto/FeedCommentDto.java (0 hunks)
  • src/main/java/stackpot/stackpot/feed/dto/FeedCommentInfoDto.java (0 hunks)
  • src/main/java/stackpot/stackpot/feed/dto/FeedCommentResponseDto.java (0 hunks)
  • src/main/java/stackpot/stackpot/feed/repository/FeedCommentRepository.java (1 hunks)
  • src/main/java/stackpot/stackpot/feed/service/FeedCommandServiceImpl.java (1 hunks)
  • src/main/java/stackpot/stackpot/feed/service/FeedCommentCommandService.java (2 hunks)
  • src/main/java/stackpot/stackpot/notification/converter/NotificationConverter.java (1 hunks)
  • src/main/java/stackpot/stackpot/notification/dto/NotificationDto.java (0 hunks)
  • src/main/java/stackpot/stackpot/notification/dto/NotificationResponseDto.java (0 hunks)
  • src/main/java/stackpot/stackpot/notification/repository/FeedCommentNotificationRepository.java (1 hunks)
  • src/main/java/stackpot/stackpot/notification/repository/FeedLikeNotificationRepository.java (1 hunks)
  • src/main/java/stackpot/stackpot/notification/repository/PotApplicationNotificationRepository.java (1 hunks)
  • src/main/java/stackpot/stackpot/notification/repository/PotCommentNotificationRepository.java (1 hunks)
  • src/main/java/stackpot/stackpot/notification/service/NotificationCommandService.java (7 hunks)
  • src/main/java/stackpot/stackpot/pot/converter/PotCommentConverter.java (2 hunks)
  • src/main/java/stackpot/stackpot/pot/dto/PotCommentDto.java (0 hunks)
  • src/main/java/stackpot/stackpot/pot/dto/PotCommentResponseDto.java (0 hunks)
  • src/main/java/stackpot/stackpot/pot/repository/PotCommentRepository.java (1 hunks)
  • src/main/java/stackpot/stackpot/pot/service/pot/PotCommandServiceImpl.java (0 hunks)
  • src/main/java/stackpot/stackpot/pot/service/potApplication/PotApplicationCommandServiceImpl.java (1 hunks)
  • src/main/java/stackpot/stackpot/pot/service/potComment/PotCommentCommandService.java (2 hunks)
  • src/main/java/stackpot/stackpot/task/controller/TaskController.java (1 hunks)
  • src/main/java/stackpot/stackpot/task/converter/TaskBoardConverter.java (1 hunks)
  • src/main/java/stackpot/stackpot/task/entity/Taskboard.java (1 hunks)
  • src/main/java/stackpot/stackpot/task/repository/TaskRepository.java (2 hunks)
  • src/main/java/stackpot/stackpot/task/repository/TaskboardRepository.java (1 hunks)
  • src/main/java/stackpot/stackpot/task/service/TaskCommandService.java (1 hunks)
  • src/main/java/stackpot/stackpot/task/service/TaskCommandServiceImpl.java (3 hunks)
  • src/main/java/stackpot/stackpot/task/service/TaskQueryService.java (2 hunks)
  • src/main/java/stackpot/stackpot/task/service/TaskQueryServiceImpl.java (4 hunks)
  • src/main/java/stackpot/stackpot/user/service/UserCommandServiceImpl.java (1 hunks)
💤 Files with no reviewable changes (8)
  • src/main/java/stackpot/stackpot/pot/dto/PotCommentResponseDto.java
  • src/main/java/stackpot/stackpot/feed/dto/FeedCommentDto.java
  • src/main/java/stackpot/stackpot/pot/dto/PotCommentDto.java
  • src/main/java/stackpot/stackpot/feed/dto/FeedCommentResponseDto.java
  • src/main/java/stackpot/stackpot/notification/dto/NotificationResponseDto.java
  • src/main/java/stackpot/stackpot/notification/dto/NotificationDto.java
  • src/main/java/stackpot/stackpot/pot/service/pot/PotCommandServiceImpl.java
  • src/main/java/stackpot/stackpot/feed/dto/FeedCommentInfoDto.java
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/main/java/stackpot/stackpot/notification/converter/NotificationConverter.java (1)
src/main/java/stackpot/stackpot/notification/dto/NotificationResponseDto.java (1)
  • NotificationResponseDto (11-25)
src/main/java/stackpot/stackpot/pot/converter/PotCommentConverter.java (1)
src/main/java/stackpot/stackpot/pot/dto/PotCommentResponseDto.java (1)
  • PotCommentResponseDto (12-64)
src/main/java/stackpot/stackpot/feed/converter/FeedCommentConverter.java (1)
src/main/java/stackpot/stackpot/feed/dto/FeedCommentResponseDto.java (1)
  • FeedCommentResponseDto (12-64)
🔇 Additional comments (26)
src/main/java/stackpot/stackpot/user/service/UserCommandServiceImpl.java (2)

450-451: 이메일 사용자 표시 규칙 불일치 가능성 (Role.toVegetable 사용)

메일 알림에서 여전히 nickname + " " + Role.toVegetable(...) 형태를 사용합니다. 이번 PR 방향이 사용자 이름 접미를 " 새싹"으로 표준화하는 것이라면, 해당 부분도 동일 규칙으로 맞춰야 합니다. 반대로 역할/야채명을 유지한다면, 다른 표시 지점에서도 동일한 규칙을 유지해야 합니다.

제품/도메인 결정에 맞춰 일관된 “사용자 표시명” 규칙을 확정해 주세요. 필요 시 여기 로직을 "nickname + ' 새싹'" 또는 공용 Formatter로 치환하는 방안을 제안드립니다.


246-246: 닉네임 생성 API: 서버 사이드 접미사 미포함 — 프론트엔드 처리 여부 검증 필요

검증 결과:

  • UserCommandServiceImpl.java(246)의 createNickname()가 반환하는 NicknameResponseDto에는 " 새싹" 접미사가 없음
  • 그 외 모든 응답 변환기(UserConverter, Pot*, Feed*, NotificationConverter)에서는 일관되게 user.getNickname() + " 새싹" 형태로 서버 사이드에서 접미사 추가
  • 닉네임 생성용 프롬프트(getPromptForNewbie)에도 이미 “새싹” 사용 금지 로직이 있어 중복 부여 위험 없음

조치 요청:

  • 닉네임 생성 화면(nickname API)에서 프론트엔드" 새싹"을 붙여주고 있는지
  • 중복(“새싹 새싹”) 또는 미적용 사례가 없는지 수동으로 확인 부탁드립니다.
src/main/java/stackpot/stackpot/feed/repository/FeedCommentRepository.java (1)

17-17: JPQL 생성자 표현식: $ 표기법이 올바릅니다
JPA의 new 표현식은 클래스 로딩 시 binary name(Inner 클래스는 $ 구분자)을 사용하므로
FeedCommentDto$FeedCommentInfoDto가 맞습니다. 변경할 필요 없습니다.

Likely an incorrect or invalid review comment.

src/main/java/stackpot/stackpot/pot/repository/PotCommentRepository.java (1)

17-20: Role 필드 제거로 인한 일관된 변경 확인됨

JPQL 생성자 표현식에서 pc.user.role 파라미터가 제거되어 7개 인자에서 6개 인자로 변경된 것이 확인됩니다. 이는 PR 전반의 Role 제거 패턴과 일치합니다.

src/main/java/stackpot/stackpot/notification/repository/PotApplicationNotificationRepository.java (1)

16-20: 알림 DTO 생성자 파라미터 순서 변경 확인

pan.potApplication.user.nickname이 세 번째 파라미터로 이동하고 role 파라미터가 제거되었습니다. 이는 UnReadNotificationDto의 생성자가 6개 파라미터로 변경된 것과 일치하는 올바른 수정입니다.

src/main/java/stackpot/stackpot/feed/service/FeedCommandServiceImpl.java (1)

213-214: 알림 생성 메서드 호출부 업데이트 확인됨

createFeedLikeNotification 메서드 호출에서 role 파라미터가 제거되어 3개 인자(feedId, likeId, userId)만 전달하도록 변경되었습니다. 이는 NotificationCommandService의 시그니처 변경과 일치하는 올바른 수정입니다.

src/main/java/stackpot/stackpot/notification/repository/FeedCommentNotificationRepository.java (1)

16-24: JPQL 투영에서 role 필드 제거 확인됨

JPQL SELECT 구문에서 fcn.feedComment.user.role 파라미터가 제거되어 UnReadNotificationDto가 6개 파라미터로 생성되도록 변경되었습니다. 복잡한 WHERE 조건(부모 댓글 및 대댓글 처리)은 그대로 유지되어 기능상 문제없습니다.

src/main/java/stackpot/stackpot/pot/service/potApplication/PotApplicationCommandServiceImpl.java (1)

62-63: 팟 지원 알림 생성 호출부 업데이트 확인됨

createPotApplicationNotification 메서드 호출에서 user.getRole() 파라미터가 제거되고 user.getNickname()만 사용하도록 변경되었습니다. 이는 알림 시스템에서 Role 기반 식별자를 nickname 기반으로 변경하는 전체적인 리팩토링과 일치합니다.

src/main/java/stackpot/stackpot/notification/repository/FeedLikeNotificationRepository.java (1)

17-21: 피드 좋아요 알림 DTO 생성자 파라미터 정리 확인됨

JPQL 생성자 표현식에서 role 파라미터가 제거되어 6개 파라미터 (id, feedId, nickname, type, null, createdAt)로 UnReadNotificationDto를 생성하도록 변경되었습니다. 이는 전체 알림 시스템의 Role 제거 패턴과 일치합니다.

src/main/java/stackpot/stackpot/notification/repository/PotCommentNotificationRepository.java (1)

16-24: 팟 댓글 알림 JPQL 투영 업데이트 확인됨

SELECT 구문에서 pcn.potComment.user.role 파라미터가 제거되어 6개 파라미터로 UnReadNotificationDto를 생성하도록 변경되었습니다. 복잡한 WHERE 조건 로직(부모 댓글과 대댓글에 대한 알림 조건)은 그대로 유지되어 기능에 영향을 주지 않습니다.

src/main/java/stackpot/stackpot/pot/service/potComment/PotCommentCommandService.java (1)

51-52: 코드 변경사항이 적절합니다

Role 파라미터 제거와 관련된 메서드 시그니처 변경이 일관성 있게 적용되었습니다. NotificationCommandService와 PotCommentConverter의 메서드 호출이 새로운 시그니처에 맞게 올바르게 수정되었습니다.

Also applies to: 56-57, 76-77, 81-82

src/main/java/stackpot/stackpot/feed/service/FeedCommentCommandService.java (1)

50-51: 코드 변경사항이 적절합니다

Role 파라미터 제거와 관련된 메서드 시그니처 변경이 일관성 있게 적용되었습니다. NotificationCommandService와 FeedCommentConverter의 메서드 호출이 새로운 시그니처에 맞게 올바르게 수정되었습니다.

Also applies to: 55-56, 75-76, 80-81

src/main/java/stackpot/stackpot/notification/service/NotificationCommandService.java (5)

78-96: 부모 댓글 작성자에 대한 알림 제외 로직이 올바르게 구현됨

Line 83-85에서 부모 댓글 작성자가 현재 댓글을 작성한 경우 알림을 생성하지 않는 로직이 추가되었습니다. 이는 자기 자신에게 불필요한 알림을 방지하는 좋은 개선사항입니다.


103-117: 피드 좋아요 알림 생성 로직 확인

메서드 시그니처에서 Role 매개변수가 제거되고 feedLike.getUser().getNickname()을 사용하여 사용자 정보를 가져오도록 변경되었습니다. 구현이 일관성 있게 적용되었습니다.


124-141: 피드 댓글 알림에서도 부모 댓글 작성자 제외 로직 적용됨

Line 129-131에서 부모 댓글 작성자에 대한 알림 제외 로직이 PotComment와 동일하게 적용되었습니다. 일관성 있는 구현으로 평가됩니다.


59-71: createPotApplicationNotification 시그니처 변경 일관성 확인 완료

새로운 시그니처(createPotApplicationNotification(Long potId, Long applicationId, String userName))를 사용하는 호출부는 다음 한 곳뿐이며, 모두 potId, applicationId, user.getNickname() 형태로 올바르게 전달되고 있습니다. 추가 조치가 필요하지 않습니다.

  • src/main/java/stackpot/stackpot/pot/service/potApplication/PotApplicationCommandServiceImpl.java

94-95: 닉네임 접미사 " 새싹" 추가 로직 정상 작동 확인

NotificationConverter의 두 toUnReadNotificationDto 메서드에서 모두

  • unReadNotificationDto.getUserName() + " 새싹"
  • userName + " 새싹"
    로 구현되어 있어, 알림에 닉네임 뒤에 " 새싹"이 올바르게 붙습니다.
    따라서 추가 검증이나 수정은 필요 없습니다.
src/main/java/stackpot/stackpot/task/controller/TaskController.java (1)

42-42: Legacy 메서드 호출(creatTask) 잔여 검증 필요

rg -n "creatTask" -S 실행 결과 호출부가 검색되지 않았으나, 누락 가능성을 차단하기 위해 수동 검증을 권장드립니다:

  • IDE 전역 검색 또는 터미널에서 rg -n "creatTask" 명령어 실행 후 호출부가 전혀 남아 있지 않은지 확인하십시오.
src/main/java/stackpot/stackpot/task/service/TaskCommandService.java (1)

11-11: 메서드명 오탈자 정정 반영 OK

creatTask → createTask로의 변경, 시그니처 유지 모두 적절합니다.

src/main/java/stackpot/stackpot/task/converter/TaskBoardConverter.java (1)

26-34: 생성 시 User 주입으로 불변성/일관성 향상 — 좋습니다

Taskboard 생성 시점에 User를 설정하도록 변경되어 이후 set 호출 누락 리스크가 줄었습니다.

src/main/java/stackpot/stackpot/badge/service/BadgeService.java (1)

6-6: getBadge 반환 정책 점검

존재하지 않는 badgeId에 대한 처리(예외/Optional)는 구현부에서 명확히 규정되어야 합니다. 컨트롤러/서비스 상위 계층에서 BADGE_NOT_FOUND로 일관 처리되는지 확인 바랍니다.

src/main/java/stackpot/stackpot/task/service/TaskQueryServiceImpl.java (2)

132-135: getTaskBoardCountByPotId 메서드 유효성 확인 완료 – 수정 불필요합니다.

TaskboardRepository에

@Query("SELECT count(*) FROM Taskboard t WHERE t.pot.potId = :potId")
int countByPotId(@Param("potId") Long potId);

가 정의되어 있어 taskboardRepository.countByPotId(potId) 호출이 정상 작동합니다.


57-57: [확인 필요] Stream.toList() 사용 시 JDK 호환성 검토

Stream#toList()는 JDK 16 이상에서만 지원됩니다.
프로젝트의 타깃 JDK 버전을 아래 설정 파일에서 확인해주세요.

  • build.gradle / build.gradle.kts
  • gradle.properties
  • settings.gradle
  • pom.xml

만약 JDK 16 미만을 사용 중이라면 아래와 같이 Collectors.toList()로 변경을 권장합니다:

-                .toList();
+                .collect(Collectors.toList());
src/main/java/stackpot/stackpot/badge/service/BadgeServiceImpl.java (2)

36-39: getBadge 도입 매우 적절

직접 조회 + 예외 처리로 명확한 실패 시맨틱스를 보장합니다. 재사용성을 높이는 좋은 추상화입니다.


61-61: Stream.toList() 사용 적합 — JDK 17 도구체인 확인됨

build.gradle의 Java toolchain이 JavaLanguageVersion.of(17)로 설정되어 있으므로 .toList() 사용에 문제가 없습니다. 변경 없이 유지해주세요.

src/main/java/stackpot/stackpot/task/service/TaskCommandServiceImpl.java (1)

48-48: 컨버터에 User 주입으로 책임 통합 — 좋습니다

taskboardConverter.toTaskboard(pot, request, user)로 생성 책임을 일원화한 점이 명확하고 안전합니다.

Comment on lines +49 to +59
@Operation(summary = "전체 프로젝트 업무 수 대비 개인이 담당한 업무 수 비율이 큰 사람에게 '없어서는 안 될 능력자' 뱃지 부여")
@PostMapping("/{potId}/task-badge")
@ApiErrorCodeExamples({
ErrorStatus.BADGE_NOT_FOUND,
ErrorStatus.BADGE_INSUFFICIENT_TOP_MEMBERS,
ErrorStatus.POT_MEMBER_NOT_FOUND
})
public ResponseEntity<ApiResponse<Void>> assignTaskBadgeToTopMembers(@PathVariable Long potId) {
badgeService.assignTaskBadgeToTopMembers(potId);
return ResponseEntity.ok(ApiResponse.onSuccess(null));
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

신규 Task 배지 부여 API: 트랜잭션/권한/멱등성 점검 필요

  • 현재 Service의 assignTaskBadgeToTopMembers는 트랜잭션 애노테이션이 없습니다. 배지 생성이 다건으로 수행되므로 전체 작업의 원자성을 위해 @Transactional 적용이 필요합니다(서비스 파일에서 제안 패치 포함).
  • 권한: 아무 사용자나 호출 가능한지 확인 필요합니다. 최소한 팟 관리자/소유자만 수행 가능하도록 보안 정책을 점검해 주세요(컨트롤러 또는 서비스 레벨).
  • 멱등성: 동일 요청 반복 시 동일 멤버에 중복 배지가 생성될 수 있습니다. 중복 방지를 위한 존재 여부 체크 또는 DB 유니크 제약(예: badge_id + pot_member_id)을 권장합니다(서비스 파일에서 제안 패치 포함).

원하시면 엔드투엔드 테스트/보안 설정 점검까지 같이 도와드리겠습니다.


서비스 레이어에 트랜잭션 애노테이션 적용 여부 및 컨트롤러 보안 설정 검증을 위해 아래 스크립트를 실행해주세요.


🏁 Script executed:

#!/bin/bash
# assignTaskBadgeToTopMembers 메서드 정의 및 애노테이션 확인
rg -n "assignTaskBadgeToTopMembers" -A3 -B3 src/main/java

# PotBadgeMemberController 보안 애노테이션 확인
rg -n "@PreAuthorize" -A2 -B2 src/main/java/stackpot/stackpot/badge/controller/PotBadgeMemberController.java

# 컨트롤러 클래스 상단 확인
sed -n '1,50p' src/main/java/stackpot/stackpot/badge/controller/PotBadgeMemberController.java

Length of output: 4709


트랜잭션·권한·멱등성 보강 필요

아래 사항을 반영해 주세요.

• 서비스 레이어에 @Transactional 적용

  • BadgeServiceImpl 클래스나 assignTaskBadgeToTopMembers 메서드에 반드시 트랜잭션 경계를 지정해야 합니다.
    예시:
@Service
@RequiredArgsConstructor
@Transactional              // 클래스 단위로 모든 메서드에 적용
public class BadgeServiceImpl implements BadgeService {
    // …
}

또는

@Override
@Transactional              // 해당 메서드에만 적용
public void assignTaskBadgeToTopMembers(Long potId) { … }

• 권한 검사(@PreAuthorize) 추가

  • 팟 관리자/소유자만 호출할 수 있도록 컨트롤러 메서드에 메소드 시큐리티를 걸어주세요.
@PreAuthorize("hasAnyRole('POT_ADMIN','POT_OWNER')")
@PostMapping("/{potId}/task-badge")
public ResponseEntity<ApiResponse<Void>> assignTaskBadgeToTopMembers(@PathVariable Long potId) { … }

• 멱등성 보장 로직 또는 DB 제약

  • 동일 멤버에 중복 배지 발급을 막기 위해 서비스에서 존재 여부 확인 후 삽입하거나, DB에 (pot_member_id, badge_id) 같은 유니크 제약을 추가하세요.
if (badgeRepository.existsByPotMemberIdAndBadgeType(memberId, TASK_BADGE))
    return;
// else insert…

원하시면 엔드투엔드 테스트 작성 및 스프링 시큐리티 설정 점검도 함께 도와드리겠습니다.

Comment on lines +73 to +93
@Override
public void assignTaskBadgeToTopMembers(Long potId) {
List<Long> potMemberIds = potMemberRepository.selectPotMemberIdsByPotId(potId);
if (potMemberIds.isEmpty()) {
throw new PotHandler(ErrorStatus.POT_MEMBER_NOT_FOUND);
}

List<PotMember> top2PotMembers = taskQueryService.getTop2TaskCountByPotMemberId(potMemberIds);
if (top2PotMembers.size() < 2) {
throw new PotHandler(ErrorStatus.BADGE_INSUFFICIENT_TOP_MEMBERS);
}

Badge badge = getBadge(2L);
for (PotMember potMember : top2PotMembers) {
PotMemberBadge potMemberBadge = PotMemberBadge.builder()
.badge(badge)
.potMember(potMember)
.build();
potMemberBadgeRepository.save(potMemberBadge);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

[중요] 트랜잭션/멱등성/배치 저장 개선

  • 트랜잭션: 다건 저장의 원자성을 위해 @Transactional 필요.
  • 멱등성: 동일 호출 반복 시 중복 배지 생성 가능. 존재 체크 후 신규만 저장 필요.
  • 성능: 루프 내 save 대신 saveAll로 배치 저장.

권장 패치:

     @Override
-    public void assignTaskBadgeToTopMembers(Long potId) {
+    @Transactional
+    public void assignTaskBadgeToTopMembers(Long potId) {
         List<Long> potMemberIds = potMemberRepository.selectPotMemberIdsByPotId(potId);
-        if (potMemberIds.isEmpty()) {
+        if (potMemberIds.isEmpty()) {
             throw new PotHandler(ErrorStatus.POT_MEMBER_NOT_FOUND);
         }
 
         List<PotMember> top2PotMembers = taskQueryService.getTop2TaskCountByPotMemberId(potMemberIds);
         if (top2PotMembers.size() < 2) {
             throw new PotHandler(ErrorStatus.BADGE_INSUFFICIENT_TOP_MEMBERS);
         }
 
-        Badge badge = getBadge(2L);
-        for (PotMember potMember : top2PotMembers) {
-            PotMemberBadge potMemberBadge = PotMemberBadge.builder()
-                    .badge(badge)
-                    .potMember(potMember)
-                    .build();
-            potMemberBadgeRepository.save(potMemberBadge);
-        }
+        Badge badge = getBadge(2L); // 또는 상수/코드명 사용
+        // 멱등성: 이미 보유한 대상 제외
+        List<PotMemberBadge> newBadges = top2PotMembers.stream()
+                .filter(pm -> !potMemberBadgeRepository.existsByBadgeAndPotMember(badge, pm))
+                .map(pm -> PotMemberBadge.builder().badge(badge).potMember(pm).build())
+                .collect(Collectors.toList());
+        if (!newBadges.isEmpty()) {
+            potMemberBadgeRepository.saveAll(newBadges);
+        }
     }

Repository 보조 메서드(외부 파일 추가 필요):

// PotMemberBadgeRepository.java
boolean existsByBadgeAndPotMember(Badge badge, PotMember potMember);
🤖 Prompt for AI Agents
In src/main/java/stackpot/stackpot/badge/service/BadgeServiceImpl.java around
lines 73 to 93, the method assignTaskBadgeToTopMembers lacks transactionality,
idempotency checks, and uses per-entity saves; annotate the method with
@Transactional, for each top PotMember check
repository.existsByBadgeAndPotMember(badge, potMember) and skip existing
assignments, collect only new PotMemberBadge instances into a list and call
potMemberBadgeRepository.saveAll(newBadges) once to perform a batched insert;
also add the repository helper boolean existsByBadgeAndPotMember(Badge badge,
PotMember potMember) to PotMemberBadgeRepository.

Comment on lines +42 to 45
// TaskBoard 생성자
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id", nullable = false)
private User user;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

User 연관관계 EAGER 로딩은 N+1 및 불필요 조인 유발 가능성 높음

ManyToOne 기본이 EAGER인데 이를 명시적으로 EAGER로 고정하면 목록 조회 시 심각한 성능 저하가 발생할 수 있습니다. LAZY로 전환하고, 필요한 곳에서만 fetch join/DTO 매핑으로 해결하는 것을 권장합니다. 또한 주석이 필드 위에 “생성자”로 표기되어 있어 오해의 소지가 있습니다.

다음과 같이 변경을 제안합니다:

-    // TaskBoard 생성자
-    @ManyToOne(fetch = FetchType.EAGER)
+    // 작성자 연관관계
+    @ManyToOne(fetch = FetchType.LAZY)
     @JoinColumn(name = "user_id", nullable = false)
     private User user;
🤖 Prompt for AI Agents
In src/main/java/stackpot/stackpot/task/entity/Taskboard.java around lines 42 to
45, the ManyToOne mapping to User is explicitly set to FetchType.EAGER which
risks N+1 queries and unnecessary joins during list retrievals, and the field
comment wrongly says "생성자"; change the mapping to FetchType.LAZY, remove or
correct the misleading comment, and update call sites or repository queries to
use fetch joins or DTO projections where the User is required so behavior
remains correct while avoiding eager loading overhead.

Comment on lines +45 to +46
@Query("SELECT count(*) FROM Taskboard t WHERE t.pot.potId = :potId")
int countByPotId(@Param("potId") Long potId);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

JPQL count(*) 비표준 + 반환형 int는 오버플로우 위험

COUNT는 JPQL 표준 형태 COUNT(t)로 바꾸고, 결과는 long으로 받는 것이 안전합니다. 서비스 인터페이스/구현부 시그니처도 long으로 맞춰주세요.

-        @Query("SELECT count(*) FROM Taskboard t WHERE t.pot.potId = :potId")
-        int countByPotId(@Param("potId") Long potId);
+        @Query("SELECT COUNT(t) FROM Taskboard t WHERE t.pot.potId = :potId")
+        long countByPotId(@Param("potId") Long potId);
📝 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
@Query("SELECT count(*) FROM Taskboard t WHERE t.pot.potId = :potId")
int countByPotId(@Param("potId") Long potId);
@Query("SELECT COUNT(t) FROM Taskboard t WHERE t.pot.potId = :potId")
long countByPotId(@Param("potId") Long potId);
🤖 Prompt for AI Agents
In src/main/java/stackpot/stackpot/task/repository/TaskboardRepository.java
around lines 45-46, change the JPQL COUNT usage from the nonstandard "count(*)"
to the standard "count(t)" and change the method return type from int to long;
then propagate the return-type change to the corresponding service interface and
implementation method signatures (and any callers) so they accept/return long
instead of int to avoid overflow.

Comment on lines +29 to +34
@Query("SELECT t.potMember " +
"FROM Task t " +
"WHERE t.potMember.potMemberId IN :potMemberIds " +
"GROUP BY t.potMember.potMemberId " +
"ORDER BY count(*) DESC")
List<PotMember> getTop2TaskCountByPotMemberId(@Param("potMemberIds") List<Long> potMemberIds, Pageable pageable);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

JPQL에서 count(*) 사용은 비표준입니다 — COUNT(t)로 교체 필요

JPQL 표준은 COUNT(식) 형태이며 COUNT(*)는 표준이 아닙니다. 또한 group by 시 엔티티를 선택하는 경우 id가 아닌 엔티티 자체로 그룹핑하는 편이 더 호환성이 좋습니다. 동률 정렬에 대한 tie-breaker도 추가하세요.

다음과 같이 수정 제안드립니다:

-    @Query("SELECT t.potMember " +
-            "FROM Task t " +
-            "WHERE t.potMember.potMemberId IN :potMemberIds " +
-            "GROUP BY t.potMember.potMemberId " +
-            "ORDER BY count(*) DESC")
+    @Query("SELECT t.potMember " +
+            "FROM Task t " +
+            "WHERE t.potMember.potMemberId IN :potMemberIds " +
+            "GROUP BY t.potMember " +
+            "ORDER BY COUNT(t) DESC, t.potMember.potMemberId ASC")
     List<PotMember> getTop2TaskCountByPotMemberId(@Param("potMemberIds") List<Long> potMemberIds, Pageable pageable);
📝 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
@Query("SELECT t.potMember " +
"FROM Task t " +
"WHERE t.potMember.potMemberId IN :potMemberIds " +
"GROUP BY t.potMember.potMemberId " +
"ORDER BY count(*) DESC")
List<PotMember> getTop2TaskCountByPotMemberId(@Param("potMemberIds") List<Long> potMemberIds, Pageable pageable);
@Query("SELECT t.potMember " +
"FROM Task t " +
"WHERE t.potMember.potMemberId IN :potMemberIds " +
"GROUP BY t.potMember " +
"ORDER BY COUNT(t) DESC, t.potMember.potMemberId ASC")
List<PotMember> getTop2TaskCountByPotMemberId(@Param("potMemberIds") List<Long> potMemberIds, Pageable pageable);
🤖 Prompt for AI Agents
In src/main/java/stackpot/stackpot/task/repository/TaskRepository.java around
lines 29-34, the JPQL uses non-standard COUNT(*) and groups by the potMember id
while selecting the entity; change COUNT(*) to COUNT(t), group by the entity
(t.potMember) instead of its id, and add a deterministic tie-breaker in the
ORDER BY (for example t.potMember.potMemberId ASC) so the query becomes standard
JPQL and stable; update the @Query accordingly while keeping the same method
signature and Pageable support.

MyPotTaskResponseDto viewDetailTask(Long potId, Long taskBoardId);
List<MyPotTaskPreViewResponseDto> getTasksFromDate(Long potId, LocalDate date);
List<MonthlyTaskDto> getMonthlyTasks(Long potId, int year, int month);
int getTaskBoardCountByPotId(Long potId);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

카운트 반환형은 long 권장

Repository에서 COUNT는 long으로 받는 것이 표준/안전합니다. 인터페이스도 int → long으로 변경하고 구현부를 함께 조정하세요.

-    int getTaskBoardCountByPotId(Long potId);
+    long getTaskBoardCountByPotId(Long potId);
📝 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
int getTaskBoardCountByPotId(Long potId);
long getTaskBoardCountByPotId(Long potId);
🤖 Prompt for AI Agents
In src/main/java/stackpot/stackpot/task/service/TaskQueryService.java around
line 16, the method getTaskBoardCountByPotId currently returns int but COUNT
results should use long; change the interface signature to return long, update
the implementing class(es) to return long (adjust any repository query methods
to return long or map the result to long), and update all callers to handle a
long return type accordingly.

Comment on lines +138 to +141
public List<PotMember> getTop2TaskCountByPotMemberId(List<Long> potMemberIds) {
Pageable pageable = PageRequest.of(0, 2);
return taskRepository.getTop2TaskCountByPotMemberId(potMemberIds, pageable);
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

상위 2명 정렬 안정성(동점 처리) 보장 권장

PageRequest.of(0, 2)만으로는 동점 발생 시 결과가 비결정적일 수 있습니다. Repository 레벨 정렬에 tie-breaker(예: count DESC, potMemberId ASC)를 명시해 안정성을 확보하세요.

동점 시 정렬 기준이 명확히 설정되어 있는지 TaskRepository#getTop2TaskCountByPotMemberId의 JPQL/SQL을 확인해 주세요:


🏁 Script executed:

#!/bin/bash
rg -n "getTop2TaskCountByPotMemberId" -A 10

Length of output: 2412


🏁 Script executed:

#!/bin/bash
# 검사: 메서드 선언 이전에 @Query 어노테이션과 ORDER BY 절이 있는지 확인
rg -n "getTop2TaskCountByPotMemberId" -B5 -A5 src/main/java/stackpot/stackpot/task/repository/TaskRepository.java

Length of output: 470


정렬 안정성 보장: ORDER BY에 Tie-Breaker 추가 필요

현재 JPQL @query에는 “ORDER BY count(*) DESC”만 지정되어 있어, 동점 발생 시 결과 순서가 불확정적입니다. 안정적인 상위 2명 선별을 위해 potMemberId를 이용한 2차 정렬(오름차순)을 추가해주세요.

수정 대상:

  • src/main/java/stackpot/stackpot/task/repository/TaskRepository.java

제안하는 변경(diff 예시):

@@ src/main/java/stackpot/stackpot/task/repository/TaskRepository.java:30-33
-   @Query("SELECT t.potMember " +
-          "FROM Task t " +
-          "WHERE t.potMember.potMemberId IN :potMemberIds " +
-          "GROUP BY t.potMember.potMemberId " +
-          "ORDER BY count(*) DESC")
+   @Query("SELECT t.potMember " +
+          "FROM Task t " +
+          "WHERE t.potMember.potMemberId IN :potMemberIds " +
+          "GROUP BY t.potMember.potMemberId " +
+          "ORDER BY count(*) DESC, t.potMember.potMemberId ASC")
📝 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 List<PotMember> getTop2TaskCountByPotMemberId(List<Long> potMemberIds) {
Pageable pageable = PageRequest.of(0, 2);
return taskRepository.getTop2TaskCountByPotMemberId(potMemberIds, pageable);
}
@Query("SELECT t.potMember " +
"FROM Task t " +
"WHERE t.potMember.potMemberId IN :potMemberIds " +
"GROUP BY t.potMember.potMemberId " +
"ORDER BY count(*) DESC, t.potMember.potMemberId ASC")
🤖 Prompt for AI Agents
In src/main/java/stackpot/stackpot/task/repository/TaskRepository.java (the JPQL
@Query that backs getTop2TaskCountByPotMemberId), the ORDER BY only uses
count(*) DESC which yields non-deterministic order on ties; update the JPQL to
add a deterministic tie-breaker by appending potMemberId (or the pot member id
path used in the query) as a secondary sort in ascending order (e.g., ORDER BY
COUNT(...) DESC, <potMemberIdPath> ASC) so the top-2 selection is stable.

@Hyun0828 Hyun0828 closed this Aug 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants