Skip to content

♻️ Refactor: 실시간 알림기능 수정#52

Merged
imjuyongp merged 7 commits intodevelopfrom
feat/notification
Feb 9, 2026
Merged

♻️ Refactor: 실시간 알림기능 수정#52
imjuyongp merged 7 commits intodevelopfrom
feat/notification

Conversation

@imjuyongp
Copy link
Copy Markdown
Member

@imjuyongp imjuyongp commented Feb 8, 2026

#️⃣ Issue Number

📝 요약(Summary)

동호회 리더 조회 메서드

  • getLeader()
    • return type : User
    • 사용자 관련 정보 추출 가능
  • getLeaderMember()
    • return type : clubMember
    • 동호회 관련 정보 추출 가능

-> getLeaderMember()는 클래스 내부에서만 사용중이므로 접근제어자 private으로 변경

알람 메세지

  • Notification의 message필드 제거
  • 제거 이유 : 알람에 대한 메세지를 DB에 저장할 필요성이 없어보임. 프론트엔드 단에서 const로 전달 가능할 듯(무슨 느낌인지 알죠)

-> 응답 필드에 수신자의 정보를 잘 담도록 dto 수정 (완료)

🛠️ PR 유형

어떤 변경 사항이 있나요?

  • 코드 리팩토링
  • 주석 추가 및 수정

📸스크린샷 (선택)

💬 공유사항 to 리뷰어

✅ PR Checklist

PR이 다음 요구 사항을 충족하는지 확인하세요.

  • 커밋 메시지 컨벤션에 맞게 작성했습니다.
  • 변경 사항에 대한 테스트를 했습니다.(버그 수정/기능에 대한 테스트).

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 댓글·답글 알림 추가 및 1:1 쪽지 알림 지원
    • 댓글 작성/답글 발생 시 대상자에게 자동 알림 발송
    • 채팅에서 쪽지 유형 채팅 전송 시 수신자 알림 발송
  • Refactor

    • 알림 페이로드 변경: 기존 단일 메시지 대신 상황별 필드(클럽명, 신청자 닉네임, 게시글 정보, 발신자 등)로 세분화
    • 일부 내부 메서드 접근성 축소로 클래스 캡슐화 강화

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 8, 2026

Caution

Review failed

The pull request is closed.

Walkthrough

Club 클래스의 getLeaderMember 가시성을 publicprivate로 축소했고, Notification 도메인은 message 필드 제거 및 NotificationType 확장(예: COMMENT, REPLY, NOTE), 댓글·답글·1:1 채팅(쪽지)용 명시적 관계(Comment, ChatMessage) 추가, NotificationService API에 댓글/답글/쪽지 생성 메서드가 추가되어 CommentService 및 ChatStompController에서 해당 서비스를 호출하도록 통합했습니다. NotificationResponse DTO는 message 대신 관련 식별자·닉네임·제목 등 세부 필드를 제공하도록 변경되었습니다.

Changes

Cohort / File(s) Summary
Club 엔티티
src/main/java/com/be/sportizebe/domain/club/entity/Club.java
getLeaderMember() 가시성을 publicprivate로 변경(로직 미변경).
Notification 엔티티
src/main/java/com/be/sportizebe/domain/notification/entity/Notification.java
message 필드 제거, NotificationTypeCOMMENT, REPLY, NOTE 등 추가, CommentChatMessage에 대한 @ManyToOne 관계(댓글·채팅 메시지 연결) 도입, 기존 joinClubRequest·targetId는 유지.
Notification 서비스 (인터페이스/구현)
src/main/java/com/be/sportizebe/domain/notification/service/NotificationService.java, .../NotificationServiceImpl.java
기존 가입 관련 생성 메서드들이 반환타입 Notificationvoid로 변경. 댓글·답글·쪽지용 public 메서드(createCommentNotification, createReplyNotification, createNoteNotification) 추가. Join-* 메서드들에서 message 세팅 제거 및 저장·push 유지.
Notification 응답 DTO
src/main/java/com/be/sportizebe/domain/notification/dto/response/NotificationResponse.java
message 제거. 대신 clubName, applicantNickname, postId, postTitle, commenterNickname, chatRoomId, senderNickname 등 필드 추가 및 from(Notification) 빌더 로직 갱신.
채팅(WebSocket) 통합
src/main/java/com/be/sportizebe/domain/chat/websocket/handler/ChatStompController.java
NotificationService, ChatRoomMemberRepository, UserRepository 주입 추가. NOTE(1:1) 채팅 전송 시 상대에게 쪽지 알림 생성하도록 sendNoteNotification 보조 로직 추가.
댓글 서비스 통합
src/main/java/com/be/sportizebe/domain/comment/service/CommentServiceImpl.java
Comment 저장 후 parent 유무에 따라 NotificationService의 createReplyNotification 또는 createCommentNotification 호출 추가.
경미한 주석 변경
src/main/java/com/be/sportizebe/domain/notification/service/JoinClubRequestServiceImpl.java
leader 조회부에 인라인 주석 추가(기능 변경 없음).

Sequence Diagram(s)

sequenceDiagram
    participant Client as User (작성자)
    participant CommentSvc as CommentService
    participant NotificationSvc as NotificationService
    participant Repo as NotificationRepository
    participant WS as WebSocketPusher

    Client->>CommentSvc: createComment(payload)
    CommentSvc->>CommentSvc: validate & save Comment
    alt reply (has parent)
        CommentSvc->>NotificationSvc: createReplyNotification(savedComment)
    else top-level comment
        CommentSvc->>NotificationSvc: createCommentNotification(savedComment)
    end
    NotificationSvc->>Repo: save Notification (type=COMMENT/REPLY, comment=...)
    NotificationSvc->>WS: push notification to target user
Loading
sequenceDiagram
    participant Sender as User A
    participant ChatCtrl as ChatStompController
    participant ChatRepo as ChatRoomMemberRepository
    participant UserRepo as UserRepository
    participant NotificationSvc as NotificationService
    participant WS as WebSocketPusher

    Sender->>ChatCtrl: sendMessage(chatRoomId, message)
    ChatCtrl->>ChatRepo: find members of chatRoom
    ChatCtrl->>UserRepo: lookup other participant
    ChatCtrl->>NotificationSvc: createNoteNotification(chatMessage, otherUser)
    NotificationSvc->>WS: push NOTE notification to otherUser
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🐰 리더는 살짝 숨기고, 알림은 새로 맞추네,
댓글도 톡도 쪽지도, 폴짝 알림을 전해요 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed ♻️ Refactor: 실시간 알림기능 수정 제목은 PR의 주요 변경사항인 알림 기능 리팩토링을 명확하게 반영하고 있으며, 동호회 리더 메서드 접근제어자 변경도 알림 기능과 연관된 변경으로 포함된다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/notification

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.

@imjuyongp imjuyongp added 👀 review needed 리뷰 요청 필요 ♻️ refactor 리팩토링 작업 labels Feb 8, 2026
Copy link
Copy Markdown

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/main/java/com/be/sportizebe/domain/notification/service/JoinClubRequestServiceImpl.java (1)

84-85: ⚠️ Potential issue | 🔴 Critical

Long 타입 비교 시 != 대신 !Objects.equals() 사용 필요.

Club.isLeader()와 동일한 문제입니다. Long 객체를 !=로 비교하면 캐시 범위(-128~127) 밖에서 잘못된 결과를 반환합니다. 본인 신청 취소 검증이 ID 128 이상에서 실패할 수 있습니다.

🐛 수정 제안
-    if (request.getUser().getId() != userId) {
+    if (!request.getUser().getId().equals(userId)) {
src/main/java/com/be/sportizebe/domain/notification/entity/Notification.java (1)

52-54: ⚠️ Potential issue | 🟡 Minor

주석과 실제 엔티티 필드 불일치.

Line 52의 주석에서 targetIdtargetType으로 다형성 처리한다고 설명하지만, 실제 엔티티에는 targetType 필드가 존재하지 않습니다. NotificationType이 그 역할을 대신한다면 주석을 수정하는 것이 좋겠습니다. 만약 별도의 targetType 필드가 필요한 설계라면 누락된 것입니다.

✏️ 주석 수정 제안
-  // 댓글 알림용 - targetId와 targetType으로 다형성 처리
-  // COMMENT: postId, CHAT: chatRoomId 등
+  // COMMENT, CHAT 등 알림용 - NotificationType에 따라 참조 대상이 달라짐
+  // COMMENT: postId, CHAT: chatRoomId
src/main/java/com/be/sportizebe/domain/club/entity/Club.java (1)

71-74: ⚠️ Potential issue | 🔴 Critical

Long 타입 비교 시 == 대신 .equals() 사용 필요.

Long 객체를 ==로 비교하면 -128~127 범위 밖의 값에서는 동일한 값이어도 false를 반환합니다. userId가 128 이상이 되면 리더 검증이 실패합니다.

🐛 수정 제안
  public boolean isLeader(Long userId) {
    ClubMember leaderMember = getLeaderMember();
-   return leaderMember != null && leaderMember.getUser().getId() == userId;
+   return leaderMember != null && leaderMember.getUser().getId().equals(userId);
  }

동일한 패턴이 다른 파일에서도 발견되었습니다:

  • PostServiceImpl.java: line 63, 78
  • JoinClubRequestServiceImpl.java: line 84
  • ChatRoomService.java: line 39
  • CommentServiceImpl.java: line 53, 94

각 파일의 비교 연산자를 .equals()로 통일하여 일관성 있는 Long 객체 비교를 적용해야 합니다.

🧹 Nitpick comments (1)
src/main/java/com/be/sportizebe/domain/notification/entity/Notification.java (1)

22-29: NotificationType 확장 승인.

JOIN_REJECTED, CHAT, COMMENT 추가는 PR 목적에 부합합니다. TODO 주석이 남아 있으니 후속 작업 시 정리해 주세요.

TODO 항목 추적을 위해 이슈를 생성할까요?

@angoroa
Copy link
Copy Markdown
Member

angoroa commented Feb 8, 2026

무슨 느낌인지 알죠 ( ✅ )

@imjuyongp
Copy link
Copy Markdown
Member Author

imjuyongp commented Feb 8, 2026

pr merge 아직 하지 말아주세요 @angoroa

@imjuyongp imjuyongp changed the title ♻️ Refactor: 실시간 알림기능 수정사항 ♻️ Refactor: 실시간 알림기능 수정 Feb 8, 2026
@imjuyongp
Copy link
Copy Markdown
Member Author

알림 응답 dto 수정 완료

Copy link
Copy Markdown

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

🤖 Fix all issues with AI agents
In
`@src/main/java/com/be/sportizebe/domain/notification/dto/response/NotificationResponse.java`:
- Around line 28-30: The builder chain in NotificationResponse (the lines
calling .clubName(...) and .applicantNickname(...) on joinRequest) can NPE if
joinRequest.getClub() or joinRequest.getUser() is null; update the expressions
to safely handle those cases (either by adding explicit null checks for
joinRequest.getClub() and joinRequest.getUser() before calling
getName()/getNickname(), or use Optional.ofNullable(joinRequest).map(j ->
j.getClub()).map(Club::getName).orElse(null) and similarly for the user
nickname) so the builder receives null when the related entity is missing rather
than throwing an exception; add import java.util.Optional if you choose the
Optional approach.

Comment on lines +28 to +30
.joinRequestId(joinRequest != null ? joinRequest.getId() : null)
.clubName(joinRequest != null ? joinRequest.getClub().getName() : null)
.applicantNickname(joinRequest != null ? joinRequest.getUser().getNickname() : null)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

joinRequest.getClub() 또는 joinRequest.getUser()가 null일 경우 NPE 발생 가능

joinRequest != null 검사만으로는 체이닝된 .getClub().getName(), .getUser().getNickname() 호출의 안전성을 보장하지 못합니다. 연관 엔티티가 null이면 NullPointerException이 발생합니다.

Optional을 사용하면 null 체크 반복도 줄이고 체이닝 안전성도 확보할 수 있습니다.

🛡️ Optional을 활용한 수정 제안
   public static NotificationResponse from(Notification notification) {
-    var joinRequest = notification.getJoinClubRequest();
-
+    var joinRequest = Optional.ofNullable(notification.getJoinClubRequest());
+
     return NotificationResponse.builder()
         .id(notification.getId())
         .type(notification.getType())
         .isRead(notification.getIsRead())
-        .joinRequestId(joinRequest != null ? joinRequest.getId() : null)
-        .clubName(joinRequest != null ? joinRequest.getClub().getName() : null)
-        .applicantNickname(joinRequest != null ? joinRequest.getUser().getNickname() : null)
+        .joinRequestId(joinRequest.map(jr -> jr.getId()).orElse(null))
+        .clubName(joinRequest.map(jr -> jr.getClub()).map(club -> club.getName()).orElse(null))
+        .applicantNickname(joinRequest.map(jr -> jr.getUser()).map(user -> user.getNickname()).orElse(null))
         .targetId(notification.getTargetId())
         .createdAt(notification.getCreatedAt())
         .build();
   }

java.util.Optional import도 추가해야 합니다:

import java.util.Optional;
📝 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
.joinRequestId(joinRequest != null ? joinRequest.getId() : null)
.clubName(joinRequest != null ? joinRequest.getClub().getName() : null)
.applicantNickname(joinRequest != null ? joinRequest.getUser().getNickname() : null)
import java.util.Optional;
public static NotificationResponse from(Notification notification) {
var joinRequest = Optional.ofNullable(notification.getJoinClubRequest());
return NotificationResponse.builder()
.id(notification.getId())
.type(notification.getType())
.isRead(notification.getIsRead())
.joinRequestId(joinRequest.map(jr -> jr.getId()).orElse(null))
.clubName(joinRequest.map(jr -> jr.getClub()).map(club -> club.getName()).orElse(null))
.applicantNickname(joinRequest.map(jr -> jr.getUser()).map(user -> user.getNickname()).orElse(null))
.targetId(notification.getTargetId())
.createdAt(notification.getCreatedAt())
.build();
}
🤖 Prompt for AI Agents
In
`@src/main/java/com/be/sportizebe/domain/notification/dto/response/NotificationResponse.java`
around lines 28 - 30, The builder chain in NotificationResponse (the lines
calling .clubName(...) and .applicantNickname(...) on joinRequest) can NPE if
joinRequest.getClub() or joinRequest.getUser() is null; update the expressions
to safely handle those cases (either by adding explicit null checks for
joinRequest.getClub() and joinRequest.getUser() before calling
getName()/getNickname(), or use Optional.ofNullable(joinRequest).map(j ->
j.getClub()).map(Club::getName).orElse(null) and similarly for the user
nickname) so the builder receives null when the related entity is missing rather
than throwing an exception; add import java.util.Optional if you choose the
Optional approach.

@imjuyongp imjuyongp merged commit 5fb30ec into develop Feb 9, 2026
1 check was pending
@imjuyongp imjuyongp deleted the feat/notification branch February 9, 2026 08:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

♻️ refactor 리팩토링 작업 👀 review needed 리뷰 요청 필요

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants