Skip to content

Conversation

@seongjunnoh
Copy link
Collaborator

@seongjunnoh seongjunnoh commented Sep 22, 2025

#️⃣ 연관된 이슈

closes #308

📝 작업 내용

  1. notifications 테이블 구조 변경
  • 알림 클릭 시 알림에 해당하는 페이지로의 리다이렉트를 위한 데이터들을 저장할 저장소가 필요했습니다
  • 저장할 데이터의 형식이 일정하지 않아 [notifications table에 컬럼 추가, redis 활용, mongo DB 도입] 3가지 방식을 고민하였으나, 기존 MySQL DB + JPA AttributeConverter 를 활용하는 방식이 가장 효율적이라 생각하여 1번 방식으로 구현하였습니다
  • 관련해서 flyway sql 파일 추가했고, 로컬 DB 잘 업데이트 되는것 확인했습니다
image
  1. fcm 푸시알림 구성 수정
  • 기존에는 fcm 서버로 보내는 메시지에 리다이렉트를 위한 데이터까지 모두 포함하였습니다
  • 하지만 '알림 읽음 처리 api' 가 도입되어야 하는 상황에서 리다이렉트를 위한 데이터를 fcm 푸시알림이 아니라, api의 응답으로 포함되는게 더 좋을 것 같다고 생각하여(-> 현재는 웹에서는 푸시알림이 안되므로), 기존 알림 이벤트 퍼블리시, 이벤트 리스닝 코드를 수정하였습니다.
  • 이벤트에는 푸시알림에 필요한 title, content 와 이후 해당 푸시알림의 읽음 처리에 필요한 id에 해당하는 notificationId 값만 포함되도록 수정했습니다.
  • 이 부분은 개발서버에서 BE -> FCM -> FE 로의 통신이 제대로 이루어지는지 확인해봐야할 것 같습니다
  1. '알림 읽음 처리 api' 구현
  • 특정 유저가 알림 센터나 푸시 알림을 클릭할 경우 [해당 알림 읽음 처리 + 알림에 해당하는 페이지로 리다이렉트] 되어야 합니다
  • 이 동작을 위한 api 1개를 구현하였습니다
  • 처음에는 FE 플랫폼에 따라 response 를 다르게 구성할까 하다가 (웹은 redirect 할 FE 페이지 url 구성하여 응답, 안드로이드는 기존 redirect data 그대로 응답),FE 플랫폼에 상관없이 [route 경로에 대한 string 값, route 시에 필요한 data] 를 응답하는 것으로 구현했습니다.
  • 이를 응답받아 웹은 각 route 상황에 맞는 페이지 redirect url을 구성하면 되고, 안드로이드는 각 route 상황에 맞는 api 요청 url을 구성하면 될 것 같습니다

📸 스크린샷

image image -> api 응답 잘 오고, DB의 is_checked 잘 업데이트 되는 것 확인했습니다

💬 리뷰 요구사항

📌 PR 진행 시 이러한 점들을 참고해 주세요

* P1 : 꼭 반영해 주세요 (Request Changes) - 이슈가 발생하거나 취약점이 발견되는 케이스 등
* P2 : 반영을 적극적으로 고려해 주시면 좋을 것 같아요 (Comment)
* P3 : 이런 방법도 있을 것 같아요~ 등의 사소한 의견입니다 (Chore)

Summary by CodeRabbit

  • 신기능
    • 알림 읽음 처리 API 추가: 알림을 읽음으로 표시하고 프론트로 리다이렉트 정보(경로/파라미터) 반환.
  • 개선
    • 푸시 페이로드 표준화: 모든 알림에 notificationId 기반 발송·열기 동작 통일.
    • 이벤트/퍼블리셔 페이로드 간소화: 발행 데이터가 notificationId + 대상 사용자 위주로 변경.
    • FCM 관련 요청 검증 메시지 명확화 및 플랫폼 입력 문자열 지원.
  • 오류 처리
    • 알림 관련 신규 오류 코드 추가(존재하지 않음, 권한 없음, 이미 읽음, 직렬화/역직렬화 실패, 유효하지 않은 플랫폼).
  • 문서화
    • Swagger 응답·오류 케이스 업데이트(알림 읽음 처리, FCM 등록).

… 데이터 삭제 (#308)

- 리다이렉트를 위한 데이터들은 fcm 푸시알림에 포함시키는게 아니라, 다른 api 를 통해 응답하도록 수정
- 알림 DB 저장 시에 RedirectSpec 또한 전달받아 저장하도록 수정
- 저장한 알림의 notificationId 값을 이벤트 퍼블리시 과정에 포함해야하므로, EventCommandInvoker 에게 전달하도록 수정
- 알림에 함께 저장해야할 redirectSpec 구성
- 이 값을 포함해서 NotificationSyncExecutor 호출하도록 수정
- 마찬가지로 redirectSpec 구성 & 이 값을 포함해서 NotificationSyncExecutor 를 호출하도록 수정
- 퍼블리시하는 이벤트의 구성 수정에 따른 기존 코드 수정
- 리스닝하는 이벤트의 구성 수정에 따른 기존 코드 수정
- PlatformType 의 정적 팩토리 메서드를 활용하여 지원하는 FE 플랫폼인지 확인하도록 코드 수정
- 필요없는 String code value 삭제
- message/domain 패키지 -> notification/domain/value 패키지로 이동
@seongjunnoh seongjunnoh linked an issue Sep 22, 2025 that may be closed by this pull request
3 tasks
@coderabbitai
Copy link

coderabbitai bot commented Sep 22, 2025

Walkthrough

알림 시스템 전반에 리다이렉트 스펙을 도입하고 이벤트 페이로드를 notificationId 중심으로 단순화했다. 알림 읽음 처리 API를 추가했으며 ErrorCode/Swagger를 확장했다. FE 플랫폼 처리 방식과 FCM 요청 DTO 검증을 보강하고 DB에 redirect_spec 컬럼을 추가했다.

Changes

Cohort / File(s) Summary
Error codes & Swagger
src/main/java/konkuk/thip/common/exception/code/ErrorCode.java, src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java
알림/유틸용 ErrorCode 항목 추가(205001–205005, 300000). Swagger 응답 그룹에 NOTIFICATION_MARK_TO_CHECKED 추가 및 FCM_TOKEN_REGISTER에 INVALID_FE_PLATFORM 반영.
알림 읽음 처리 API / UseCase / DTO
src/main/java/konkuk/thip/notification/adapter/in/web/NotificationCommandController.java, .../request/NotificationMarkToCheckedRequest.java, .../response/NotificationMarkToCheckedResponse.java, .../application/port/in/NotificationMarkUseCase.java, .../application/service/NotificationMarkService.java
POST /notifications/check 엔드포인트 추가, NotificationMarkUseCase/서비스/DTO 신설. 소유자 검증 후 읽음 처리 및 redirectSpec(route/params) 반환.
Notification 도메인 및 리다이렉트 스펙
src/main/java/konkuk/thip/notification/domain/Notification.java, .../value/MessageRoute.java, .../value/NotificationRedirectSpec.java, .../value/NotificationRedirectSpecConverter.java
Notification에 redirectSpec 추가, validateOwner/markToChecked 메서드 추가. MessageRoute enum 신설, RedirectSpec 레코드 및 JPA 컨버터 추가(직렬화/역직렬화 예외 처리 포함).
퍼시스턴스/매핑/마이그레이션
.../adapter/out/jpa/NotificationJpaEntity.java, .../adapter/out/mapper/NotificationMapper.java, .../adapter/out/persistence/NotificationCommandPersistenceAdapter.java, src/main/resources/db/migration/V250921__Add_notification_redirect_spec.sql
JPA 엔티티에 redirect_spec 매핑/컨버터 적용, save 반환값을 Long으로 변경, findById/update 추가, DB 마이그레이션 파일로 컬럼 추가.
이벤트/퍼블리셔 및 DTO 단순화 (Feed / Room)
.../message/adapter/out/event/FeedEventPublisherAdapter.java, .../message/adapter/out/event/dto/FeedEvents.java, .../message/adapter/out/event/RoomEventPublisherAdapter.java, .../message/adapter/out/event/dto/RoomEvents.java, .../message/application/port/out/FeedEventCommandPort.java, .../message/application/port/out/RoomEventCommandPort.java
기존 actor/room/feedId 등 상세 필드 제거, 모든 이벤트 페이로드를 (title, content, notificationId, targetUserId)로 통일. 이벤트/메서드명 일부 변경(FolloweeNewPost→FolloweeNewFeed 등).
오케스트레이터 / 실행기 / invoker 변경
.../notification/application/service/NotificationSyncExecutor.java, .../notification/application/service/EventCommandInvoker.java, .../notification/application/service/FeedNotificationOrchestratorSyncImpl.java, .../notification/application/service/RoomNotificationOrchestratorSyncImpl.java, .../notification/application/port/in/FeedNotificationOrchestrator.java
Notification 저장 시 redirectSpec 전달, save 후 생성된 notificationId를 EventCommandInvoker.publish로 전달하도록 흐름 변경(Invoker 시그니처 변경). orchestrator에서 redirectSpec 생성 및 notificationId 기반 퍼블리시로 전환.
메시지 디스패치 리팩터링 (FCM 관련)
.../message/application/service/FeedNotificationDispatchService.java, .../message/application/service/RoomNotificationDispatchService.java, .../message/domain/MessageRoute.java
푸시 로직을 공통 push(...)로 통합하고 MessageRoute 제거(모듈 내 기존 enum 삭제). Message 빌드 시 category/action/notificationId만 포함하도록 단순화.
FCM 플랫폼 처리 및 요청 DTO 변경
.../notification/domain/value/PlatformType.java, .../notification/adapter/in/web/request/FcmTokenRegisterRequest.java, .../FcmTokenDeleteRequest.java, .../FcmTokenEnableStateChangeRequest.java
PlatformType에 문자열 값과 from(String) 팩토리 추가(유효하지 않을 경우 INVALID_FE_PLATFORM 예외). RegisterRequest는 platformType을 String으로 받고 PlatformType.from으로 변환. 검증 메시지 보강.
포트 시그니처 / persistence 포트 변경
.../notification/application/port/out/NotificationCommandPort.java, .../message/application/port/out/*
NotificationCommandPort.save 반환 타입을 Long으로 변경, findById/getByIdOrThrow 추가. 각 이벤트 퍼블리시 포트 서명을 notificationId 중심으로 변경.
테스트 반영
src/test/... 다수 (TestEntityFactory, Event/Listener 테스트, Orchestrator/Executor 테스트, NotificationTest, API 통합 테스트 등)
이벤트 페이로드 변경(notificationId 사용)과 Executor/Invoker 시그니처 변경 반영, Notification 리다이렉트 스펙 테스트/팩토리 추가, 읽음 처리 API 통합 테스트 추가.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Orchestrator
  participant Executor as NotificationSyncExecutor
  participant Repo as NotificationCommandPort
  participant Invoker as EventCommandInvoker
  participant Publisher as EventPublisher(Feed/Room)

  Orchestrator->>Executor: execute(template, args, targetUserId, redirectSpec, invoker)
  Executor->>Executor: title/content/category 생성
  Executor->>Repo: save(Notification.withoutId(..., redirectSpec))
  Repo-->>Executor: notificationId
  Executor->>Invoker: publish(title, content, notificationId)
  Invoker->>Publisher: publish...(title, content, notificationId, targetUserId)
  Note right of Publisher: 이벤트 DTO는 (title, content, notificationId, targetUserId)
Loading
sequenceDiagram
  autonumber
  actor Client
  participant Controller as NotificationCommandController
  participant Service as NotificationMarkService
  participant Repo as NotificationCommandPort

  Client->>Controller: POST /notifications/check {notificationId}
  Controller->>Service: markToChecked(notificationId, userId)
  Service->>Repo: getByIdOrThrow(notificationId)
  Repo-->>Service: Notification
  Service->>Service: validateOwner(userId) / markToChecked()
  Service->>Repo: update(notification)
  Service-->>Controller: NotificationMarkToCheckedResponse{route, params}
  Controller-->>Client: 200 BaseResponse{data}
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • buzz0331
  • hd0rable

Poem

토끼가 말하네, 깡총깡총 알림길에 발자국 남기며 🐇
notificationId 하나로 길을 잇고, 스펙은 주머니에 쏙.
읽음 한 번에 route와 params를 꺼내어 건네주면,
이벤트는 가볍게 날아가고 DB엔 기록이 남네.
폴짝—FE는 이제 잃지 않으리, 길잡이 토끼의 약속.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning 대부분의 변경은 알림 리다이렉트 설계(이벤트 페이로드를 notificationId 중심으로 변경하고 리다이렉트 데이터는 DB에서 제공)와 관련되어 있으나 PR에 알림 외의 영역에 영향을 주는 광범위한 리팩토링·인터페이스 변경이 포함되어 있습니다; 예로 PlatformType 요청형 변경(문자열 변환 및 INVALID_FE_PLATFORM 예외), FCM 토큰 DTO 검증 메시지 변경, message.domain.MessageRoute 삭제 및 MessageRoute의 도메인 이전, 다수의 퍼블리셔/포트/이벤트 시그니처 대규모 변경 및 타입/이름 변경(예: FolloweeNewPost → FolloweeNewFeed) 등이 있으며 이들 중 일부는 링크된 이슈의 최소 범위를 벗어나는 부수적 변경으로 보입니다. 광범위한 API/인터페이스 변경과 알림 외 DTO/유효성 검증 변경은 별도 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 제목은 PR의 핵심 작업인 "알림(알림센터 / 푸시) 클릭 시 백엔드 로직 구현"을 간결하게 요약하고 있어 변경 내용과 일치합니다; 다만 한국어 일관성을 위해 영어 "or" 대신 "또는"으로 바꾸면 더 깔끔합니다.
Linked Issues Check ✅ Passed 연결된 이슈(#308) 요구사항을 충족합니다: 기존 RDB에 redirect_spec 컬럼 추가(Flyway 마이그레이션 + JPA AttributeConverter), NotificationRedirectSpec 및 관련 도메인·매퍼·엔티티·퍼시스턴스 변경, '알림 읽음 처리 + 리다이렉트 데이터 반환' API(컨트롤러, use-case, 서비스, DTO)와 관련 테스트가 구현되어 있어 설계·저장·API 제공 세 가지 주요 목표가 모두 반영되어 있습니다.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#308-notification-redirect-data-storage

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8d4ba69 and 57f9970.

📒 Files selected for processing (2)
  • src/main/java/konkuk/thip/notification/adapter/in/web/NotificationCommandController.java (3 hunks)
  • src/main/java/konkuk/thip/notification/adapter/in/web/response/NotificationMarkToCheckedResponse.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/konkuk/thip/notification/adapter/in/web/NotificationCommandController.java
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#113
File: src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java:38-44
Timestamp: 2025-07-30T14:05:04.945Z
Learning: seongjunnoh는 코드 최적화 제안에 대해 구체적인 기술적 근거와 효율성 차이를 이해하고 싶어하며, 성능 개선 방식에 대한 상세한 설명을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#93
File: src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java:49-114
Timestamp: 2025-07-28T16:44:31.224Z
Learning: seongjunnoh는 코드 중복 문제에 대한 리팩토링 제안을 적극적으로 수용하고 함수형 인터페이스를 활용한 해결책을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#285
File: src/main/java/konkuk/thip/room/adapter/out/jpa/RoomStatus.java:1-7
Timestamp: 2025-08-31T05:25:14.845Z
Learning: seongjunnoh는 enum 의존성에 대해 유연한 접근을 선호하며, 도메인→어댑터 레이어 참조와 같은 아키텍처 layering 원칙보다 실용적인 구현을 우선시한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#195
File: src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java:0-0
Timestamp: 2025-08-13T05:22:32.287Z
Learning: seongjunnoh는 데이터 무결성과 중복 방지에 대한 고민이 깊으며, LinkedHashSet을 활용한 중복 제거와 순서 보장을 동시에 달성하는 솔루션을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#305
File: src/main/java/konkuk/thip/notification/adapter/out/persistence/repository/NotificationQueryRepository.java:9-13
Timestamp: 2025-09-17T06:40:49.863Z
Learning: seongjunnoh는 메서드 네이밍 시 구현 세부사항보다 비즈니스 의도를 반영하는 것을 선호하며, 미래 확장성과 아키텍처 변화를 고려한 설계 철학을 가지고 있다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#278
File: src/main/java/konkuk/thip/TestTokenController.java:0-0
Timestamp: 2025-08-24T09:33:52.982Z
Learning: seongjunnoh는 Spring의 ConditionalOnProperty 동작 원리를 정확히 이해하고 있으며, 보안 이슈에 대해서도 실질적인 위험성을 기준으로 판단하는 실용적 접근을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#278
File: src/main/java/konkuk/thip/common/exception/code/ErrorCode.java:210-211
Timestamp: 2025-08-24T09:40:48.595Z
Learning: seongjunnoh는 HTTP 상태 코드 선택에 대해 기술적 근거와 코드베이스 내 일관성을 중요하게 생각하며, 구체적인 사례 분석을 통한 설명을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#180
File: src/main/java/konkuk/thip/room/application/service/AttendanceCheckCreateService.java:22-40
Timestamp: 2025-08-14T09:15:31.371Z
Learning: seongjunnoh는 기술적 완벽성보다 실제 비즈니스 시나리오와 사용자 행동 패턴을 우선 고려하며, 발생 가능성이 낮은 엣지 케이스보다는 실용적인 구현을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#112
File: src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java:272-272
Timestamp: 2025-07-30T10:44:34.115Z
Learning: seongjunnoh는 피드 커서 페이지네이션에서 LocalDateTime 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
src/main/java/konkuk/thip/notification/adapter/in/web/response/NotificationMarkToCheckedResponse.java (2)

8-16: LGTM! record 구조와 Swagger 문서화가 잘 되어 있습니다

알림 읽음 처리 응답을 위한 record 클래스가 깔끔하게 구현되었고, Swagger 어노테이션을 통한 API 문서화도 적절합니다. MessageRoute와 파라미터를 분리한 설계도 합리적입니다.


14-14: [중복 댓글] JavaScript 정밀도 손실 위험 — Long ID를 문자열로 변환하세요

Map<String, Object> params에서 Long 타입의 ID 값들이 그대로 반환되면 웹 클라이언트에서 2^53-1을 초과하는 정수의 정밀도가 손실될 수 있습니다.

기존 리뷰와 동일한 이슈로, 다음 중 하나의 방법으로 해결해 주세요:

  • 권장: NotificationRedirectSpec.params 타입을 Map<String, String>으로 변경하고 생성 시점에서 String.valueOf(id) 사용
  • 대안: 서비스 레이어에서 숫자 값을 문자열로 변환하는 매퍼/유틸리티 추가
-        Map<String, Object> params
+        Map<String, String> params

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.

@github-actions
Copy link

github-actions bot commented Sep 22, 2025

Test Results

479 tests  +5   479 ✅ +5   42s ⏱️ -1s
142 suites +2     0 💤 ±0 
142 files   +2     0 ❌ ±0 

Results for commit 57f9970. ± Comparison against base commit d96c12e.

♻️ This comment has been updated with latest results.

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

🧹 Nitpick comments (38)
src/main/java/konkuk/thip/notification/adapter/in/web/request/NotificationMarkToCheckedRequest.java (1)

9-11: ID 유효성 강화 (@min) 및 스키마 required 표시

음수/0 차단과 스웨거 required 명시를 권장합니다.

 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Min;

 @Schema(description = "알림 읽음 처리 요청 DTO")
 public record NotificationMarkToCheckedRequest(

-        @NotNull
-        @Schema(description = "읽음 처리할 알림 ID", example = "1")
+        @NotNull
+        @Min(1)
+        @Schema(description = "읽음 처리할 알림 ID", example = "1", requiredMode = Schema.RequiredMode.REQUIRED)
         Long notificationId
 ) { }
src/main/java/konkuk/thip/notification/domain/value/NotificationRedirectSpecConverter.java (4)

17-17: ObjectMapper 전역 설정 정렬

글로벌 설정과의 불일치/시간 타입 역직렬화 이슈 방지를 위해 모듈 등록 + UNKNOWN 무시 권장.

-    private static final ObjectMapper objectMapper = new ObjectMapper();
+    private static final ObjectMapper objectMapper = new ObjectMapper()
+            .findAndRegisterModules()
+            .configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

24-26: 예외 원인(cause) 보존

스택트레이스 유실 방지. InvalidStateException 오버로드가 있으니 cause 전달 권장.

-        } catch (JsonProcessingException e) {
-            throw new InvalidStateException(NOTIFICATION_REDIRECT_DATA_SERIALIZE_FAILED);
+        } catch (JsonProcessingException e) {
+            throw new InvalidStateException(NOTIFICATION_REDIRECT_DATA_SERIALIZE_FAILED, e);
         }

34-36: 역직렬화 예외 cause 보존

동일하게 cause 전달.

-        } catch (IOException e) {
-            throw new InvalidStateException(NOTIFICATION_REDIRECT_DATA_DESERIALIZE_FAILED);
+        } catch (IOException e) {
+            throw new InvalidStateException(NOTIFICATION_REDIRECT_DATA_DESERIALIZE_FAILED, e);
         }

31-31: null ↔ none 비대칭 동작 검증

DB null을 도메인 none()으로 치환하는 정책이 의도인지 확인 바랍니다. 라운드트립 대칭성 요구 시 null 유지가 맞습니다.

-        if (dbData == null || dbData.isBlank()) return NotificationRedirectSpec.none();
+        if (dbData == null || dbData.isBlank()) return null;
src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1)

242-244: INVALID_FE_PLATFORM의 분류 확인

util 섹션 배치는 합리적이나, 도메인별(FCM) 전용으로 쓰인다면 섹션 재배치 여부 검토 바랍니다. 기능엔 영향 없음.

src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (2)

361-363: FCM_TOKEN_REGISTER에 FCM_TOKEN_NOT_FOUND 포함 적절성 재검토

등록 시점에 404 토큰 미존재가 발생하는가요? 사용 흐름상 부자연스러우면 제외 권장.

-    FCM_TOKEN_REGISTER(new LinkedHashSet<>(Set.of(
-            USER_NOT_FOUND,
-            FCM_TOKEN_NOT_FOUND,
-            INVALID_FE_PLATFORM
-    ))),
+    FCM_TOKEN_REGISTER(new LinkedHashSet<>(Set.of(
+            USER_NOT_FOUND,
+            INVALID_FE_PLATFORM
+    ))),

358-358: 주석 오타

"Notiification" → "Notification".

-    // Notiification
+    // Notification
src/main/java/konkuk/thip/notification/adapter/out/jpa/NotificationJpaEntity.java (2)

41-44: redirect_spec 컬럼 매핑 방안 개선 제안

  • DB 이식성: columnDefinition("TEXT") 대신 @lob 사용 검토.
  • MySQL 8+: JSON 타입 사용 시 유효성/함수/인덱싱 이점.
-    @Convert(converter = NotificationRedirectSpecConverter.class)
-    @Column(name = "redirect_spec", columnDefinition = "TEXT")  // nullable
-    private NotificationRedirectSpec redirectSpec;
+    @Convert(converter = NotificationRedirectSpecConverter.class)
+    @Lob
+    @Column(name = "redirect_spec", nullable = true)
+    private NotificationRedirectSpec redirectSpec;

비즈니스가 route별 조회/통계를 원한다면, JSON 컬럼 + 생성/가상 컬럼 인덱스(Route 등)도 고려 바랍니다.


45-47: 엔티티-도메인 결합도 축소

JPA 엔티티가 도메인 객체를 직접 참조(updateFrom(Notification))하는 패턴은 레이어 결합을 높입니다. 매퍼로 위임하거나 원시값 파라미터로 축소 권장.

-    public void updateFrom(Notification notification) {
-        this.isChecked = notification.isChecked();  // 현재는 isChecked만 업데이트 가능
-    }
+    public void updateChecked(boolean checked) {
+        this.isChecked = checked;
+    }
src/main/java/konkuk/thip/notification/adapter/out/jpa/FcmTokenJpaEntity.java (1)

34-35: last_used_time 정밀도 확인

LocalDate(일 단위)로 충분한지 검토 바랍니다. 필요 시 LocalDateTime 전환 고려.

src/main/resources/db/migration/V250921__Add_notification_redirect_spec.sql (1)

2-3: 컬럼 타입 선택 검토(TEXT vs JSON)

MySQL 8+ 환경이면 JSON 타입 사용으로 유효성/부분 인덱싱 이점이 있습니다. 멀티 DB 타겟이면 TEXT 유지도 무방.

-- 대안 (MySQL 8+)
ALTER TABLE `notifications`
  ADD COLUMN `redirect_spec` JSON NULL
  COMMENT 'NotificationRedirectSpec을 JSON 컬럼으로 저장';
src/main/java/konkuk/thip/notification/adapter/out/mapper/NotificationMapper.java (1)

18-18: null‑safety를 위한 기본값 적용 제안

DB/도메인 어디서든 null이 올 수 있다면 none()으로 강제 기본값을 두면 NPE/직렬화 이슈 방지에 유리합니다.

아래처럼 안전 가드를 추가하는 방안 고려해주세요:

@@ public class NotificationMapper {
-                .redirectSpec(notification.getRedirectSpec())
+                .redirectSpec(
+                        java.util.Optional.ofNullable(notification.getRedirectSpec())
+                                .orElse(konkuk.thip.notification.domain.value.NotificationRedirectSpec.none())
+                )
@@
-                .redirectSpec(notificationJpaEntity.getRedirectSpec())
+                .redirectSpec(
+                        java.util.Optional.ofNullable(notificationJpaEntity.getRedirectSpec())
+                                .orElse(konkuk.thip.notification.domain.value.NotificationRedirectSpec.none())
+                )

Also applies to: 30-30

src/test/java/konkuk/thip/notification/application/service/RoomNotificationOrchestratorSyncImplTest.java (1)

129-137: 테스트 가독성/실패 메시지 개선: 조회 결과 크기 검증 후 get(0)

바로 get(0) 호출 시 비어 있으면 IOBE만 발생합니다. 크기 검증을 먼저 두면 실패 원인이 더 명확합니다.

다음 수정 제안:

-        NotificationJpaEntity saved = notificationJpaRepository.findAll().get(0);
+        var all = notificationJpaRepository.findAll();
+        assertThat(all).hasSize(1);
+        NotificationJpaEntity saved = all.get(0);
src/test/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImplTest.java (1)

109-117: 테스트 가독성/실패 메시지 개선: 조회 결과 크기 검증 후 get(0)

Room 테스트와 동일하게, 목록 크기를 먼저 검증하면 디버깅이 쉬워집니다.

-        NotificationJpaEntity saved = notificationJpaRepository.findAll().get(0);
+        var all = notificationJpaRepository.findAll();
+        assertThat(all).hasSize(1);
+        NotificationJpaEntity saved = all.get(0);
src/test/java/konkuk/thip/notification/application/service/NotificationSyncExecutorTest.java (1)

38-41: 퍼블리시 호출 검증을 Mockito로 강화 제안

람다 대신 목을 사용하면 예외 상황에서도 호출 여부와 전달된 notificationId(42L)를 명확히 검증할 수 있습니다.

-        EventCommandInvoker invoker = (title, content, notificationId) -> {
-            throw new RuntimeException("강제 퍼블리시 실패");
-        };
+        EventCommandInvoker invoker = mock(EventCommandInvoker.class);
+        doThrow(new RuntimeException("강제 퍼블리시 실패"))
+                .when(invoker).publish(anyString(), anyString(), anyLong());
@@
         assertThatCode(() ->
                 executor.execute(template, "dummyArgs", 123L, invoker)
         ).doesNotThrowAnyException();
+        // 퍼블리시가 한 번 시도되었고, 저장된 ID(42L)가 전달되었는지 검증
+        verify(invoker, times(1)).publish("테스트제목", "테스트내용", 42L);

Also applies to: 42-46, 51-57

src/main/java/konkuk/thip/common/util/PlatformType.java (2)

17-24: 입력 정규화/널 처리로 복원력 강화

FE에서 "web", " android " 등 케이스/공백이 섞여 올 수 있습니다. trim + upper(Locale.ROOT) 후 매칭하고, null은 명시적으로 예외를 던지도록 하면 방어력이 좋아집니다.

 package konkuk.thip.common.util;

 import konkuk.thip.common.exception.InvalidStateException;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
+import java.util.Locale;

 import static konkuk.thip.common.exception.code.ErrorCode.INVALID_FE_PLATFORM;
 
 @Getter
 @RequiredArgsConstructor
 public enum PlatformType {
@@
     private final String value;
 
     public static PlatformType from(String value) {
-        for (PlatformType type : PlatformType.values()) {
-            if (type.getValue().equals(value)) {
-                return type;
-            }
-        }
+        if (value == null) {
+            throw new InvalidStateException(INVALID_FE_PLATFORM);
+        }
+        String normalized = value.trim().toUpperCase(Locale.ROOT);
+        for (PlatformType type : PlatformType.values()) {
+            if (type.getValue().equals(normalized)) {
+                return type;
+            }
+        }
         throw new InvalidStateException(INVALID_FE_PLATFORM);
     }
 }

9-11: Lombok 사용 축소(선택): enum에 @requiredargsconstructor 불필요

enum은 고정 생성자를 가지므로 Lombok 없이도 명확합니다. 가독성 측면에서 제거를 검토해도 좋습니다.

src/main/java/konkuk/thip/message/adapter/in/event/MessageFeedEventListener.java (1)

37-39: 메서드명과 파라미터 타입 불일치: 네이밍 정합성 개선 제안

이벤트 타입이 FolloweeNewFeedEvent로 바뀐 만큼 메서드명도 onFolloweeNewFeed로 맞추는 편이 추후 유지보수에 유리합니다.

-    public void onFolloweeNewPost(FeedEvents.FolloweeNewFeedEvent e) {
-        feedUseCase.handleFolloweeNewFeed(e);
-    }
+    public void onFolloweeNewFeed(FeedEvents.FolloweeNewFeedEvent e) {
+        feedUseCase.handleFolloweeNewFeed(e);
+    }
src/main/java/konkuk/thip/notification/application/service/NotificationMarkService.java (1)

28-33: (대안) JS 정밀도 이슈 완화: 서버에서 숫자 값을 문자열로 변환 후 반환

현 구조를 유지하되 FE 안전성을 올리려면, params 내 Number를 문자열로 변환한 사본을 만들어 응답하세요.

-        return new NotificationMarkToCheckedResponse(
-                notification.getRedirectSpec().route(),
-                notification.getRedirectSpec().params()
-        );
+        Map<String, Object> raw = notification.getRedirectSpec().params();
+        Map<String, Object> safe = new java.util.HashMap<>(raw.size());
+        for (Map.Entry<String, Object> e : raw.entrySet()) {
+            Object v = e.getValue();
+            safe.put(e.getKey(), (v instanceof Number) ? v.toString() : v);
+        }
+        return new NotificationMarkToCheckedResponse(
+                notification.getRedirectSpec().route(),
+                java.util.Map.copyOf(safe)
+        );

FE와 협의하여 숫자→문자열 변환 범위(모든 Number vs Long/BigInteger 한정)를 결정해 주세요.

src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenRegisterRequest.java (2)

19-22: platformType 유효성 강화 (@notblank + 허용값 제한 제안)

DTO 레벨에서 문자열 공백/빈값이 들어와도 통과합니다. 허용값(ANDROID|WEB)을 스키마/검증에서 즉시 거르는 편이 낫습니다.

-        @NotNull
-        @Schema(description = "플랫폼 타입 (ANDROID 또는 WEB)", example = "ANDROID")
-        String platformType
+        @NotBlank
+        @Schema(description = "플랫폼 타입 (ANDROID 또는 WEB)", example = "ANDROID", allowableValues = {"ANDROID","WEB"})
+        String platformType

27-29: PlatformType 변환 시 공백/대소문자 안전성 확보

from(String)의 동작(대소문자/트리밍/예외 타입)을 확실히 하거나, DTO에서 선제 정규화가 안전합니다.

-                .platformType(PlatformType.from(this.platformType))
+                .platformType(PlatformType.from(this.platformType.trim()))

from(String)이 대소문자 무시를 지원하지 않으면 내부에서 toUpperCase(Locale.ROOT) 처리/검증을 추가해주세요.

src/test/java/konkuk/thip/notification/domain/NotificationTest.java (1)

24-27: 예외 검증을 메시지 대신 코드 중심으로 변경 고려

메시지 동등성은 취약합니다. 가능하면 ErrorCode 자체나 코드값으로 검증하세요(예: getErrorCode() 노출 시).

Also applies to: 38-41

src/test/java/konkuk/thip/notification/adapter/in/web/NotificationMarkToCheckedApiTest.java (1)

96-122: 추가 케이스 제안: NOT_FOUND 및 멱등 재시도

  • 존재하지 않는 notificationId → 404(NOTIFICATION_NOT_FOUND) 검증 추가 제안.
  • 중복 클릭(동시/재시도) 시 의도된 응답(200 재응답 vs 400) 정책을 테스트로 명시하면 FE 연동 안정성이 올라갑니다.
src/main/java/konkuk/thip/notification/domain/value/MessageRoute.java (1)

3-8: 불필요한 Lombok 제거

필드가 없어 @Getter/@requiredargsconstructor는 무의미합니다. 의존성/바이트코드 최소화를 위해 제거 권장.

-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-
-@Getter
-@RequiredArgsConstructor
 public enum MessageRoute {
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)

386-402: redirectSpec 파라미터 방어적 복사로 테스트 안정성 향상

외부에서 전달된 Map 변경으로 인한 플래키 방지를 위해 불변 복사 권장.

-    public static NotificationRedirectSpec createNotificationRedirectSpec(MessageRoute route, Map<String, Object> params) {
-        return new NotificationRedirectSpec(route, params);
-    }
+    public static NotificationRedirectSpec createNotificationRedirectSpec(MessageRoute route, Map<String, Object> params) {
+        return new NotificationRedirectSpec(route, Map.copyOf(params));
+    }
src/main/java/konkuk/thip/notification/application/port/out/NotificationCommandPort.java (1)

12-22: CQRS 컨벤션 부합 + 잠금 조회 포트 추가 고려

save 반환형/Optional 조회/예외 변환 기본 메서드 구성 좋습니다. 동시 클릭(중복 요청) 시 TOCTOU를 줄이려면 잠금 조회(예: findByIdForUpdate) 포트도 고려 바랍니다.

 public interface NotificationCommandPort {
 
     Long save(Notification notification);
 
     Optional<Notification> findById(Long id);
 
     default Notification getByIdOrThrow(Long id) {
         return findById(id)
                 .orElseThrow(() -> new EntityNotFoundException(NOTIFICATION_NOT_FOUND));
     }
 
+    /**
+     * 업데이트 목적의 잠금 조회 (persistence 어댑터에서 SELECT ... FOR UPDATE 등으로 구현)
+     */
+    default Optional<Notification> findByIdForUpdate(Long id) { return Optional.empty(); }
+
     void update(Notification notification);
 }
src/test/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImplUnitTest.java (1)

39-52: Invoker 경로 검증 적절함 + 잔여 상호작용 검증 제안

현재 검증은 충분합니다. 추가로 verifyNoMoreInteractions(feedEventCommandPort) 넣으면 시그니처 변동 시 빠르게 감지 가능합니다.

src/main/java/konkuk/thip/notification/domain/Notification.java (1)

31-41: 도메인 불변식 보강: 필수 인자 null 방지

빌더 진입점에서 필수값을 즉시 검증하면 오류를 조기 발견할 수 있습니다(특히 targetUserId, notificationCategory).

// 추가 제안 (메서드 내부 선행 검사)
java.util.Objects.requireNonNull(notificationCategory, "notificationCategory");
java.util.Objects.requireNonNull(targetUserId, "targetUserId");
src/test/java/konkuk/thip/message/adapter/in/event/MessageRoomEventListenerTest.java (1)

34-38: 테스트 데이터 의미 명확화 필요

테스트에서 사용하는 title과 content가 "title", "content"라는 리터럴 문자열입니다. 실제 알림 메시지 포맷을 반영한 더 의미있는 테스트 데이터를 사용하면 테스트의 가독성과 의도 파악이 개선될 것 같습니다.

         var e = RoomEvents.RoomPostCommentedEvent.builder()
-                .title("title")
-                .content("content")
+                .title("[방 이름] 새로운 댓글")
+                .content("사용자님이 댓글을 작성했습니다")
                 .notificationId(1L)
                 .targetUserId(10L)
                 .build();
src/main/java/konkuk/thip/notification/application/service/RoomNotificationOrchestratorSyncImpl.java (1)

68-69: 하드코딩된 postType 값

postType이 "VOTE"로 하드코딩되어 있습니다. 이는 상수로 관리하거나 enum을 활용하는 것이 유지보수성 측면에서 더 좋을 것 같습니다.

+        private static final String POST_TYPE_VOTE = "VOTE";
+        
         NotificationRedirectSpec redirectSpec = new NotificationRedirectSpec(
                 MessageRoute.ROOM_VOTE_DETAIL,
                 Map.of(
                         "roomId", roomId,
                         "page", page,
                         "postId", postId,
-                        "postType", "VOTE"
+                        "postType", POST_TYPE_VOTE
                 )
         );
src/test/java/konkuk/thip/message/adapter/in/event/MessageFeedEventListenerTest.java (1)

21-21: 테스트 클래스 DisplayName 오류

클래스의 @DisplayName이 "[단위] MessageRoomEventListener 단위 테스트"로 되어 있는데, 실제로는 MessageFeedEventListenerTest 클래스입니다.

 @SpringBootTest
 @ActiveProfiles("test")
-@DisplayName("[단위] MessageRoomEventListener 단위 테스트")
+@DisplayName("[단위] MessageFeedEventListener 단위 테스트")
 class MessageFeedEventListenerTest {
src/main/java/konkuk/thip/notification/adapter/in/web/NotificationCommandController.java (1)

68-72: API 문서화 개선 제안

Operation description에 리다이렉트 데이터의 구체적인 형식(route와 params 구조)을 명시하면 API 사용자에게 더 도움이 될 것 같습니다.

     @Operation(
             summary = "유저의 특정 알림 읽음 처리",
-            description = "유저가 특정 알림을 읽음 처리합니다. 읽음 처리 후, 해당 알림의 페이지로 리다이렉트를 위한 데이터를 응답합니다."
+            description = "유저가 특정 알림을 읽음 처리합니다. 읽음 처리 후, 해당 알림의 페이지로 리다이렉트를 위한 데이터(route 경로와 필요한 파라미터)를 응답합니다. 웹은 이를 통해 페이지 URL을, 앱은 API 요청 URL을 구성합니다."
     )
src/main/java/konkuk/thip/message/application/service/RoomNotificationDispatchService.java (1)

72-94: 빈/불량 토큰 처리 및 관찰성 보강 제안

  • 잠재 이슈: null/공백 토큰이 포함되면 FCM InvalidRegistration로 전체 배치에 영향을 줄 수 있습니다.
  • 제안: 공백/NULL 토큰을 건너뛰고, skip 건수를 디버그 로그로 남깁니다. 토큰이 0개면 리턴 전에 디버그 로그 1건 남기면 운영 추적에 유용합니다.

적용 예시:

@@
-    private void push(Long userId, String title, String content, Long notificationId) {
+    private void push(Long userId, String title, String content, Long notificationId) {
         Notification notification = buildNotification(title, content);
 
         List<FcmToken> tokens = fcmTokenQueryPort.findEnabledByUserId(userId);
-        if (tokens.isEmpty()) return;
+        if (tokens.isEmpty()) {
+            // log.debug("No FCM tokens for userId={}, skip", userId);
+            return;
+        }
@@
-        for (FcmToken t : tokens) {
-            Message m = Message.builder()
-                    .setToken(t.getFcmToken())
+        for (FcmToken t : tokens) {
+            final String tokenStr = t.getFcmToken();
+            if (tokenStr == null || tokenStr.isBlank()) {
+                // log.debug("Skip blank FCM token for deviceId={}", t.getDeviceId());
+                continue;
+            }
+            Message m = Message.builder()
+                    .setToken(tokenStr)
                     .setNotification(notification)
                     .putData("category", NotificationCategory.ROOM.getDisplay())
                     .putData("action", "OPEN_NOTIFICATION") // FE는 이 액션으로 알림 상세/라우팅을 BE api 요청으로 처리
                     .putData("notificationId", String.valueOf(notificationId))
                     .build();
-            msgs.add(m); tk.add(t.getFcmToken()); dev.add(t.getDeviceId());
+            msgs.add(m); tk.add(tokenStr); dev.add(t.getDeviceId());
         }
 
-        firebasePort.sendBatch(msgs, tk, dev);
+        firebasePort.sendBatch(msgs, tk, dev);
     }

추가로, 데이터 키("category","action","notificationId")는 상수로 올려두면(typo 예방) 유지보수성이 좋아집니다.

FE가 기대하는 action 문자열이 정확히 "OPEN_NOTIFICATION"인지 최종 확인해 주세요(대소문자/스펙 합의).

src/main/java/konkuk/thip/message/application/service/FeedNotificationDispatchService.java (3)

27-30: 코드 중복: FCM 알림 생성 및 전송 로직이 반복됩니다

모든 이벤트 핸들러에서 동일한 패턴(buildFcmNotificationpush)이 반복되고 있습니다. 이는 유지보수성을 저하시킬 수 있습니다.

제네릭 핸들러 메서드를 도입하여 중복을 제거하는 것을 권장합니다:

+    private void handleNotificationEvent(String title, String content, Long targetUserId, Long notificationId) {
+        Notification n = buildFcmNotification(title, content);
+        push(targetUserId, n, notificationId);
+    }
+
     @Override
     public void handleFollower(final FeedEvents.FollowerEvent event) {
-        Notification n = buildFcmNotification(event.title(), event.content());
-        push(event.targetUserId(), n, event.notificationId());
+        handleNotificationEvent(event.title(), event.content(), event.targetUserId(), event.notificationId());
     }

이렇게 하면 향후 로직 변경 시 한 곳만 수정하면 됩니다.

Also applies to: 32-36, 38-42, 44-48, 50-54, 56-60


74-75: 하드코딩된 문자열 상수화 필요

"OPEN_NOTIFICATION" 액션 문자열이 하드코딩되어 있습니다. 이는 타이핑 실수의 위험이 있고, 향후 변경 시 누락될 수 있습니다.

클래스 레벨 상수로 추출하는 것을 권장합니다:

 public class FeedNotificationDispatchService implements FeedNotificationDispatchUseCase {
 
+    private static final String ACTION_OPEN_NOTIFICATION = "OPEN_NOTIFICATION";
+    
     private final FcmTokenPersistencePort fcmTokenPersistencePort;
-                    .putData("action", "OPEN_NOTIFICATION") // FE는 이 액션으로 알림 상세/라우팅을 BE api 요청으로 처리
+                    .putData("action", ACTION_OPEN_NOTIFICATION) // FE는 이 액션으로 알림 상세/라우팅을 BE api 요청으로 처리

66-67: 변수명 개선 필요

tkdev 같은 축약형 변수명은 코드 가독성을 저하시킵니다.

더 명확한 변수명 사용을 권장합니다:

-        List<String> tk  = new ArrayList<>(tokens.size());
-        List<String> dev = new ArrayList<>(tokens.size());
+        List<String> tokenStrings = new ArrayList<>(tokens.size());
+        List<String> deviceIds = new ArrayList<>(tokens.size());

그리고 Line 80-81, 84도 함께 수정:

-            tk.add(t.getFcmToken());
-            dev.add(t.getDeviceId());
+            tokenStrings.add(t.getFcmToken());
+            deviceIds.add(t.getDeviceId());

-        firebasePort.sendBatch(msgs, tk, dev);
+        firebasePort.sendBatch(msgs, tokenStrings, deviceIds);
src/main/java/konkuk/thip/message/adapter/out/event/RoomEventPublisherAdapter.java (1)

16-25: 반복적인 이벤트 빌더 패턴

모든 메서드에서 동일한 빌더 패턴이 반복되고 있습니다. 이는 코드 중복을 야기하고 있습니다.

제네릭 헬퍼 메서드를 통해 중복을 제거할 수 있습니다:

+    private <T> T buildEvent(Function<EventBuilder<T>, T> builderFunction,
+                              String title, String content, Long notificationId, Long targetUserId) {
+        return builderFunction.apply(new EventBuilder<>(title, content, notificationId, targetUserId));
+    }
+
+    @FunctionalInterface
+    private interface EventBuilder<T> {
+        T build(String title, String content, Long notificationId, Long targetUserId);
+    }

     @Override
     public void publishRoomPostCommentedEvent(
             String title, String content, Long notificationId,
             Long targetUserId) {
-        publisher.publishEvent(RoomEvents.RoomPostCommentedEvent.builder()
-                .title(title)
-                .content(content)
-                .notificationId(notificationId)
-                .targetUserId(targetUserId)
-                .build());
+        publisher.publishEvent(
+            RoomEvents.RoomPostCommentedEvent.builder()
+                .title(title).content(content)
+                .notificationId(notificationId).targetUserId(targetUserId)
+                .build()
+        );
     }

하지만 Java record의 빌더 패턴 특성상 위 접근이 복잡할 수 있으므로, 대안으로 각 이벤트를 직접 생성하는 팩토리 메서드를 고려해볼 수 있습니다.

Also applies to: 27-37, 39-49, 51-61, 63-73, 75-85, 87-97, 99-109, 111-121

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d96c12e and 4b6e9c5.

📒 Files selected for processing (49)
  • src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1 hunks)
  • src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (2 hunks)
  • src/main/java/konkuk/thip/common/util/PlatformType.java (1 hunks)
  • src/main/java/konkuk/thip/feed/application/service/FeedCreateService.java (1 hunks)
  • src/main/java/konkuk/thip/message/adapter/in/event/MessageFeedEventListener.java (1 hunks)
  • src/main/java/konkuk/thip/message/adapter/out/event/FeedEventPublisherAdapter.java (1 hunks)
  • src/main/java/konkuk/thip/message/adapter/out/event/RoomEventPublisherAdapter.java (1 hunks)
  • src/main/java/konkuk/thip/message/adapter/out/event/dto/FeedEvents.java (1 hunks)
  • src/main/java/konkuk/thip/message/adapter/out/event/dto/RoomEvents.java (1 hunks)
  • src/main/java/konkuk/thip/message/application/port/in/FeedNotificationDispatchUseCase.java (1 hunks)
  • src/main/java/konkuk/thip/message/application/port/out/FeedEventCommandPort.java (1 hunks)
  • src/main/java/konkuk/thip/message/application/port/out/RoomEventCommandPort.java (1 hunks)
  • src/main/java/konkuk/thip/message/application/service/FeedNotificationDispatchService.java (1 hunks)
  • src/main/java/konkuk/thip/message/application/service/RoomNotificationDispatchService.java (1 hunks)
  • src/main/java/konkuk/thip/message/domain/MessageRoute.java (0 hunks)
  • src/main/java/konkuk/thip/notification/adapter/in/web/NotificationCommandController.java (3 hunks)
  • src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenRegisterRequest.java (2 hunks)
  • src/main/java/konkuk/thip/notification/adapter/in/web/request/NotificationMarkToCheckedRequest.java (1 hunks)
  • src/main/java/konkuk/thip/notification/adapter/in/web/response/NotificationMarkToCheckedResponse.java (1 hunks)
  • src/main/java/konkuk/thip/notification/adapter/out/jpa/FcmTokenJpaEntity.java (1 hunks)
  • src/main/java/konkuk/thip/notification/adapter/out/jpa/NotificationJpaEntity.java (2 hunks)
  • src/main/java/konkuk/thip/notification/adapter/out/mapper/NotificationMapper.java (2 hunks)
  • src/main/java/konkuk/thip/notification/adapter/out/persistence/NotificationCommandPersistenceAdapter.java (3 hunks)
  • src/main/java/konkuk/thip/notification/application/port/in/FeedNotificationOrchestrator.java (1 hunks)
  • src/main/java/konkuk/thip/notification/application/port/in/NotificationMarkUseCase.java (1 hunks)
  • src/main/java/konkuk/thip/notification/application/port/in/dto/FcmTokenRegisterCommand.java (1 hunks)
  • src/main/java/konkuk/thip/notification/application/port/out/NotificationCommandPort.java (1 hunks)
  • src/main/java/konkuk/thip/notification/application/service/EventCommandInvoker.java (1 hunks)
  • src/main/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImpl.java (6 hunks)
  • src/main/java/konkuk/thip/notification/application/service/NotificationMarkService.java (1 hunks)
  • src/main/java/konkuk/thip/notification/application/service/NotificationSyncExecutor.java (2 hunks)
  • src/main/java/konkuk/thip/notification/application/service/RoomNotificationOrchestratorSyncImpl.java (10 hunks)
  • src/main/java/konkuk/thip/notification/domain/FcmToken.java (1 hunks)
  • src/main/java/konkuk/thip/notification/domain/Notification.java (2 hunks)
  • src/main/java/konkuk/thip/notification/domain/value/MessageRoute.java (1 hunks)
  • src/main/java/konkuk/thip/notification/domain/value/NotificationRedirectSpec.java (1 hunks)
  • src/main/java/konkuk/thip/notification/domain/value/NotificationRedirectSpecConverter.java (1 hunks)
  • src/main/java/konkuk/thip/notification/domain/value/PlatformType.java (0 hunks)
  • src/main/resources/db/migration/V250921__Add_notification_redirect_spec.sql (1 hunks)
  • src/test/java/konkuk/thip/common/util/TestEntityFactory.java (3 hunks)
  • src/test/java/konkuk/thip/message/adapter/in/event/MessageFeedEventListenerTest.java (2 hunks)
  • src/test/java/konkuk/thip/message/adapter/in/event/MessageRoomEventListenerTest.java (2 hunks)
  • src/test/java/konkuk/thip/notification/adapter/in/web/NotificationMarkToCheckedApiTest.java (1 hunks)
  • src/test/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImplTest.java (1 hunks)
  • src/test/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImplUnitTest.java (2 hunks)
  • src/test/java/konkuk/thip/notification/application/service/NotificationSyncExecutorTest.java (3 hunks)
  • src/test/java/konkuk/thip/notification/application/service/RoomNotificationOrchestratorSyncImplTest.java (1 hunks)
  • src/test/java/konkuk/thip/notification/application/service/RoomNotificationOrchestratorSyncImplUnitTest.java (1 hunks)
  • src/test/java/konkuk/thip/notification/domain/NotificationTest.java (1 hunks)
💤 Files with no reviewable changes (2)
  • src/main/java/konkuk/thip/message/domain/MessageRoute.java
  • src/main/java/konkuk/thip/notification/domain/value/PlatformType.java
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#113
File: src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java:38-44
Timestamp: 2025-07-30T14:05:04.945Z
Learning: seongjunnoh는 코드 최적화 제안에 대해 구체적인 기술적 근거와 효율성 차이를 이해하고 싶어하며, 성능 개선 방식에 대한 상세한 설명을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#93
File: src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java:49-114
Timestamp: 2025-07-28T16:44:31.224Z
Learning: seongjunnoh는 코드 중복 문제에 대한 리팩토링 제안을 적극적으로 수용하고 함수형 인터페이스를 활용한 해결책을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#285
File: src/main/java/konkuk/thip/room/adapter/out/jpa/RoomStatus.java:1-7
Timestamp: 2025-08-31T05:25:14.845Z
Learning: seongjunnoh는 enum 의존성에 대해 유연한 접근을 선호하며, 도메인→어댑터 레이어 참조와 같은 아키텍처 layering 원칙보다 실용적인 구현을 우선시한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#195
File: src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java:0-0
Timestamp: 2025-08-13T05:22:32.287Z
Learning: seongjunnoh는 데이터 무결성과 중복 방지에 대한 고민이 깊으며, LinkedHashSet을 활용한 중복 제거와 순서 보장을 동시에 달성하는 솔루션을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#305
File: src/main/java/konkuk/thip/notification/adapter/out/persistence/repository/NotificationQueryRepository.java:9-13
Timestamp: 2025-09-17T06:40:49.863Z
Learning: seongjunnoh는 메서드 네이밍 시 구현 세부사항보다 비즈니스 의도를 반영하는 것을 선호하며, 미래 확장성과 아키텍처 변화를 고려한 설계 철학을 가지고 있다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#278
File: src/main/java/konkuk/thip/TestTokenController.java:0-0
Timestamp: 2025-08-24T09:33:52.982Z
Learning: seongjunnoh는 Spring의 ConditionalOnProperty 동작 원리를 정확히 이해하고 있으며, 보안 이슈에 대해서도 실질적인 위험성을 기준으로 판단하는 실용적 접근을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#180
File: src/main/java/konkuk/thip/room/application/service/AttendanceCheckCreateService.java:22-40
Timestamp: 2025-08-14T09:15:31.371Z
Learning: seongjunnoh는 기술적 완벽성보다 실제 비즈니스 시나리오와 사용자 행동 패턴을 우선 고려하며, 발생 가능성이 낮은 엣지 케이스보다는 실용적인 구현을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#278
File: src/main/java/konkuk/thip/common/exception/code/ErrorCode.java:210-211
Timestamp: 2025-08-24T09:40:48.595Z
Learning: seongjunnoh는 HTTP 상태 코드 선택에 대해 기술적 근거와 코드베이스 내 일관성을 중요하게 생각하며, 구체적인 사례 분석을 통한 설명을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#112
File: src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java:272-272
Timestamp: 2025-07-30T10:44:34.115Z
Learning: seongjunnoh는 피드 커서 페이지네이션에서 LocalDateTime 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.
📚 Learning: 2025-09-05T05:26:24.628Z
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#293
File: src/main/java/konkuk/thip/notification/adapter/out/jpa/FcmTokenJpaEntity.java:44-51
Timestamp: 2025-09-05T05:26:24.628Z
Learning: FcmTokenJpaEntity에서 deviceId는 디바이스를 식별하는 불변 값으로, updateFrom 메서드에서 동기화할 필요가 없음

Applied to files:

  • src/main/java/konkuk/thip/notification/adapter/out/jpa/FcmTokenJpaEntity.java
📚 Learning: 2025-07-03T03:05:05.031Z
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.

Applied to files:

  • src/main/java/konkuk/thip/notification/application/port/out/NotificationCommandPort.java
📚 Learning: 2025-07-26T06:09:00.850Z
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#101
File: src/main/java/konkuk/thip/comment/application/service/CommentCreateService.java:36-39
Timestamp: 2025-07-26T06:09:00.850Z
Learning: THIP 프로젝트에서 Record와 Vote는 Room에 속하지만 Feed는 Room에 속하지 않는 구조이며, 댓글 작성 시 Record/Vote에 대해서만 사용자가 해당 Room의 참가자인지 검증이 필요하다.

Applied to files:

  • src/main/java/konkuk/thip/message/adapter/out/event/dto/RoomEvents.java
🧬 Code graph analysis (3)
src/main/java/konkuk/thip/message/adapter/out/event/FeedEventPublisherAdapter.java (1)
src/main/java/konkuk/thip/message/adapter/out/event/dto/FeedEvents.java (1)
  • FeedEvents (5-42)
src/test/java/konkuk/thip/notification/adapter/in/web/NotificationMarkToCheckedApiTest.java (1)
src/test/java/konkuk/thip/common/util/TestEntityFactory.java (1)
  • TestEntityFactory (35-403)
src/main/java/konkuk/thip/message/adapter/out/event/RoomEventPublisherAdapter.java (1)
src/main/java/konkuk/thip/message/adapter/out/event/dto/RoomEvents.java (1)
  • RoomEvents (5-61)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (33)
src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (2)

234-239: 알림 에러 코드 추가 적절

시멘틱/HTTP 매핑 일관성 좋아 보입니다.


1-257: 에러 코드 중복 없음 — 검증 완료
ErrorCode.java에서 정수 코드 중복 미발견 (총 137개, 고유 137개).

src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (1)

380-384: 알림 읽음 처리 응답 스펙 추가 LGTM

필요 에러군이 잘 매핑되었습니다.

src/main/java/konkuk/thip/notification/domain/FcmToken.java (2)

6-6: PlatformType 공통 util로 이관 LGTM

도메인 간 일관성 측면에서 긍정적입니다.


49-54: updateToken에서 소유자(userId) 변경 허용 검토

토큰 소유 이전이 의도된 케이스인지 확인 필요. 오남용 시 접근 통제 취약점 소지.

src/main/java/konkuk/thip/notification/adapter/out/jpa/FcmTokenJpaEntity.java (1)

44-51: deviceId 비동기화 유지 적절

과거 합의대로 deviceId는 불변값이며 updateFrom에서 동기화하지 않는 현재 구현이 맞습니다.

src/main/resources/db/migration/V250921__Add_notification_redirect_spec.sql (1)

1-3: 배포 안전성 체크

대용량 테이블이면 온라인 DDL(innodb_online_alter_log_max_size 등) 설정/점검 권장.

src/main/java/konkuk/thip/notification/adapter/in/web/request/NotificationMarkToCheckedRequest.java (1)

6-12: 검증 완료 — @Valid 적용 확인

NotificationMarkToCheckedRequest는 컨트롤러의 markNotificationToChecked 메서드 파라미터에 @RequestBody @Valid로 선언되어 있어 @NotNull 검증이 적용됩니다. (src/main/java/konkuk/thip/notification/adapter/in/web/NotificationCommandController.java:75)

src/main/java/konkuk/thip/notification/adapter/out/mapper/NotificationMapper.java (1)

18-18: redirectSpec 매핑 추가 OK

도메인↔JPA 매핑에 redirectSpec을 포함한 점 일관적입니다. 저장/조회 경로 모두 누락 없습니다.

  • NotificationRedirectSpecConverter가 null 입력/출력을 안전하게 처리하는지 확인 부탁드립니다(기본 none() 유지 여부).
  • round-trip 매핑 테스트(도메인→JPA→도메인)로 route/params 보존 검증 추천합니다.

Also applies to: 30-30

src/main/java/konkuk/thip/feed/application/service/FeedCreateService.java (1)

74-75: 메서드 명 변경 일관성 확인

notifyFolloweeNewPost → notifyFolloweeNewFeed 호출로 정리된 부분 좋습니다. 구현/테스트 전역에서 동일 네이밍으로 통일됐는지 한 번 더 점검만 부탁드립니다.

빠른 점검:

  • 컴파일 시그니처 불일치 여부
  • 이전 이벤트 타입(…NewPost) 참조 잔존 여부
src/test/java/konkuk/thip/notification/application/service/NotificationSyncExecutorTest.java (1)

23-24: save 스텁으로 notificationId 고정 — 좋은 테스트 설계

ID를 고정해 이후 퍼블리시 파라미터를 예측 가능하게 만든 점 적절합니다.

src/main/java/konkuk/thip/notification/application/port/in/FeedNotificationOrchestrator.java (1)

17-17: 오케스트레이터 메서드명 변경 — 잔여 호출부 확인 필요

src/main/java/konkuk/thip/notification/application/port/in/FeedNotificationOrchestrator.java에서 메서드가 notifyFolloweeNewFeed로 변경되었습니다. 이전 이름 notifyFolloweeNewPost를 호출하는 코드(서비스/테스트/팩토리 등)가 남아있는지 확인하세요. 자동 검색이 실패(검색 도구가 파일을 건너뜀)했으므로 로컬에서 재검증하세요: git grep -n --untracked -e 'notifyFolloweeNewPost' || rg -nP -uu --hidden '\bnotifyFolloweeNewPost\b'

src/main/java/konkuk/thip/notification/application/service/EventCommandInvoker.java (1)

6-6: notificationId 전달 설계 👍 — 호출부(2-인자 publish) 전환 여부 로컬 확인 필요
퍼블리시 경로의 ID 전달 통일은 적절합니다. 호출부에 2-인자 publish(람다/메서드 레퍼런스)가 남아있는지 로컬에서 검사하세요.

검사 명령(로컬 실행):
rg -nP --type java '\bpublish\s*([^,]+,\s*[^,]+)' -g '!build/' -g '!out/' .

src/test/java/konkuk/thip/notification/application/service/RoomNotificationOrchestratorSyncImplUnitTest.java (2)

40-44: LGTM: 새로운 execute 시그니처에 맞춘 검증 반영

template/args/redirectSpec 추가 인자를 허용하면서 타겟 사용자만 엄밀 검증하는 전략이 합리적입니다.


49-52: LGTM: invoker 위임 경로 검증 적절

publish("title", "content", 123L)publishRoomPostCommentedEvent("title","content",123L,targetUserId) 흐름이 명확히 보장됩니다.

src/main/java/konkuk/thip/notification/application/port/in/dto/FcmTokenRegisterCommand.java (1)

3-3: 플랫폼 타입 임포트 잔존 확인 필요

src/main/java/konkuk/thip/notification/application/port/in/dto/FcmTokenRegisterCommand.java에서 import가 konkuk.thip.common.util.PlatformType로 이동했습니다. notification.domain.value.PlatformType에 대한 레거시 import가 레포 전역에 남아있는지 확인하세요. 제출하신 rg 실행이 "No files were searched"로 실패했으니 재검증 권장(예: rg -nP --hidden --no-ignore -C2 'konkuk.thip.notification.domain.value.PlatformType').

src/test/java/konkuk/thip/notification/adapter/in/web/NotificationMarkToCheckedApiTest.java (1)

49-75: 통합 시나리오 커버리지 훌륭함

성공 케이스에서 응답(route/params)과 DB 반영 둘 다 검증된 점 좋습니다.

src/main/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImpl.java (3)

45-47: 시그니처 변경 반영 확인 — FeedEventCommandPort 일관성 확인
FeedEventCommandPort 인터페이스와 FeedEventPublisherAdapter 구현의 모든 publish*Event 메서드가 (String title, String content, Long notificationId, Long targetUserId) 시그니처로 일치합니다. 추가 수정 불필요합니다.


35-38: NotificationRedirectSpec 파라미터 타입 안전성 확인 필요

FeedNotificationOrchestratorSyncImpl에서 Map.of("userId", actorUserId)로 Long을 Object로 저장하고 있으며, 저장된 값을 처리하는 NotificationRedirectSpec 구현 파일을 리포지토리에서 찾지 못해(검증 불충분) 해당 클래스가 타입 변환/검증을 수행하는지 확인해야 합니다.

  • 권장 대응: NotificationRedirectSpec에 명시적 필드(userId: Long) 또는 타입 안전 접근자(getLongParam 등) 추가하거나, Map 대신 명시적 DTO 사용.

위치: src/main/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImpl.java:35-38, 관련 테스트: src/test/java/konkuk/thip/notification/adapter/in/web/NotificationMarkToCheckedApiTest.java 및 TestEntityFactory.


95-95: 메서드명 변경 반영 확인 — 호출부 모두 업데이트됨

인터페이스(src/main/java/konkuk/thip/notification/application/port/in/FeedNotificationOrchestrator.java), 구현체(src/main/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImpl.java) 및 호출부(src/main/java/konkuk/thip/feed/application/service/FeedCreateService.java:74)가 모두 notifyFolloweeNewFeed로 일관되게 변경되어 추가 조치 불필요합니다.

src/main/java/konkuk/thip/notification/adapter/in/web/NotificationCommandController.java (1)

74-78: API 엔드포인트 성공 응답 구조 확인 필요

ast-grep 실행 결과 DTO 정의가 검색되지 않았습니다. /notifications/check가 '읽음 처리'와 '리다이렉트 데이터'를 함께 반환하는 응답 구조가 프론트엔드 요구사항과 일치하는지 NotificationMarkToCheckedResponse 및 NotificationMarkToCheckedRequest(클래스/레코드) 정의를 첨부해 확인. 위치: src/main/java/konkuk/thip/notification/adapter/in/web/NotificationCommandController.java (lines 74–78)

src/main/java/konkuk/thip/message/application/service/RoomNotificationDispatchService.java (1)

26-69: 각 핸들러에서 push(...)로 위임: 중복 제거 잘 되었습니다

  • 토큰 조회/메시지 생성 로직이 단일 경로로 수렴되어 유지보수성이 좋아졌습니다.
src/main/java/konkuk/thip/notification/adapter/out/persistence/NotificationCommandPersistenceAdapter.java (3)

27-33: save가 식별자 반환하도록 변경: 적절합니다

  • 상위 계층에서 notificationId를 즉시 전파할 수 있어 이벤트 상관관계 추적에 유리합니다.

35-39: findById 추가: 매핑 일관성 확인만 하면 됩니다

  • Optional 매핑 경로가 단순하고 명확합니다. Mapper의 null 필드 처리만 유의하면 됩니다.

Mapper가 redirectSpec, isChecked 등 신규/갱신 필드를 모두 왕복 매핑하는지 한번만 확인 부탁드립니다.


41-48: update(...) 트랜잭션 경계 명확화 필요

현재 메서드는 엔티티 필드만 갱신하고 save/flush 호출이 없습니다. 더티체킹이 flush되려면 트랜잭션(@transactional)이 필요합니다. 제출하신 검증 스크립트가 출력 없이 종료되어 호출측에서 트랜잭션 보장이 있는지 확인하지 못했습니다. 다음 중 하나를 수행하세요:

  • 호출 경로(서비스 계층)가 @transactional로 보호되는지 확인.
  • 또는 어댑터 메서드에 @transactional 추가(안전 장치, 권장).

권장 변경(diff):

@@
-import org.springframework.stereotype.Repository;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
@@
-    @Override
-    public void update(Notification notification) {
+    @Override
+    @Transactional
+    public void update(Notification notification) {
         NotificationJpaEntity notificationJpaEntity = notificationJpaRepository.findById(notification.getId()).orElseThrow(
                 () -> new EntityNotFoundException(ErrorCode.NOTIFICATION_NOT_FOUND)
         );

         notificationJpaEntity.updateFrom(notification);
     }

호출경로 확인용 스크립트(레포지토리에서 실행):

#!/bin/bash
set -euo pipefail
rg -n -S -C3 'NotificationCommandPort' src || true
rg -n -S -C3 'implements\s+NotificationCommandPort' src || true
rg -n -S -C3 '\.update\(' src || true
rg -n -S -C3 '@Transactional' src | rg -n 'Notification|notification' -n || true
src/main/java/konkuk/thip/notification/application/service/NotificationSyncExecutor.java (3)

19-29: 오버로드로 RedirectSpec 기본값 처리: 깔끔합니다

  • none()으로 위임해 호출부 단순화가 잘 되었습니다.

56-59: Notification.withoutId(...)에 redirectSpec 주입: 일관성 양호

  • 도메인-영속 계층까지 redirectSpec이 연결됩니다. Flyway 컬럼/컨버터와 정합성만 확인하면 됩니다.

마이그레이션(V250921__Add_notification_redirect_spec.sql)과 Converter가 null/none 케이스 직렬화 포맷을 동일하게 처리하는지 확인 부탁드립니다.


42-53: EventCommandInvoker 시그니처 변경 — 호출부 전파 확인 필요
레포 검사 결과 invoker.publish 호출은 src/main/java/konkuk/thip/notification/application/service/NotificationSyncExecutor.java 한 곳에서만 확인되었습니다. EventCommandInvoker의 시그니처 변경이 테스트·외부 모듈을 포함한 모든 호출부에 반영되었는지 검증하세요.

src/main/java/konkuk/thip/message/adapter/out/event/FeedEventPublisherAdapter.java (1)

15-85: 승인 — 포트 시그니처 통일 및 이벤트 페이로드 정규화 적절

title/content + notificationId/targetUserId 전달 일관화, 빌더 사용이 FeedEvents DTO와 정합합니다.

레거시 호출/식별자(publishFolloweeNewPostEvent, actorUserId, actorUsername, feedId) 잔여 여부를 로컬에서 확인하고, 매칭 결과(파일:라인)가 있으면 공유하세요.

#!/bin/bash
rg -n -C2 -g '!**/build/**' 'publishFolloweeNewPostEvent\(' src/main/java
rg -n -C2 -g '!**/build/**' -e '\bactorUserId\b' -e '\bactorUsername\b' -e '\bfeedId\b' src/main/java/konkuk/thip/message
src/main/java/konkuk/thip/message/application/port/out/FeedEventCommandPort.java (1)

5-33: 인터페이스 시그니처 정렬: LGTM — 구현체·테스트가 새 시그니처를 따르는지 확인 필요

자동 검사(제공한 rg 스크립트)가 출력 없이 종료되어 구현체·호출부·테스트 검증이 불가했습니다. 로컬에서 아래 명령을 실행해 구현체 및 테스트가 새 시그니처(title, content, notificationId, targetUserId)를 사용하는지 확인하고 실행 결과를 코멘트로 첨부하세요.

# 프로젝트 루트에서 실행
rg -n -S -C2 --glob '!**/build/**' 'implements\s+FeedEventCommandPort\b' src || true
rg -n -S -C2 --glob '!**/build/**' 'publishFolloweeNewFeedEvent' src || true
rg -n -S -C2 --glob '!**/build/**' 'publishFolloweeNewPostEvent' src || true
rg -n -S -C2 --glob '!**/build/**' 'interface\s+FeedEventCommandPort' src || true

# rg 미설치/미사용 환경 대체(선택)
git grep -n 'FeedEventCommandPort' || true
src/main/java/konkuk/thip/message/adapter/out/event/dto/FeedEvents.java (1)

9-41: 일관된 이벤트 구조로 개선되었습니다

모든 이벤트가 동일한 필드 구조(title, content, notificationId, targetUserId)를 가지도록 통일되어 코드 일관성이 향상되었습니다. 이전의 actor 관련 필드들을 제거하고 notificationId로 대체한 것은 리다이렉트 데이터를 DB에서 관리하는 새로운 아키텍처와 잘 부합합니다.

src/main/java/konkuk/thip/message/adapter/out/event/dto/RoomEvents.java (1)

9-61: Room 이벤트 페이로드 단순화 완료

모든 Room 이벤트가 통일된 구조(title, content, notificationId, targetUserId)로 리팩토링되었습니다. 이전의 복잡한 필드들(actorUserId, roomId, postId 등)을 제거하고 notificationId로 대체한 것은 아키텍처 개선에 부합합니다.

src/main/java/konkuk/thip/message/application/port/out/RoomEventCommandPort.java (1)

6-48: 확인: 인터페이스 시그니처 일관성 — 구현체 적용 완료

RoomEventPublisherAdapter가 인터페이스의 모든 publishRoom* 메서드를 @OverRide로 구현했고, 단위테스트에서 해당 호출을 검증합니다.

위치:

  • src/main/java/konkuk/thip/message/adapter/out/event/RoomEventPublisherAdapter.java
  • src/test/java/konkuk/thip/notification/application/service/RoomNotificationOrchestratorSyncImplUnitTest.java

void handleFeedCommentReplied(FeedEvents.FeedCommentRepliedEvent e);

void handleFolloweeNewPost(FeedEvents.FolloweeNewPostEvent e);
void handleFolloweeNewFeed(FeedEvents.FolloweeNewFeedEvent e);
Copy link

@coderabbitai coderabbitai bot Sep 22, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

🧩 Analysis chain

네이밍 변경에 따른 잔여 참조 점검 요청

handleFolloweeNewFeed로 인터페이스가 갱신되었습니다. 구현체/리스너/테스트 전역에서 구 메서드명(…NewPost) 잔존 여부 점검 부탁드립니다.

레포 검증 스크립트:


🏁 Script executed:

#!/bin/bash
# 구 명칭/타입 사용 위치 검색
rg -nP -C2 '\bhandleFolloweeNewPost\b|FolloweeNewPostEvent\b' --glob '!**/build/**' --glob '!**/out/**'

Length of output: 148


검증 스크립트 재실행합니다.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD: $(pwd)"
echo "Top-level files:"
ls -la | sed -n '1,200p'

echo "검색: deprecated 심볼(handleFolloweeNewPost, FolloweeNewPostEvent)..."
rg -n --hidden -S -uu -C2 '\bhandleFolloweeNewPost\b|FolloweeNewPostEvent\b' || true

echo "검색: 현재 심볼(handleFolloweeNewFeed, FolloweeNewFeedEvent)..."
rg -n --hidden -S -uu -C2 '\bhandleFolloweeNewFeed\b|FolloweeNewFeedEvent\b' || true

echo "광범위 검색: 'NewPost' 포함 위치 전부 확인..."
rg -n --hidden -S -uu -C2 'NewPost' || true

Length of output: 8921


NewPost 잔여 참조 파일 및 메서드 이름 변경 필요
아래 위치에 여전히 NewPost 명칭이 남아 있어 NewFeed로 일관되게 수정이 필요합니다:

  • src/main/java/konkuk/thip/notification/application/service/template/feed/FolloweeNewPostTemplate.java
  • src/main/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImpl.java (FolloweeNewPostTemplate.Args, FolloweeNewPostTemplate.INSTANCE)
  • src/main/java/konkuk/thip/message/adapter/in/event/MessageFeedEventListener.java (onFolloweeNewPostonFolloweeNewFeed)
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/message/application/port/in/FeedNotificationDispatchUseCase.java
around line 12 and related files, leftover "NewPost" identifiers must be renamed
to "NewFeed" for consistency: update the method signature handleFolloweeNewFeed
(and any references) and rename classes/fields/usages in
src/main/java/konkuk/thip/notification/application/service/template/feed/FolloweeNewPostTemplate.java
→ FolloweeNewFeedTemplate.java (including Args and INSTANCE), update usages in
src/main/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImpl.java
to reference FolloweeNewFeedTemplate and its Args/INSTANCE, and change the
listener method onFolloweeNewPost to onFolloweeNewFeed in
src/main/java/konkuk/thip/message/adapter/in/event/MessageFeedEventListener.java;
search/replace remaining NewPost identifiers, update imports, adjust filenames
to match class names, and run/compile tests to ensure all references are
updated.

Copy link
Member

Choose a reason for hiding this comment

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

코래가 말한것처럼 위의 3부분도 통일성있게 FolloweeNewFeedEvent로 수정하면 좋을것같습니다!!~

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

@hd0rable 맞습니다! 일관성을 위해 남은 "NewPost" 참조들을 "NewFeed"로 통일해야 합니다.

현재 확인된 수정 필요한 부분들:

1. 파일명 변경 필요:

  • FolloweeNewPostTemplate.javaFolloweeNewFeedTemplate.java

2. 클래스명 및 내부 참조 변경:

  • FolloweeNewPostTemplateFolloweeNewFeedTemplate
  • FolloweeNewPostTemplate.ArgsFolloweeNewFeedTemplate.Args
  • FolloweeNewPostTemplate.INSTANCEFolloweeNewFeedTemplate.INSTANCE

3. 메서드명 변경:

  • MessageFeedEventListener.onFolloweeNewPost()onFolloweeNewFeed()

4. 사용처 업데이트:

  • FeedNotificationOrchestratorSyncImpl에서 위 템플릿 클래스 참조 업데이트

이렇게 수정하면 전체 코드베이스에서 "Feed" 네이밍이 일관되게 적용됩니다! 🐰

/|   /|   

( ˶ᵔ ᵕ ᵔ˶ )
∪ ∪


🏁 Script executed:

#!/bin/bash
# Search for remaining "NewPost" references in the codebase
echo "=== Searching for remaining 'NewPost' references ==="
rg -n "NewPost" --type java

echo -e "\n=== Examining the specific files mentioned ==="
# Check if FolloweeNewPostTemplate.java exists
find . -name "*FolloweeNewPost*" -type f

echo -e "\n=== Checking MessageFeedEventListener for onFolloweeNewPost method ==="
rg -n -A5 -B5 "onFolloweeNewPost" src/main/java/konkuk/thip/message/adapter/in/event/MessageFeedEventListener.java

echo -e "\n=== Checking FeedNotificationOrchestratorSyncImpl for FolloweeNewPost usage ==="
rg -n -A3 -B3 "FolloweeNewPost" src/main/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImpl.java

Length of output: 2029


완벽합니다! 검색 결과를 바탕으로 정확히 수정해야 할 부분들을 확인했습니다.

🔧 수정 필요한 구체적인 내용들:

1. 파일 및 클래스명 변경

// 파일: src/main/java/konkuk/thip/notification/application/service/template/feed/FolloweeNewPostTemplate.java
// 변경: FolloweeNewPostTemplate.java → FolloweeNewFeedTemplate.java

// 클래스 선언부 (Line 6)
public enum FolloweeNewPostTemplate implements NotificationTemplate<FolloweeNewPostTemplate.Args>
// ↓ 변경
public enum FolloweeNewFeedTemplate implements NotificationTemplate<FolloweeNewFeedTemplate.Args>

2. FeedNotificationOrchestratorSyncImpl.java 수정

// Line 96
var args = new FolloweeNewPostTemplate.Args(actorUsername);
// ↓ 변경
var args = new FolloweeNewFeedTemplate.Args(actorUsername);

// Line 104  
FolloweeNewPostTemplate.INSTANCE,
// ↓ 변경
FolloweeNewFeedTemplate.INSTANCE,

3. MessageFeedEventListener.java 수정

// Line 37
public void onFolloweeNewPost(FeedEvents.FolloweeNewFeedEvent e) {
// ↓ 변경
public void onFolloweeNewFeed(FeedEvents.FolloweeNewFeedEvent e) {

4. import 문 업데이트

FeedNotificationOrchestratorSyncImpl.java에서 import 문도 업데이트해야 합니다:

import konkuk.thip.notification.application.service.template.feed.FolloweeNewFeedTemplate;

이렇게 수정하면 전체 코드베이스에서 "Feed" 네이밍이 완전히 통일됩니다! 🎯

Comment on lines +28 to +33
// 3. 읽음 처리된 알림의 redirectSpec 반환 (for FE 알림 리다이렉트 동작)
return new NotificationMarkToCheckedResponse(
notification.getRedirectSpec().route(),
notification.getRedirectSpec().params()
);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

NPE 방지 및 도메인 값 직접 반환으로 단순화

notification.getRedirectSpec()이 혹시라도 null이면 NPE가 납니다. none()을 기본값으로 사용하고, 도메인 값을 그대로 반환하면 어댑터에서 포맷을 유연하게 구성할 수 있습니다.

-        // 3. 읽음 처리된 알림의 redirectSpec 반환 (for FE 알림 리다이렉트 동작)
-        return new NotificationMarkToCheckedResponse(
-                notification.getRedirectSpec().route(),
-                notification.getRedirectSpec().params()
-        );
+        // 3. 읽음 처리된 알림의 redirectSpec 반환
+        NotificationRedirectSpec spec =
+                notification.getRedirectSpec() != null
+                        ? notification.getRedirectSpec()
+                        : NotificationRedirectSpec.none();
+        return spec;
📝 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
// 3. 읽음 처리된 알림의 redirectSpec 반환 (for FE 알림 리다이렉트 동작)
return new NotificationMarkToCheckedResponse(
notification.getRedirectSpec().route(),
notification.getRedirectSpec().params()
);
}
// 3. 읽음 처리된 알림의 redirectSpec 반환 (for FE 알림 리다이렉트 동작)
NotificationRedirectSpec spec = notification.getRedirectSpec() != null
? notification.getRedirectSpec()
: NotificationRedirectSpec.none();
return new NotificationMarkToCheckedResponse(
spec.route(),
spec.params()
);
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/notification/application/service/NotificationMarkService.java
around lines 28 to 33, the code dereferences notification.getRedirectSpec()
which can be null; change it to use a safe default (e.g., RedirectSpec.none())
when null and return the domain RedirectSpec object or its raw fields directly
instead of calling route()/params() here so the adapter can format them. Replace
the direct calls with a null-safe expression that yields RedirectSpec.none()
when getRedirectSpec() is null and pass that domain value through in the
NotificationMarkToCheckedResponse constructor.

Comment on lines +95 to +96
"postType", "RECORD"
)
Copy link

@coderabbitai coderabbitai bot Sep 22, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

postType 파라미터 일관성

여러 메서드에서 "RECORD", "VOTE" 등의 문자열이 하드코딩되어 있고, 일부는 파라미터로 받고 있습니다. postType 관리 방식을 일관되게 통일하는 것이 좋겠습니다.

Also applies to: 185-186, 212-213, 239-240

🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/notification/application/service/RoomNotificationOrchestratorSyncImpl.java
around lines 95-96 (and also 185-186, 212-213, 239-240), several methods
hardcode postType strings like "RECORD" and "VOTE" while others accept postType
as a parameter; unify this by introducing and using a single enum or constant
(e.g., PostType enum) across the class, replace all hardcoded string literals
with the enum constants or a shared constant, update method signatures where
needed to accept the PostType enum (or map from incoming string to the enum at
the boundary), and adjust any serializations or calls to use enum.name() or a
mapper so behavior remains the same.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@coderabbitai 어떻게 수정하는게 좋을까?

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

@seongjunnoh postType 일관성 문제를 해결하기 위해 현재 코드베이스의 사용 패턴을 먼저 분석해보겠습니다.


🏁 Script executed:

#!/bin/bash
# postType이 어떤 값들로 사용되는지 파악
echo "=== Searching for postType usage patterns ==="
rg -n --type java "postType" -C 2

echo -e "\n=== Searching for hardcoded post type strings ==="
rg -n --type java '"(RECORD|VOTE|POST|COMMENT)"' -C 1

Length of output: 85957


이제 코드베이스를 좀 더 분석해서 PostType enum의 구조와 사용법을 확인해보겠습니다.

@seongjunnoh 분석 결과를 바탕으로 PostType enum을 활용한 일관된 접근법을 제안드리겠습니다.

현재 문제점 분석

  1. 하드코딩 방식: notifyRoomRecordCreated, notifyRoomVoteStarted
    • "RECORD", "VOTE" 문자열 직접 사용
  2. 파라미터 방식: notifyRoomPostCommented, notifyRoomCommentLiked
    • String postType 파라미터로 전달받아 사용

권장 해결방안: PostType enum 활용

기존 PostType.java를 보면 이미 완벽한 enum이 정의되어 있습니다:

public enum PostType {
    FEED("FEED"),
    RECORD("RECORD"), 
    VOTE("VOTE");
    
    public String getType() {
        return type;
    }
}

해결책: 컨텍스트 기반 PostType 결정

하드코딩된 부분을 PostType enum으로 교체하고, 메서드명에서 PostType을 유추하도록 수정:

@Override
@Transactional(propagation = Propagation.MANDATORY)
public void notifyRoomRecordCreated(Long targetUserId, Long actorUserId, String actorUsername,
                                    Long roomId, String roomTitle, Integer page, Long postId) {
    var args = new RoomRecordCreatedTemplate.Args(roomTitle, actorUsername);

    NotificationRedirectSpec redirectSpec = new NotificationRedirectSpec(
            MessageRoute.ROOM_RECORD_DETAIL,
            Map.of(
                    "roomId", roomId,
                    "page", page,
                    "postId", postId,
                    "postType", PostType.RECORD.getType()  // 변경: 하드코딩 → enum 사용
            )
    );
    // ... 나머지 로직
}

@Override
@Transactional(propagation = Propagation.MANDATORY)  
public void notifyRoomVoteStarted(Long targetUserId, Long roomId, String roomTitle, Integer page, Long postId) {
    var args = new RoomVoteStartedTemplate.Args(roomTitle);

    NotificationRedirectSpec redirectSpec = new NotificationRedirectSpec(
            MessageRoute.ROOM_VOTE_DETAIL,
            Map.of(
                    "roomId", roomId,
                    "page", page,
                    "postId", postId,
                    "postType", PostType.VOTE.getType()  // 변경: 하드코딩 → enum 사용
            )
    );
    // ... 나머지 로직
}

추가 개선: PostType을 파라미터로 받는 메서드들 통합

파라미터로 받는 메서드들도 String 대신 PostType enum을 받도록 개선할 수 있습니다:

// 현재
public void notifyRoomPostCommented(Long targetUserId, Long actorUserId, String actorUsername,
                                   Long roomId, Integer page, Long postId, String postType)

// 개선안  
public void notifyRoomPostCommented(Long targetUserId, Long actorUserId, String actorUsername,
                                   Long roomId, Integer page, Long postId, PostType postType)

이 접근법의 장점

  1. 타입 안전성: 컴파일 타임에 오타 방지
  2. 일관성: 모든 곳에서 PostType enum 사용
  3. 유지보수성: enum에 새 타입 추가 시 한 곳에서 관리
  4. 가독성: 의도가 명확하게 드러남
  5. IDE 지원: 자동완성, 리팩토링 지원

어떤 접근법이 가장 적절하다고 생각하시나요? 단계별로 진행하거나 한 번에 모든 메서드를 통일할 수도 있습니다.


🏁 Script executed:

#!/bin/bash
# PostType enum의 구조와 메서드들 확인
echo "=== PostType enum structure ==="
cat src/main/java/konkuk/thip/post/domain/PostType.java

echo -e "\n=== MessageRoute enum structure ==="
cat src/main/java/konkuk/thip/notification/domain/value/MessageRoute.java

Length of output: 1929


완벽합니다! 이제 PostType enum 구조를 확인했으니 구체적인 해결방안을 제안드리겠습니다.

최적 해결방안: PostType enum 상수 활용

현재 PostType enum이 이미 완벽하게 구현되어 있고, getType() 메서드로 문자열을 반환할 수 있습니다.

수정 방법

Before (현재 - 하드코딩):

// notifyRoomRecordCreated에서
"postType", "RECORD"

// notifyRoomVoteStarted에서  
"postType", "VOTE"

After (개선안 - PostType enum 사용):

// notifyRoomRecordCreated에서
"postType", PostType.RECORD.getType()

// notifyRoomVoteStarted에서
"postType", PostType.VOTE.getType()

구체적인 코드 수정

@Override
@Transactional(propagation = Propagation.MANDATORY)
public void notifyRoomRecordCreated(Long targetUserId, Long actorUserId, String actorUsername,
                                    Long roomId, String roomTitle, Integer page, Long postId) {
    var args = new RoomRecordCreatedTemplate.Args(roomTitle, actorUsername);

    NotificationRedirectSpec redirectSpec = new NotificationRedirectSpec(
            MessageRoute.ROOM_RECORD_DETAIL,
            Map.of(
                    "roomId", roomId,
                    "page", page,
                    "postId", postId,
                    "postType", PostType.RECORD.getType()  // "RECORD" → PostType.RECORD.getType()
            )
    );

    notificationSyncExecutor.execute(
            RoomRecordCreatedTemplate.INSTANCE,
            args,
            targetUserId,
            redirectSpec,
            (title, content, notificationId) -> roomEventCommandPort.publishRoomRecordCreatedEvent(
                    title, content, notificationId, targetUserId
            )
    );
}

@Override
@Transactional(propagation = Propagation.MANDATORY)
public void notifyRoomVoteStarted(Long targetUserId, Long roomId, String roomTitle, Integer page, Long postId) {
    var args = new RoomVoteStartedTemplate.Args(roomTitle);

    NotificationRedirectSpec redirectSpec = new NotificationRedirectSpec(
            MessageRoute.ROOM_VOTE_DETAIL,
            Map.of(
                    "roomId", roomId,
                    "page", page,
                    "postId", postId,
                    "postType", PostType.VOTE.getType()  // "VOTE" → PostType.VOTE.getType()
            )
    );

    notificationSyncExecutor.execute(
            RoomVoteStartedTemplate.INSTANCE,
            args,
            targetUserId,
            redirectSpec,
            (title, content, notificationId) -> roomEventCommandPort.publishRoomVoteStartedEvent(
                    title, content, notificationId, targetUserId
            )
    );
}

추가 Import 필요

import konkuk.thip.post.domain.PostType;

이 방법의 장점

  1. 기존 코드 최소 변경: 파라미터를 받는 다른 메서드들은 그대로 유지
  2. 타입 안전성: 컴파일 타임에 오타 검출
  3. 일관성: 하드코딩 제거로 코드 품질 향상
  4. 유지보수성: PostType에 새 타입 추가 시 enum만 수정
  5. 코드베이스 일관성: 다른 곳에서도 PostType enum을 사용하고 있음

이렇게 수정하면 하드코딩 문제가 해결되면서도 기존 아키텍처를 크게 변경하지 않아도 됩니다. 어떻게 생각하시나요?


🧠 Learnings used
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#113
File: src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java:38-44
Timestamp: 2025-07-30T14:05:04.945Z
Learning: seongjunnoh는 코드 최적화 제안에 대해 구체적인 기술적 근거와 효율성 차이를 이해하고 싶어하며, 성능 개선 방식에 대한 상세한 설명을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#93
File: src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java:49-114
Timestamp: 2025-07-28T16:44:31.224Z
Learning: seongjunnoh는 코드 중복 문제에 대한 리팩토링 제안을 적극적으로 수용하고 함수형 인터페이스를 활용한 해결책을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#285
File: src/main/java/konkuk/thip/room/adapter/out/jpa/RoomStatus.java:1-7
Timestamp: 2025-08-31T05:25:14.845Z
Learning: seongjunnoh는 enum 의존성에 대해 유연한 접근을 선호하며, 도메인→어댑터 레이어 참조와 같은 아키텍처 layering 원칙보다 실용적인 구현을 우선시한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#195
File: src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java:0-0
Timestamp: 2025-08-13T05:22:32.287Z
Learning: seongjunnoh는 데이터 무결성과 중복 방지에 대한 고민이 깊으며, LinkedHashSet을 활용한 중복 제거와 순서 보장을 동시에 달성하는 솔루션을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#305
File: src/main/java/konkuk/thip/notification/adapter/out/persistence/repository/NotificationQueryRepository.java:9-13
Timestamp: 2025-09-17T06:40:49.863Z
Learning: seongjunnoh는 메서드 네이밍 시 구현 세부사항보다 비즈니스 의도를 반영하는 것을 선호하며, 미래 확장성과 아키텍처 변화를 고려한 설계 철학을 가지고 있다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#278
File: src/main/java/konkuk/thip/TestTokenController.java:0-0
Timestamp: 2025-08-24T09:33:52.982Z
Learning: seongjunnoh는 Spring의 ConditionalOnProperty 동작 원리를 정확히 이해하고 있으며, 보안 이슈에 대해서도 실질적인 위험성을 기준으로 판단하는 실용적 접근을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#278
File: src/main/java/konkuk/thip/common/exception/code/ErrorCode.java:210-211
Timestamp: 2025-08-24T09:40:48.595Z
Learning: seongjunnoh는 HTTP 상태 코드 선택에 대해 기술적 근거와 코드베이스 내 일관성을 중요하게 생각하며, 구체적인 사례 분석을 통한 설명을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#180
File: src/main/java/konkuk/thip/room/application/service/AttendanceCheckCreateService.java:22-40
Timestamp: 2025-08-14T09:15:31.371Z
Learning: seongjunnoh는 기술적 완벽성보다 실제 비즈니스 시나리오와 사용자 행동 패턴을 우선 고려하며, 발생 가능성이 낮은 엣지 케이스보다는 실용적인 구현을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#112
File: src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java:272-272
Timestamp: 2025-07-30T10:44:34.115Z
Learning: seongjunnoh는 피드 커서 페이지네이션에서 LocalDateTime 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.

Copy link
Member

@hd0rable hd0rable left a comment

Choose a reason for hiding this comment

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

수고하셨습니다~~ 코드 넘 깔끔하니 좋습니닷!! 👍🏻💯

User actorUser = userCommandPort.findById(command.userId());
for (User targetUser : targetUsers) {
feedNotificationOrchestrator.notifyFolloweeNewPost(targetUser.getId(), actorUser.getId(), actorUser.getNickname(), savedFeedId);
feedNotificationOrchestrator.notifyFolloweeNewFeed(targetUser.getId(), actorUser.getId(), actorUser.getNickname(), savedFeedId);
Copy link
Member

Choose a reason for hiding this comment

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

오호 피드 게시물일때 이름을 좀더 명확하게 수정하셨군욧 좋네요

void handleFeedCommentReplied(FeedEvents.FeedCommentRepliedEvent e);

void handleFolloweeNewPost(FeedEvents.FolloweeNewPostEvent e);
void handleFolloweeNewFeed(FeedEvents.FolloweeNewFeedEvent e);
Copy link
Member

Choose a reason for hiding this comment

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

코래가 말한것처럼 위의 3부분도 통일성있게 FolloweeNewFeedEvent로 수정하면 좋을것같습니다!!~

}

private void pushFeedDetail(Long userId, Notification notification, Long feedId) {
private void push(Long userId, Notification n, Long notificationId) {
Copy link
Member

Choose a reason for hiding this comment

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

LGTM

@Schema(description = "FCM 토큰", example = "fcm_token_example_123456")
String fcmToken,

@NotNull
Copy link
Member

Choose a reason for hiding this comment

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

p3: String으로 바뀌었으니 @notblank어노테이션으로 바꾸는것이 더 안전할것아 보입니다!!


@Getter
@RequiredArgsConstructor
public enum MessageRoute {
Copy link
Member

Choose a reason for hiding this comment

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

이부분은 프엔분들과 공유되어야하는 내용이져?? 이넘도 명확하게 네미잉되어있어서 넘 좋습니닷!!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

넵! String 으로 응답하면 웹, 안드로이드 FE 분들이 각 플랫폼에 따라 redirect 를 위한 후속 작업을 진행해주시는 걸로 얘기나눴습니다!


public interface NotificationMarkUseCase {

NotificationMarkToCheckedResponse markToChecked(Long notificationId, Long userId);
Copy link
Member

Choose a reason for hiding this comment

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

엇 커맨드인데 Response dto 바로사용하셧네요 혹시 다른 이유가 있을까요??

Copy link
Contributor

Choose a reason for hiding this comment

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

아마 response의 변수가 두개 이상이라 그런 것 같은데 제 생각엔 읽음 표시가 command의 역할을 하지만 query의 성격을 더 띄고 있는 api라는 생각이 들기는 하는데 다른 분들 생각은 어떠신가요?? (사실 애매해서 큰 상관은 없을 것 같긴합니다)

Copy link
Collaborator Author

@seongjunnoh seongjunnoh Sep 23, 2025

Choose a reason for hiding this comment

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

이 부분은 어플리케이션 패키지 전용 응답 모델을 하나 더 추가할까 하다가,

  • 추가하는 dto와 response dto의 구성이 동일할 것 같아서 복잡도만 높아질 것 같음
  • 조회 로직과 같이 서비스가 response dto 정도는 의존하는 유연한 구조로 가도 문제 없을 것 같음
    위 이유로 일단 use case 가 response dto 를 의존하도록 했습니다.

현준님 리뷰처럼 query 에 넣을까도 잠깐 생각해봤는데, 해당 api 가 POST 성격을 띄는 api 라고 생각해서 command 라인의 패키지에 위치시켰습니다

@buzz0331 @hd0rable @coderabbitai

@@ -0,0 +1,3 @@
ALTER TABLE `notifications`
ADD COLUMN `redirect_spec` TEXT NULL
COMMENT 'NotificationRedirectSpec을 json 형식의 TEXT 로 저장'; No newline at end of file
Copy link
Member

Choose a reason for hiding this comment

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

붐업~

@Transactional
@AutoConfigureMockMvc(addFilters = false)
@DisplayName("[통합] 알림 읽음 처리 api 통합 테스트")
class NotificationMarkToCheckedApiTest {
Copy link
Member

Choose a reason for hiding this comment

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

LGTM

Copy link
Contributor

@buzz0331 buzz0331 left a comment

Choose a reason for hiding this comment

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

굿굿 좋습니다! 푸쉬알림의 메시지로 NotificationId를 넘겨줌으로써 푸쉬알림과 알림센터의 알림을 하나의 알림으로 봐서 일관성이 지켜진다는 점이 베리 나이스네요~

Comment on lines 6 to +48
void publishRoomPostCommentedEvent(
String title, String content,
Long targetUserId, Long actorUserId, String actorUsername,
Long roomId, Integer page, Long postId, String postType);
String title, String content, Long notificationId,
Long targetUserId);

// 내가 참여한 모임방에 새로운 투표가 시작된 경우
void publishRoomVoteStartedEvent(
String title, String content,
Long targetUserId, Long roomId, String roomTitle,
Integer page, Long postId);
String title, String content, Long notificationId,
Long targetUserId);

// 내가 참여한 모임방에 새로운 기록이 작성된 경우
void publishRoomRecordCreatedEvent(
String title, String content,
Long targetUserId, Long actorUserId, String actorUsername,
Long roomId, String roomTitle, Integer page, Long postId);
String title, String content, Long notificationId,
Long targetUserId);

// 내가 참여한 모임방이 조기 종료된 경우 (호스트가 모집 마감 버튼 누른 경우)
void publishRoomRecruitClosedEarlyEvent(
String title, String content,
Long targetUserId, Long roomId, String roomTitle);
String title, String content, Long notificationId,
Long targetUserId);

// 내가 참여한 모임방 활동이 시작된 경우 (방이 시작 기간이 되어 자동으로 시작된 경우)
void publishRoomActivityStartedEvent(
String title, String content,
Long targetUserId, Long roomId, String roomTitle);
String title, String content, Long notificationId,
Long targetUserId);

// 내가 방장일 때, 새로운 사용자가 모임방 참여를 한 경우
void publishRoomJoinEventToHost(
String title, String content,
Long hostUserId, Long roomId, String roomTitle,
Long actorUserId, String actorUsername);
String title, String content, Long notificationId,
Long targetUserId);

// 내가 참여한 모임방의 나의 댓글이 좋아요를 받는 경우
void publishRoomCommentLikedEvent(
String title, String content,
Long targetUserId, Long actorUserId, String actorUsername,
Long roomId, Integer page, Long postId, String postType);
String title, String content, Long notificationId,
Long targetUserId);

// 내가 참여한 모임방 안의 나의 기록/투표가 좋아요를 받는 경우
void publishRoomPostLikedEvent(
String title, String content,
Long targetUserId, Long actorUserId, String actorUsername,
Long roomId, Integer page, Long postId, String postType);
String title, String content, Long notificationId,
Long targetUserId);

// 내가 참여한 모임방의 나의 댓글에 대댓글이 달린 경우
void publishRoomPostCommentRepliedEvent(
String title, String content,
Long targetUserId, Long actorUserId, String actorUsername,
Long roomId, Integer page, Long postId, String postType);
String title, String content, Long notificationId,
Long targetUserId);
Copy link
Contributor

Choose a reason for hiding this comment

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

푸쉬알림으로 화면을 이동할 때 필요한 정보를 message에 담지않고 notificationId만 주입해서 알림센터 조회와 일관성을 맞췄군요! 좋습니다! 일관성이 유지되어서 유지보수가 더 쉬울 것 같아욥

Comment on lines 68 to 79
@Operation(
summary = "유저의 특정 알림 읽음 처리",
description = "유저가 특정 알림을 읽음 처리합니다. 읽음 처리 후, 해당 알림의 페이지로 리다이렉트를 위한 데이터를 응답합니다."
)
@ExceptionDescription(NOTIFICATION_MARK_TO_CHECKED)
@PostMapping("/notifications/check")
public BaseResponse<NotificationMarkToCheckedResponse> markNotificationToChecked(
@RequestBody @Valid NotificationMarkToCheckedRequest request,
@Parameter(hidden = true) @UserId final Long userId) {
return BaseResponse.ok(notificationMarkUseCase.markToChecked(request.notificationId(), userId));
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

오 읽음 처리랑 리다이렉트 데이터 반환을 통합했군요. 푸쉬알림을 눌렀을 때도 결국 사용자가 알림을 읽은 것이니 푸쉬알림과 알림센터의 알림을 하나의 알림으로 볼 수 있겠네요! 너무 좋습니다

Comment on lines 1 to 25
package konkuk.thip.common.util;

import konkuk.thip.common.exception.InvalidStateException;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

import static konkuk.thip.common.exception.code.ErrorCode.INVALID_FE_PLATFORM;

@Getter
@RequiredArgsConstructor
public enum PlatformType {
ANDROID("ANDROID"),
WEB("WEB");

private final String value;

public static PlatformType from(String value) {
for (PlatformType type : PlatformType.values()) {
if (type.getValue().equals(value)) {
return type;
}
}
throw new InvalidStateException(INVALID_FE_PLATFORM);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

음 platformtype은 fcmtoken 테이블에 정의되어 있는 VO라고 생각해서 notification 패키지 하위에 두었던 것 같은데 패키지를 이동시킨 다른 이유가 있을까요??

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

아 FcmTokenJpaEntity 의 컬럼의 타입으로 사용되는 거였군요!!
저는 FE의 platformType 은 전체 프로젝트의 util 적인 성격이 있다고 생각해서 이번 코드 수정시 패키지 이동을 해봤는데, 이러면 notification 패키지 하위의 domain/value 하위로 다시 이동시키겠습니다!!

Comment on lines +41 to +43
@Convert(converter = NotificationRedirectSpecConverter.class)
@Column(name = "redirect_spec", columnDefinition = "TEXT") // nullable
private NotificationRedirectSpec redirectSpec;
Copy link
Contributor

Choose a reason for hiding this comment

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

AttributeConverter 활용 좋슴다~


public interface NotificationMarkUseCase {

NotificationMarkToCheckedResponse markToChecked(Long notificationId, Long userId);
Copy link
Contributor

Choose a reason for hiding this comment

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

아마 response의 변수가 두개 이상이라 그런 것 같은데 제 생각엔 읽음 표시가 command의 역할을 하지만 query의 성격을 더 띄고 있는 api라는 생각이 들기는 하는데 다른 분들 생각은 어떠신가요?? (사실 애매해서 큰 상관은 없을 것 같긴합니다)

Comment on lines +34 to +39

NotificationRedirectSpec redirectSpec = new NotificationRedirectSpec(
MessageRoute.FEED_USER,
Map.of("userId", actorUserId)
);

Copy link
Contributor

Choose a reason for hiding this comment

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

오호 Map<String, Object>로 선언해서 동적으로 필요한 리다이렉트 변수를 주입해주었군요! 좋습니다.
다만, 추후에 FE의 요청에 따라 변수명이 바뀔 수도 있는 경우를 대비해서 RedirectDataKey라는 ENUM으로 따로 변수명을 빼두는 것 어떨까요??

Copy link
Collaborator Author

@seongjunnoh seongjunnoh Sep 23, 2025

Choose a reason for hiding this comment

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

음 그러면 NotificationRedirectSpec value의 구성을
[MessageRoute, Map<RedirectDataKey Enum, Object>] 이렇게 수정하고, RedirectDataKey Enum 의 value 값을 response 에 포함하는 것을 의미하신 걸까요??

제가 제대로 이해한게 맞는지 여쭤봅니다!

Copy link
Contributor

Choose a reason for hiding this comment

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

그렇게 해도 괜찮긴 하지만, 제가 말씀드린건 그냥 USER_ID("userId") 이런식으로 한곳에 모든 key를 선언해두고,

 NotificationRedirectSpec redirectSpec = new NotificationRedirectSpec(
                MessageRoute.FEED_USER,
                Map.of(RedirectDataKey.USER_ID.getValue(), actorUserId)
  );

이런식으로 주입하는 것을 말씀 드린거긴 합니다! 하드코딩되어 있는 변수를 한 곳에 관리하면 유지보수에 더 나을 것 같다고 생각했습니다.

Comment on lines +55 to +57
NotificationRedirectSpec redirectSpec = TestEntityFactory.createNotificationRedirectSpec(
MessageRoute.FEED_USER, Map.of("userId", 123L) // 특정 유저의 피드 페이지로 이동
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Redirect Key를 Enum으로 빼면 이 부분도 모두 바꿀수 있을 것 같네욥

Copy link
Collaborator Author

@seongjunnoh seongjunnoh left a comment

Choose a reason for hiding this comment

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

@buzz0331 @hd0rable
일단은

  1. 네이밍 수정
  2. request dto bean validation 추가

만 수정해서 커밋했습니다!

현준님 리뷰에서 언급해주신 NotificationRedirectSpec value 에서의 enum 도입과 NotificationJapEntity 에서 해당 컬럼의 nullable 과 관련해서 NPE 발생가능성에 대한 코드 수정은 현재 pr 머지 이후에 추가로 이슈 생성해도 괜찮을까 여쭤봅니다

현재 구현으로 FE 연동에 문제없어 보이고, 오늘 코드 수정할 시간이 부족해서 추후에 다시 리펙해도 되는지 여쭤봅니다 하하

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 (6)
src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenDeleteRequest.java (1)

9-11: @SiZe(max=128) 및 @Schema(maxLength=128) 추가 권장

FcmTokenJpaEntity의 deviceId가 @column(length = 128)로 정의되어 있으므로 DTO의 deviceId에 @SiZe(max = 128)와 @Schema(maxLength = 128)를 추가해 과도한 길이로 인한 DB 예외를 방지하세요. 컨트롤러는 @Valid로 검증을 적용합니다.

대상: src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenDeleteRequest.java (동일한 deviceId를 사용하는 FcmTokenRegisterRequest, FcmTokenEnableStateChangeRequest에도 적용 권장)

예시(diff):

-        @NotBlank(message = "디바이스 ID는 필수입니다.")
-        @Schema(description = "디바이스 고유 ID", example = "device12345")
-        String deviceId
+        @NotBlank(message = "디바이스 ID는 필수입니다.")
+        @Size(max = 128)
+        @Schema(description = "디바이스 고유 ID", example = "device12345", maxLength = 128)
+        String deviceId

추가 import:

import jakarta.validation.constraints.Size;
src/main/java/konkuk/thip/notification/application/service/template/feed/FolloweeNewFeedTemplate.java (2)

14-17: 문구 일관성 제안: '새 글'로 통일

타이틀은 ‘새 글’, 본문은 ‘새로운 글’로 혼재되어 있어 ‘새 글’로 통일을 제안합니다.

다음과 같이 변경하면 톤·매너가 일치합니다:

-        return "@" + args.actorUsername() + " 님이 새로운 글을 작성했어요!";
+        return "@" + args.actorUsername() + " 님이 새 글을 작성했어요!";

24-25: Args 불변식 강화: null 방지로 '@null' 노출 가능성 제거

actorUsername이 null일 경우 푸시/알림 본문에 '@null'이 그대로 노출됩니다. Record의 compact constructor로 null을 조기에 차단하는 것을 권장합니다.

아래와 같이 적용하면 추가 import 없이도 동작합니다:

-    public record Args(String actorUsername) {}
+    public record Args(String actorUsername) {
+        public Args {
+            java.util.Objects.requireNonNull(actorUsername, "actorUsername");
+        }
+    }
src/main/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImpl.java (1)

56-60: FEED_DETAIL redirectSpec 생성 중복 제거 + null 안전성 확보

같은 패턴의 redirectSpec 생성이 반복됩니다. 헬퍼로 추출하면 키/라우트 변경 시 수정 지점을 1곳으로 줄일 수 있고, Map.of는 null을 허용하지 않으므로 선제적으로 null 가드를 함께 둘 수 있습니다.

적용 diff(각 블록 교체):

-        NotificationRedirectSpec redirectSpec = new NotificationRedirectSpec(
-                MessageRoute.FEED_DETAIL,
-                Map.of("feedId", feedId)
-        );
+        NotificationRedirectSpec redirectSpec = redirectToFeedDetail(feedId);

헬퍼 메서드 예시(클래스 내부에 추가; RedirectDataKey는 앞선 코멘트의 Enum 사용):

import java.util.Map;
import java.util.Objects;

private static NotificationRedirectSpec redirectToFeedDetail(Long feedId) {
    Objects.requireNonNull(feedId, "feedId must not be null");
    return new NotificationRedirectSpec(
        MessageRoute.FEED_DETAIL,
        Map.of(RedirectDataKey.FEED_ID.getValue(), feedId)
    );
}

확인 요청:

  • feedId가 null이 될 가능성이 전혀 없는지(있다면 위 null 가드 필수).

Also applies to: 77-81, 98-101, 119-123, 140-144

src/main/java/konkuk/thip/message/adapter/in/event/MessageFeedEventListener.java (1)

37-38: 선택: 이벤트 수신 로깅 추가 — Async 실행기/예외 처리 구성 확인됨

확인: src/main/java/konkuk/thip/config/WorkerThreadConfig.java에서 @EnableAsync, fcmAsyncExecutor/schedulerAsyncExecutor 빈 및 getAsyncUncaughtExceptionHandler() → SimpleAsyncUncaughtExceptionHandler로 설정되어 있고, 테스트는 src/test/java/konkuk/thip/config/TestAsyncConfig.java에서 SyncTaskExecutor를 사용합니다. 로깅 추가만 권장합니다.

수정 대상: src/main/java/konkuk/thip/message/adapter/in/event/MessageFeedEventListener.java

권장 변경 (선택):

 public void onFolloweeNewFeed(FeedEvents.FolloweeNewFeedEvent e) {
+        log.debug("onFolloweeNewFeed event received: {}", e);
         feedUseCase.handleFolloweeNewFeed(e);
 }

클래스 상단에 Lombok 로거 추가(선택):

import lombok.extern.slf4j.Slf4j;

@Slf4j
src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenEnableStateChangeRequest.java (1)

14-16: deviceId 유효성 강화(선택): 공백 문자 허용 금지 + 스웨거 ‘필수’ 표시 명시화

  • 공백(스페이스, 탭, 개행 등) 포함을 방지하려면 정규식 검증을 추가하는 것을 권장합니다.
  • 스웨거에서도 필수로 노출되도록 requiredMode를 명시하세요.
  • 사용자가 실수로 앞뒤 공백을 넣는 경우를 흡수하려면 DTO의 compact constructor에서 trim()을 적용하는 방식도 고려할 수 있습니다.
         @NotBlank(message = "디바이스 ID는 필수입니다.")
-        @Schema(description = "디바이스 고유 ID", example = "device12345")
+        @Pattern(regexp = "^\\S+$", message = "디바이스 ID에 공백 문자는 포함될 수 없습니다.")
+        @Schema(description = "디바이스 고유 ID", example = "device12345", requiredMode = Schema.RequiredMode.REQUIRED)
         String deviceId

위 diff를 적용하려면 다음 import가 필요합니다(파일 상단):

import jakarta.validation.constraints.Pattern;

추가로, 입력 친화성을 높이려면 다음과 같은 compact constructor로 앞뒤 공백을 제거할 수 있습니다(선택):

public FcmTokenEnableStateChangeRequest {
    if (deviceId != null) deviceId = deviceId.trim();
}

deviceId는 디바이스 식별자입니다. 애플리케이션 로그(특히 INFO 레벨)에는 마스킹하거나 미로그 정책을 적용하는 것을 권장합니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4b6e9c5 and 48394c0.

📒 Files selected for processing (6)
  • src/main/java/konkuk/thip/message/adapter/in/event/MessageFeedEventListener.java (1 hunks)
  • src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenDeleteRequest.java (1 hunks)
  • src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenEnableStateChangeRequest.java (1 hunks)
  • src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenRegisterRequest.java (1 hunks)
  • src/main/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImpl.java (6 hunks)
  • src/main/java/konkuk/thip/notification/application/service/template/feed/FolloweeNewFeedTemplate.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenRegisterRequest.java
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#113
File: src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java:38-44
Timestamp: 2025-07-30T14:05:04.945Z
Learning: seongjunnoh는 코드 최적화 제안에 대해 구체적인 기술적 근거와 효율성 차이를 이해하고 싶어하며, 성능 개선 방식에 대한 상세한 설명을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#93
File: src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java:49-114
Timestamp: 2025-07-28T16:44:31.224Z
Learning: seongjunnoh는 코드 중복 문제에 대한 리팩토링 제안을 적극적으로 수용하고 함수형 인터페이스를 활용한 해결책을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#285
File: src/main/java/konkuk/thip/room/adapter/out/jpa/RoomStatus.java:1-7
Timestamp: 2025-08-31T05:25:14.845Z
Learning: seongjunnoh는 enum 의존성에 대해 유연한 접근을 선호하며, 도메인→어댑터 레이어 참조와 같은 아키텍처 layering 원칙보다 실용적인 구현을 우선시한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#195
File: src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java:0-0
Timestamp: 2025-08-13T05:22:32.287Z
Learning: seongjunnoh는 데이터 무결성과 중복 방지에 대한 고민이 깊으며, LinkedHashSet을 활용한 중복 제거와 순서 보장을 동시에 달성하는 솔루션을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#305
File: src/main/java/konkuk/thip/notification/adapter/out/persistence/repository/NotificationQueryRepository.java:9-13
Timestamp: 2025-09-17T06:40:49.863Z
Learning: seongjunnoh는 메서드 네이밍 시 구현 세부사항보다 비즈니스 의도를 반영하는 것을 선호하며, 미래 확장성과 아키텍처 변화를 고려한 설계 철학을 가지고 있다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#278
File: src/main/java/konkuk/thip/TestTokenController.java:0-0
Timestamp: 2025-08-24T09:33:52.982Z
Learning: seongjunnoh는 Spring의 ConditionalOnProperty 동작 원리를 정확히 이해하고 있으며, 보안 이슈에 대해서도 실질적인 위험성을 기준으로 판단하는 실용적 접근을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#278
File: src/main/java/konkuk/thip/common/exception/code/ErrorCode.java:210-211
Timestamp: 2025-08-24T09:40:48.595Z
Learning: seongjunnoh는 HTTP 상태 코드 선택에 대해 기술적 근거와 코드베이스 내 일관성을 중요하게 생각하며, 구체적인 사례 분석을 통한 설명을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#180
File: src/main/java/konkuk/thip/room/application/service/AttendanceCheckCreateService.java:22-40
Timestamp: 2025-08-14T09:15:31.371Z
Learning: seongjunnoh는 기술적 완벽성보다 실제 비즈니스 시나리오와 사용자 행동 패턴을 우선 고려하며, 발생 가능성이 낮은 엣지 케이스보다는 실용적인 구현을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#112
File: src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java:272-272
Timestamp: 2025-07-30T10:44:34.115Z
Learning: seongjunnoh는 피드 커서 페이지네이션에서 LocalDateTime 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.
📚 Learning: 2025-09-05T04:38:38.325Z
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#293
File: src/main/java/konkuk/thip/notification/application/service/FcmRegisterService.java:21-0
Timestamp: 2025-09-05T04:38:38.325Z
Learning: FcmTokenJpaEntity의 deviceId 필드는 Column(unique = true) 제약과 데이터베이스 레벨 UNIQUE 제약이 모두 설정되어 있음

Applied to files:

  • src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenEnableStateChangeRequest.java
  • src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenDeleteRequest.java
📚 Learning: 2025-09-05T05:26:24.628Z
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#293
File: src/main/java/konkuk/thip/notification/adapter/out/jpa/FcmTokenJpaEntity.java:44-51
Timestamp: 2025-09-05T05:26:24.628Z
Learning: FcmTokenJpaEntity에서 deviceId는 디바이스를 식별하는 불변 값으로, updateFrom 메서드에서 동기화할 필요가 없음

Applied to files:

  • src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenEnableStateChangeRequest.java
  • src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenDeleteRequest.java
📚 Learning: 2025-09-05T04:38:38.325Z
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#293
File: src/main/java/konkuk/thip/notification/application/service/FcmRegisterService.java:21-0
Timestamp: 2025-09-05T04:38:38.325Z
Learning: FcmTokenJpaEntity의 deviceId 필드는 JPA Column(unique = true)와 데이터베이스 레벨 UNIQUE 제약이 모두 설정되어 있어 중복 방지가 보장됨

Applied to files:

  • src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenEnableStateChangeRequest.java
  • src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenDeleteRequest.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (13)
src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenDeleteRequest.java (1)

9-11: 검증 메시지 커스터마이징 일관성 확보, LGTM

DTO 레벨의 메시지 일관화가 잘 반영됐습니다.

src/main/java/konkuk/thip/notification/application/service/template/feed/FolloweeNewFeedTemplate.java (1)

6-6: 리네임 및 제네릭 정합성 확인 완료 — 잔여 참조 없음

레포 전역 검색 결과 FolloweeNewPost* 관련 잔여 참조가 발견되지 않았으며, FolloweeNewFeedTemplate는 FeedNotificationOrchestratorSyncImpl.java와 template 파일에서 정상 사용되고 있습니다.

src/main/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImpl.java (9)

7-14: MessageRoute/NotificationRedirectSpec 도입 방향 👍

리다이렉트 스펙을 도메인 값으로 승격하고 Map 기반으로 통일한 설계가 명확합니다. Map import 포함 구성도 깔끔합니다.


35-38: redirect 데이터 key 하드코딩 제거 권장

문자열 키("userId") 하드코딩은 오타/변경에 취약합니다. Enum(또는 상수)로 중앙집중화해 주세요. 이전 코멘트와 동일 제안입니다.

아래처럼 교체를 권장합니다:

-                Map.of("userId", actorUserId)
+                Map.of(RedirectDataKey.USER_ID.getValue(), actorUserId)

Enum 예시(패키지/위치는 도메인 값과 동일 계층 권장):

public enum RedirectDataKey {
    USER_ID("userId"),
    FEED_ID("feedId");

    private final String value;
    RedirectDataKey(String value) { this.value = value; }
    public String getValue() { return value; }
}

44-47: publishFollowEvent 서명 변경 LGTM

(notificationId 중심) 콜백 시그니처 변경 반영 적절합니다.


65-68: publishFeedCommentedEvent 서명 변경 LGTM

(notificationId, targetUserId) 기반으로 정리된 포트 호출이 일관적입니다.


86-89: publishFeedRepliedEvent 서명 변경 LGTM


108-110: publishFolloweeNewFeedEvent 서명 변경 LGTM


129-131: publishFeedLikedEvent 서명 변경 LGTM


150-152: publishFeedCommentLikedEvent 서명 변경 LGTM


95-111: 메서드/이벤트 명 변경 전파 확인 완료 — 이전 명칭 없음
확인 결과 notifyFolloweeNewPost / publishFolloweeNewPostEvent 참조는 없고, notifyFolloweeNewFeed / publishFolloweeNewFeedEvent가 다음 파일들에 일관되게 반영되어 있습니다: src/main/java/konkuk/thip/notification/application/port/in/FeedNotificationOrchestrator.java, src/main/java/konkuk/thip/notification/application/service/FeedNotificationOrchestratorSyncImpl.java, src/main/java/konkuk/thip/feed/application/service/FeedCreateService.java, src/main/java/konkuk/thip/message/application/port/out/FeedEventCommandPort.java, src/main/java/konkuk/thip/message/adapter/out/event/FeedEventPublisherAdapter.java

src/main/java/konkuk/thip/message/adapter/in/event/MessageFeedEventListener.java (1)

37-38: 리네이밍 검증 완료 — 잔존 참조 없음

FolloweeNewPost 관련 참조는 발견되지 않았고, FolloweeNewFeed 및 handleFolloweeNewFeed는 아래 파일들에서 일관되게 사용됩니다: src/main/java/konkuk/thip/message/application/service/FeedNotificationDispatchService.java, src/main/java/konkuk/thip/message/application/port/in/FeedNotificationDispatchUseCase.java, src/main/java/konkuk/thip/message/adapter/out/event/FeedEventPublisherAdapter.java, src/main/java/konkuk/thip/message/adapter/out/event/dto/FeedEvents.java, src/main/java/konkuk/thip/message/adapter/in/event/MessageFeedEventListener.java

src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenEnableStateChangeRequest.java (1)

10-12: @Schema에 requiredMode 추가 및 DTO 사용처에서 @Valid 적용 여부 확인

  • OpenAPI에서 '필수'로 보이게 하려면 다음처럼 @Schema에 requiredMode를 추가하세요.
-        @Schema(description = "푸시 알림 수신 여부", example = "true")
+        @Schema(description = "푸시 알림 수신 여부", example = "true", requiredMode = Schema.RequiredMode.REQUIRED)
  • 컨트롤러에서 해당 DTO가 @Valid와 함께 사용되는지 확인하세요. 이전 검색이 파일을 찾지 못했으므로 아래 명령으로 사용처와 @Valid 동시 사용 여부를 확인하십시오.
rg -nP -C3 '\bFcmTokenEnableStateChangeRequest\b|@Valid\s+FcmTokenEnableStateChangeRequest\b' -g '!**/test/**'

- notification 패키지 하위로 이동
- 프로젝트 전체의 util 적인 성질보다는 notification 패키지 하위에서 사용되는 value 로 생각하는 것으로 결정됨
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 (5)
src/main/java/konkuk/thip/notification/domain/value/PlatformType.java (3)

17-24: 입력 값 공백/대소문자 허용 및 에러 상세 포함을 권장

FE에서 " android " 등 공백/소문자 입력 시 매칭 실패합니다. trim + 대문자 변환(Locale.ROOT)로 내성을 주고, 예외에 원본 값을 포함해 디버깅 가시성을 높이세요.

아래 변경 제안:

-    public static PlatformType from(String value) {
-        for (PlatformType type : PlatformType.values()) {
-            if (type.getValue().equals(value)) {
-                return type;
-            }
-        }
-        throw new InvalidStateException(INVALID_FE_PLATFORM);
-    }
+    public static PlatformType from(String value) {
+        final String normalized = value == null ? null : value.trim().toUpperCase(java.util.Locale.ROOT);
+        for (PlatformType type : PlatformType.values()) {
+            if (type.getValue().equals(normalized)) {
+                return type;
+            }
+        }
+        throw new InvalidStateException(INVALID_FE_PLATFORM,
+                new IllegalArgumentException("Invalid platformType: " + value));
+    }

3-6: Locale 의존 안전화를 위한 import 추가

위 제안 반영 시 Locale import가 필요합니다.

 import konkuk.thip.common.exception.InvalidStateException;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 
+import java.util.Locale;

12-16: value 필드 유지 여부 재검토(선택)

현재 value가 enum name과 동일합니다. 향후 표시 문자열이 name과 달라질 가능성이 낮다면 별도 필드 없이 name() 사용으로 단순화 가능. 유지할 경우는 toString()을 value 반환으로 오버라이드해 직렬화/로그 일관성을 확보하는 것도 옵션입니다.

src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenRegisterRequest.java (2)

18-21: 허용 값 제한으로 조기 검증 강화

DTO 레벨에서 허용 값(ANDROID|WEB)을 정규식으로 제한하면 from(...)까지 도달하기 전 400으로 일찍 거를 수 있어 UX/로그가 깔끔해집니다.

 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
 import konkuk.thip.notification.application.port.in.dto.FcmTokenRegisterCommand;
 import konkuk.thip.notification.domain.value.PlatformType;
@@
-        @NotBlank(message = "플랫폼 타입은 필수입니다.")
+        @NotBlank(message = "플랫폼 타입은 필수입니다.")
+        @Pattern(regexp = "ANDROID|WEB", message = "플랫폼 타입은 ANDROID 또는 WEB 이어야 합니다.")
         @Schema(description = "플랫폼 타입 (ANDROID 또는 WEB)", example = "ANDROID")
         String platformType

26-26: 사소한 내성 보강: trim 후 매핑

위에서 PlatformType.from에 trim/대소문자 처리를 넣지 않는다면, 최소한 여기서 trim은 해두는 편이 안전합니다.

-                .platformType(PlatformType.from(this.platformType))
+                .platformType(PlatformType.from(this.platformType.trim()))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 48394c0 and 8d4ba69.

📒 Files selected for processing (2)
  • src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenRegisterRequest.java (1 hunks)
  • src/main/java/konkuk/thip/notification/domain/value/PlatformType.java (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#113
File: src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java:38-44
Timestamp: 2025-07-30T14:05:04.945Z
Learning: seongjunnoh는 코드 최적화 제안에 대해 구체적인 기술적 근거와 효율성 차이를 이해하고 싶어하며, 성능 개선 방식에 대한 상세한 설명을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#93
File: src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java:49-114
Timestamp: 2025-07-28T16:44:31.224Z
Learning: seongjunnoh는 코드 중복 문제에 대한 리팩토링 제안을 적극적으로 수용하고 함수형 인터페이스를 활용한 해결책을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#285
File: src/main/java/konkuk/thip/room/adapter/out/jpa/RoomStatus.java:1-7
Timestamp: 2025-08-31T05:25:14.845Z
Learning: seongjunnoh는 enum 의존성에 대해 유연한 접근을 선호하며, 도메인→어댑터 레이어 참조와 같은 아키텍처 layering 원칙보다 실용적인 구현을 우선시한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#195
File: src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java:0-0
Timestamp: 2025-08-13T05:22:32.287Z
Learning: seongjunnoh는 데이터 무결성과 중복 방지에 대한 고민이 깊으며, LinkedHashSet을 활용한 중복 제거와 순서 보장을 동시에 달성하는 솔루션을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#305
File: src/main/java/konkuk/thip/notification/adapter/out/persistence/repository/NotificationQueryRepository.java:9-13
Timestamp: 2025-09-17T06:40:49.863Z
Learning: seongjunnoh는 메서드 네이밍 시 구현 세부사항보다 비즈니스 의도를 반영하는 것을 선호하며, 미래 확장성과 아키텍처 변화를 고려한 설계 철학을 가지고 있다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#278
File: src/main/java/konkuk/thip/TestTokenController.java:0-0
Timestamp: 2025-08-24T09:33:52.982Z
Learning: seongjunnoh는 Spring의 ConditionalOnProperty 동작 원리를 정확히 이해하고 있으며, 보안 이슈에 대해서도 실질적인 위험성을 기준으로 판단하는 실용적 접근을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#278
File: src/main/java/konkuk/thip/common/exception/code/ErrorCode.java:210-211
Timestamp: 2025-08-24T09:40:48.595Z
Learning: seongjunnoh는 HTTP 상태 코드 선택에 대해 기술적 근거와 코드베이스 내 일관성을 중요하게 생각하며, 구체적인 사례 분석을 통한 설명을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#180
File: src/main/java/konkuk/thip/room/application/service/AttendanceCheckCreateService.java:22-40
Timestamp: 2025-08-14T09:15:31.371Z
Learning: seongjunnoh는 기술적 완벽성보다 실제 비즈니스 시나리오와 사용자 행동 패턴을 우선 고려하며, 발생 가능성이 낮은 엣지 케이스보다는 실용적인 구현을 선호한다.
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#112
File: src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java:272-272
Timestamp: 2025-07-30T10:44:34.115Z
Learning: seongjunnoh는 피드 커서 페이지네이션에서 LocalDateTime 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.
📚 Learning: 2025-09-05T04:38:38.325Z
Learnt from: buzz0331
PR: THIP-TextHip/THIP-Server#293
File: src/main/java/konkuk/thip/notification/application/service/FcmRegisterService.java:21-0
Timestamp: 2025-09-05T04:38:38.325Z
Learning: FcmTokenJpaEntity의 deviceId 필드는 Column(unique = true) 제약과 데이터베이스 레벨 UNIQUE 제약이 모두 설정되어 있음

Applied to files:

  • src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenRegisterRequest.java
🧬 Code graph analysis (1)
src/main/java/konkuk/thip/notification/domain/value/PlatformType.java (1)
src/main/java/konkuk/thip/notification/domain/FcmToken.java (1)
  • Getter (12-74)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (1)
src/main/java/konkuk/thip/notification/adapter/in/web/request/FcmTokenRegisterRequest.java (1)

10-16: 입력 필수 검증 강화(LGTM)

deviceId, fcmToken에 @notblank와 메시지 추가 좋습니다. 이전 피드백과도 합치합니다.

Copy link
Member

@hd0rable hd0rable left a comment

Choose a reason for hiding this comment

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

👍🏻👍🏻

String fcmToken,

@NotNull
@NotBlank(message = "플랫폼 타입은 필수입니다.")
Copy link
Member

Choose a reason for hiding this comment

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

LGTM

@seongjunnoh seongjunnoh merged commit 50c7cee into develop Sep 23, 2025
3 of 4 checks passed
@seongjunnoh seongjunnoh deleted the feat/#308-notification-redirect-data-storage branch September 23, 2025 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[THIP2025-354] [feat] 알림 리다이렉트를 위한 데이터 저장

4 participants