-
Notifications
You must be signed in to change notification settings - Fork 0
[feat] 알림센터 알림 or 푸시알림 클릭 시 BE 로직 구현 #309
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[feat] 알림센터 알림 or 푸시알림 클릭 시 BE 로직 구현 #309
Conversation
… 데이터 삭제 (#308) - 리다이렉트를 위한 데이터들은 fcm 푸시알림에 포함시키는게 아니라, 다른 api 를 통해 응답하도록 수정
- 알림 DB 저장 시에 RedirectSpec 또한 전달받아 저장하도록 수정 - 저장한 알림의 notificationId 값을 이벤트 퍼블리시 과정에 포함해야하므로, EventCommandInvoker 에게 전달하도록 수정
- 알림에 함께 저장해야할 redirectSpec 구성 - 이 값을 포함해서 NotificationSyncExecutor 호출하도록 수정
- 마찬가지로 redirectSpec 구성 & 이 값을 포함해서 NotificationSyncExecutor 를 호출하도록 수정
- 퍼블리시하는 이벤트의 구성 수정에 따른 기존 코드 수정
- 리스닝하는 이벤트의 구성 수정에 따른 기존 코드 수정
- notifications 패키지 -> common/util 패키지로 이동
- PlatformType 의 정적 팩토리 메서드를 활용하여 지원하는 FE 플랫폼인지 확인하도록 코드 수정
- 필요없는 String code value 삭제 - message/domain 패키지 -> notification/domain/value 패키지로 이동
…irect-data-storage
Walkthrough알림 시스템 전반에 리다이렉트 스펙을 도입하고 이벤트 페이로드를 notificationId 중심으로 단순화했다. 알림 읽음 처리 API를 추가했으며 ErrorCode/Swagger를 확장했다. FE 플랫폼 처리 방식과 FCM 요청 DTO 검증을 보강하고 DB에 redirect_spec 컬럼을 추가했다. Changes
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)
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}
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro Disabled knowledge base sources:
📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
🧰 Additional context used🧠 Learnings (1)📓 Common learnings⏰ 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)
🔇 Additional comments (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
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 + // Notificationsrc/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 알림 생성 및 전송 로직이 반복됩니다모든 이벤트 핸들러에서 동일한 패턴(
buildFcmNotification→push)이 반복되고 있습니다. 이는 유지보수성을 저하시킬 수 있습니다.제네릭 핸들러 메서드를 도입하여 중복을 제거하는 것을 권장합니다:
+ 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: 변수명 개선 필요
tk와dev같은 축약형 변수명은 코드 가독성을 저하시킵니다.더 명확한 변수명 사용을 권장합니다:
- 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.
📒 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 || truesrc/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/messagesrc/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' || truesrc/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); |
There was a problem hiding this comment.
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' || trueLength 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 (
onFolloweeNewPost→onFolloweeNewFeed)
🤖 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
코래가 말한것처럼 위의 3부분도 통일성있게 FolloweeNewFeedEvent로 수정하면 좋을것같습니다!!~
There was a problem hiding this comment.
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.java→FolloweeNewFeedTemplate.java
2. 클래스명 및 내부 참조 변경:
FolloweeNewPostTemplate→FolloweeNewFeedTemplateFolloweeNewPostTemplate.Args→FolloweeNewFeedTemplate.ArgsFolloweeNewPostTemplate.INSTANCE→FolloweeNewFeedTemplate.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" 네이밍이 완전히 통일됩니다! 🎯
...java/konkuk/thip/notification/adapter/in/web/response/NotificationMarkToCheckedResponse.java
Show resolved
Hide resolved
src/main/java/konkuk/thip/notification/application/port/in/NotificationMarkUseCase.java
Show resolved
Hide resolved
src/main/java/konkuk/thip/notification/application/service/NotificationMarkService.java
Show resolved
Hide resolved
| // 3. 읽음 처리된 알림의 redirectSpec 반환 (for FE 알림 리다이렉트 동작) | ||
| return new NotificationMarkToCheckedResponse( | ||
| notification.getRedirectSpec().route(), | ||
| notification.getRedirectSpec().params() | ||
| ); | ||
| } |
There was a problem hiding this comment.
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.
| // 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.
.../java/konkuk/thip/notification/application/service/RoomNotificationOrchestratorSyncImpl.java
Show resolved
Hide resolved
| "postType", "RECORD" | ||
| ) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@coderabbitai 어떻게 수정하는게 좋을까?
There was a problem hiding this comment.
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을 활용한 일관된 접근법을 제안드리겠습니다.
현재 문제점 분석
- 하드코딩 방식:
notifyRoomRecordCreated,notifyRoomVoteStarted"RECORD","VOTE"문자열 직접 사용
- 파라미터 방식:
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)이 접근법의 장점
- 타입 안전성: 컴파일 타임에 오타 방지
- 일관성: 모든 곳에서 PostType enum 사용
- 유지보수성: enum에 새 타입 추가 시 한 곳에서 관리
- 가독성: 의도가 명확하게 드러남
- 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;이 방법의 장점
- 기존 코드 최소 변경: 파라미터를 받는 다른 메서드들은 그대로 유지
- 타입 안전성: 컴파일 타임에 오타 검출
- 일관성: 하드코딩 제거로 코드 품질 향상
- 유지보수성: PostType에 새 타입 추가 시 enum만 수정
- 코드베이스 일관성: 다른 곳에서도 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 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.
src/main/java/konkuk/thip/notification/domain/value/NotificationRedirectSpec.java
Show resolved
Hide resolved
hd0rable
left a comment
There was a problem hiding this 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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이부분은 프엔분들과 공유되어야하는 내용이져?? 이넘도 명확하게 네미잉되어있어서 넘 좋습니닷!!
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
엇 커맨드인데 Response dto 바로사용하셧네요 혹시 다른 이유가 있을까요??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아마 response의 변수가 두개 이상이라 그런 것 같은데 제 생각엔 읽음 표시가 command의 역할을 하지만 query의 성격을 더 띄고 있는 api라는 생각이 들기는 하는데 다른 분들 생각은 어떠신가요?? (사실 애매해서 큰 상관은 없을 것 같긴합니다)
There was a problem hiding this comment.
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 라인의 패키지에 위치시켰습니다
| @@ -0,0 +1,3 @@ | |||
| ALTER TABLE `notifications` | |||
| ADD COLUMN `redirect_spec` TEXT NULL | |||
| COMMENT 'NotificationRedirectSpec을 json 형식의 TEXT 로 저장'; No newline at end of file | |||
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
buzz0331
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
굿굿 좋습니다! 푸쉬알림의 메시지로 NotificationId를 넘겨줌으로써 푸쉬알림과 알림센터의 알림을 하나의 알림으로 봐서 일관성이 지켜진다는 점이 베리 나이스네요~
| 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
푸쉬알림으로 화면을 이동할 때 필요한 정보를 message에 담지않고 notificationId만 주입해서 알림센터 조회와 일관성을 맞췄군요! 좋습니다! 일관성이 유지되어서 유지보수가 더 쉬울 것 같아욥
| @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)); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오 읽음 처리랑 리다이렉트 데이터 반환을 통합했군요. 푸쉬알림을 눌렀을 때도 결국 사용자가 알림을 읽은 것이니 푸쉬알림과 알림센터의 알림을 하나의 알림으로 볼 수 있겠네요! 너무 좋습니다
| 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); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
음 platformtype은 fcmtoken 테이블에 정의되어 있는 VO라고 생각해서 notification 패키지 하위에 두었던 것 같은데 패키지를 이동시킨 다른 이유가 있을까요??
There was a problem hiding this comment.
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 하위로 다시 이동시키겠습니다!!
| @Convert(converter = NotificationRedirectSpecConverter.class) | ||
| @Column(name = "redirect_spec", columnDefinition = "TEXT") // nullable | ||
| private NotificationRedirectSpec redirectSpec; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아마 response의 변수가 두개 이상이라 그런 것 같은데 제 생각엔 읽음 표시가 command의 역할을 하지만 query의 성격을 더 띄고 있는 api라는 생각이 들기는 하는데 다른 분들 생각은 어떠신가요?? (사실 애매해서 큰 상관은 없을 것 같긴합니다)
|
|
||
| NotificationRedirectSpec redirectSpec = new NotificationRedirectSpec( | ||
| MessageRoute.FEED_USER, | ||
| Map.of("userId", actorUserId) | ||
| ); | ||
|
|
There was a problem hiding this comment.
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으로 따로 변수명을 빼두는 것 어떨까요??
There was a problem hiding this comment.
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 에 포함하는 것을 의미하신 걸까요??
제가 제대로 이해한게 맞는지 여쭤봅니다!
There was a problem hiding this comment.
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)
);이런식으로 주입하는 것을 말씀 드린거긴 합니다! 하드코딩되어 있는 변수를 한 곳에 관리하면 유지보수에 더 나을 것 같다고 생각했습니다.
.../java/konkuk/thip/notification/application/service/RoomNotificationOrchestratorSyncImpl.java
Show resolved
Hide resolved
| NotificationRedirectSpec redirectSpec = TestEntityFactory.createNotificationRedirectSpec( | ||
| MessageRoute.FEED_USER, Map.of("userId", 123L) // 특정 유저의 피드 페이지로 이동 | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Redirect Key를 Enum으로 빼면 이 부분도 모두 바꿀수 있을 것 같네욥
- FolloweeNewPost -> FolloweeNewFeed 로 네이밍 수정
seongjunnoh
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- 네이밍 수정
- request dto bean validation 추가
만 수정해서 커밋했습니다!
현준님 리뷰에서 언급해주신 NotificationRedirectSpec value 에서의 enum 도입과 NotificationJapEntity 에서 해당 컬럼의 nullable 과 관련해서 NPE 발생가능성에 대한 코드 수정은 현재 pr 머지 이후에 추가로 이슈 생성해도 괜찮을까 여쭤봅니다
현재 구현으로 FE 연동에 문제없어 보이고, 오늘 코드 수정할 시간이 부족해서 추후에 다시 리펙해도 되는지 여쭤봅니다 하하
There was a problem hiding this 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; @Slf4jsrc/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.
📒 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.javasrc/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.javasrc/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.javasrc/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: 검증 메시지 커스터마이징 일관성 확보, LGTMDTO 레벨의 메시지 일관화가 잘 반영됐습니다.
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 기반으로 통일한 설계가 명확합니다.
Mapimport 포함 구성도 깔끔합니다.
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.javasrc/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)rg -nP -C3 '\bFcmTokenEnableStateChangeRequest\b|@Valid\s+FcmTokenEnableStateChangeRequest\b' -g '!**/test/**'
- notification 패키지 하위로 이동 - 프로젝트 전체의 util 적인 성질보다는 notification 패키지 하위에서 사용되는 value 로 생각하는 것으로 결정됨
There was a problem hiding this 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.
📒 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와 메시지 추가 좋습니다. 이전 피드백과도 합치합니다.
hd0rable
left a comment
There was a problem hiding this 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 = "플랫폼 타입은 필수입니다.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
#️⃣ 연관된 이슈
📝 작업 내용
📸 스크린샷
💬 리뷰 요구사항
📌 PR 진행 시 이러한 점들을 참고해 주세요
Summary by CodeRabbit