Skip to content

refactor : SkillExchange의 알림 read flag 컬럼 방식에서 Notification 도메인 연결 전환#157

Merged
ekdan38 merged 14 commits into
mainfrom
feature/notification/skill
Apr 18, 2026
Merged

refactor : SkillExchange의 알림 read flag 컬럼 방식에서 Notification 도메인 연결 전환#157
ekdan38 merged 14 commits into
mainfrom
feature/notification/skill

Conversation

@chuuminggg
Copy link
Copy Markdown
Member

🔥ISSUE

스킬교환 알림 시스템의 미읽음 상태 관리를 SkillExchange 엔티티의 read flag 컬럼 방식에서 Notification 테이블 단일 소스 방식으로 완전 전환

📒TODO

  1. GET /exchange/request/notification 엔드포인트 제거 → /notifications/unread-count 사용으로 대체
  2. SkillExchangeNotificationResponseDto 클래스 삭제
  3. SkillExchangeRepository — bulkUpdateRequesterReadStatus, bulkUpdateReceiverReadStatus,
    existsByRequester_IdAndIsRequesterReadFalse, existsByReceiver_IdAndIsReceiverReadFalse 4개 메서드 제거
  4. SentDetailQuery.isRead, ReceivedDetailQuery.isRead 필드 제거 (JPQL 포함)
  5. acceptSkillExchange, rejectSkillExchange, cancelSkillExchange 에서 updateRequesterReadToFalse() / updateReceiverReadToFalse() 호출 제거
  6. NotificationRepository — findUnreadRefIdsByUserIdAndTypes() 배치 쿼리 추가
  7. NotificationService — getUnreadSentRequestRefIds(), getUnreadReceivedRequestRefIds() 추가
  8. getSentRequests, getReceivedRequests — 읽음 처리 전 미읽음 refId 배치 조회 후 isNew per-item 계산으로 변경

📋REF

변경 배경

  • 기존에는 SkillExchange.isRequesterRead / isReceiverRead 컬럼을 직접 UPDATE해 미읽음 상태를 관리하고, GET /exchange/request/notification 엔드포인트로 별도 조회
  • 채팅 알림이 이미 Notification 테이블 기반으로 리팩토링되어 있어 스킬교환 알림만 방식이 달랐음

변경 후 동작

  • 미읽음 여부(isNew per-item): Notification 테이블에서 findUnreadRefIdsByUserIdAndTypes() 배치 쿼리 1회로 조회 → Set 으로 DTO 조립 시 O(1) 룩업
  • 탭/네비바 배지 카운트: GET /notifications/unread-count 의 requestTabCount, receivedRequestCount, sentRequestCount 필드 사용
  • SkillExchange.isRequesterRead / isReceiverRead 컬럼은 DB에 유지 (backward compat), 애플리케이션에서 더 이상 읽거나 쓰지 않음

chuuminggg and others added 12 commits January 21, 2026 23:40
- GET /exchange/request/notification 엔드포인트 제거 (→ /notifications/unread-count 사용)
- SkillExchangeNotificationResponseDto 삭제
- SkillExchangeRepository bulkUpdateRequesterReadStatus/ReceiverReadStatus, existsBy*IsReadFalse 제거
- SentDetailQuery.isRead, ReceivedDetailQuery.isRead 필드 제거 (JPQL 포함)
- acceptSkillExchange/rejectSkillExchange/cancelSkillExchange updateRequesterReadToFalse/ReadToFalse 호출 제거
- NotificationRepository.findUnreadRefIdsByUserIdAndTypes() 배치 쿼리 추가
- NotificationService.getUnreadSentRequestRefIds(), getUnreadReceivedRequestRefIds() 추가
- getSentRequests/getReceivedRequests : 읽음 처리 전 미읽음 refId 배치 조회 후 isNew per-item 계산

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@chuuminggg chuuminggg requested review from ekdan38 and jaey-oung April 16, 2026 15:04
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the skill exchange notification system by migrating read-status tracking from the SkillExchange entity to the Notification domain. Key changes include removing the dedicated notification endpoint, updating DTOs to accept externally calculated isNew flags, and implementing batch retrieval of unread notification IDs via the NotificationService. Review feedback identifies critical inconsistencies in NotificationServiceImpl, noting that methods like markReceivedRequestAsRead still target single notification types rather than the newly defined RECEIVED_REQUEST_TYPES constant, which could lead to status-change notifications remaining unread.

Comment on lines +64 to +67
private static final List<NotificationType> RECEIVED_REQUEST_TYPES = List.of(
NotificationType.REQUEST_RECEIVED,
NotificationType.REQUEST_STATUS_CHANGED
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

RECEIVED_REQUEST_TYPES 상수가 정의되었으나, 기존의 markReceivedRequestAsRead 메서드와 getUnreadCounts 메서드(본 diff에는 포함되지 않음)에서는 여전히 NotificationType.REQUEST_RECEIVED를 직접 사용하고 있는 것으로 보입니다. 이로 인해 REQUEST_STATUS_CHANGED 타입의 알림이 "받은 요청" 탭의 미읽음 카운트에 반영되지 않거나, 탭 진입 시 읽음 처리가 누락되는 현상이 발생할 수 있습니다. SENT_REQUEST_TYPES를 사용하는 방식과 일관성을 유지하도록 해당 메서드들이 RECEIVED_REQUEST_TYPES를 사용하도록 수정이 필요합니다.

Comment on lines +257 to 260
Set<Long> unreadRefIds = notificationService.getUnreadReceivedRequestRefIds(userId);

// 5. Notification 도메인 읽음 처리 (REQUEST_RECEIVED)
// 4. Notification 도메인 읽음 처리 (REQUEST_RECEIVED)
notificationService.markReceivedRequestAsRead(userId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

getUnreadReceivedRequestRefIdsRECEIVED_REQUEST_TYPES(REQUEST_RECEIVED + REQUEST_STATUS_CHANGED)를 사용하여 미읽음 ID를 조회하는 반면, markReceivedRequestAsReadREQUEST_RECEIVED 타입만 읽음 처리하고 있습니다. 이로 인해 상태 변경 알림(REQUEST_STATUS_CHANGED)이 목록에서는 isNew=true로 표시되지만, 실제 알림 엔티티는 읽음 처리가 되지 않아 탭의 배지 카운트가 줄어들지 않는 문제가 발생할 수 있습니다. markReceivedRequestAsRead 메서드가 RECEIVED_REQUEST_TYPES 전체를 처리하도록 NotificationService 구현부의 수정을 검토해 주세요.

- RECEIVED_REQUEST_TYPES에 REQUEST_STATUS_CHANGED 포함 시
  받은 요청 탭 진입만으로 보낸 요청 탭의 상태 변경 알림까지 읽음 처리되는 부작용 발생
- RECEIVED_REQUEST_TYPES = [REQUEST_RECEIVED] 으로 수정
- markReceivedRequestAsRead(), getUnreadCounts() receivedRequestCount 모두
  RECEIVED_REQUEST_TYPES 상수를 통해 일관성 있게 처리

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@chuuminggg
Copy link
Copy Markdown
Member Author

/gemini review

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the skill exchange notification system by migrating the 'read' status management from the SkillExchange entity to the Notification domain. Key changes include removing read-status flags from the SkillExchange repository and DTOs, and instead utilizing the NotificationService to fetch unread reference IDs for determining the 'isNew' status of items in request lists. Feedback indicates a logic issue in the classification of notification types: since REQUEST_STATUS_CHANGED is exclusively mapped to sent requests, receivers will not see updated status indicators or correct unread counts when a requester cancels an exchange. It is recommended to distinguish between status changes for sent and received requests to ensure UI consistency.

Comment on lines +64 to +69
// REQUEST_STATUS_CHANGED 는 보낸 요청(SENT) 버킷에만 속함
// 받은 요청 탭에서 REQUEST_STATUS_CHANGED 까지 읽음 처리하면
// 보낸 요청 탭의 미읽음 배지가 잘못 감소하는 부작용 발생
private static final List<NotificationType> RECEIVED_REQUEST_TYPES = List.of(
NotificationType.REQUEST_RECEIVED
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The current classification of REQUEST_STATUS_CHANGED exclusively into SENT_REQUEST_TYPES is problematic. When a requester cancels a skill exchange, a REQUEST_STATUS_CHANGED notification is sent to the receiver. Since this type is missing from RECEIVED_REQUEST_TYPES, the receiver will not see a red dot (isNew) for that item in their "Received Requests" tab, and the unread count for that tab will be incorrect.

To fix this while avoiding the "cross-tab clearing" issue mentioned in the comments, you should distinguish between status changes for sent requests and received requests. Consider splitting REQUEST_STATUS_CHANGED into two distinct types: SENT_REQUEST_STATUS_CHANGED and RECEIVED_REQUEST_STATUS_CHANGED.


// 받은 요청 탭
long receivedRequestCount = notificationRepository.countUnreadByUserIdAndType(userId, NotificationType.REQUEST_RECEIVED);
long receivedRequestCount = notificationRepository.countUnreadByUserIdAndTypes(userId, RECEIVED_REQUEST_TYPES);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

As noted in the previous comment, receivedRequestCount will be inaccurate because it excludes REQUEST_STATUS_CHANGED notifications that are intended for the receiver (e.g., when a requester cancels). This leads to a discrepancy where the main notification badge shows a count that doesn't match the sum of the tab badges, or a tab badge that doesn't reflect actual unread changes.

- REQUEST_STATUS_CHANGED 단일 타입으로는 수락/거절(→요청자)과 요청자취소(→수신자)를
  서로 다른 탭 버킷에 분류하는 것이 불가능한 문제 해결
- SENT_REQUEST_STATUS_CHANGED: 수락/거절/수신자취소/만료 시 요청자에게 발송
- RECEIVED_REQUEST_STATUS_CHANGED: 요청자취소/만료 시 수신자에게 발송
- SENT_REQUEST_TYPES = [REQUEST_SENT, SENT_REQUEST_STATUS_CHANGED]
- RECEIVED_REQUEST_TYPES = [REQUEST_RECEIVED, RECEIVED_REQUEST_STATUS_CHANGED]
- 기존 REQUEST_STATUS_CHANGED 는 DB 호환성을 위해 enum에 @deprecated 유지

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ekdan38 ekdan38 merged commit 9989a54 into main Apr 18, 2026
1 check passed
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