Skip to content

Comments

[FEAT] 알림 도메인 요구사항 개발#163

Merged
Be-HinD merged 3 commits intomainfrom
feat/noti-list
Dec 23, 2025
Merged

[FEAT] 알림 도메인 요구사항 개발#163
Be-HinD merged 3 commits intomainfrom
feat/noti-list

Conversation

@Be-HinD
Copy link
Member

@Be-HinD Be-HinD commented Dec 23, 2025

📝 Pull Request

📌 PR 종류

해당하는 항목에 체크해주세요.

  • 기능 추가 (Feature)
  • 버그 수정 (Fix)
  • 문서 수정 (Docs)
  • 코드 리팩터링 (Refactor)
  • 테스트 추가 (Test)
  • 기타 변경 (Chore)

✨ 변경 내용

알림 도메인 요구사항 건 개발 진행했습니다.
추가된 엔드포인트는 아래와 같습니다.

  • GET /api/v1/notifications 알림 목록 조회
  • GET /api/v1/notifications/unread-count 읽지 않은 알림 개수 조회
  • POST /api/v1/notifications/{notificationId}/read 단건 읽음 처리
  • POST /api/v1/notifications/read-all 전체 읽음 처리

주요 변경점은 아래와 같습니다.

  • Refresh Token 쿠키 관련 이슈 수정 있습니다. (쿠키 도메인 지정 건)

  • 알림 Entity 일부 수정 있습니다. (테이블명 notification -> notifications, 필드 is_read -> read_at)

    • 읽음 여부 필드의 경우 LocalDataTime으로 변경 되었습니다. (쿼리 처리 용이성 및 확장성 고려)
    • Entity 변경으로 인해 공지 후 DB 초기화 1회 해야합니다.

🔍 관련 이슈

해당 PR이 해결하는 이슈가 있다면 연결해주세요.
#162


🧪 테스트

변경된 기능에 대한 테스트 범위 또는 테스트 결과를 작성해주세요.

  • 유닛 테스트 추가 / 수정
  • 통합 테스트 검증
  • 수동 테스트 완료

.http로 시나리오 테스트 진행 (팔로우 요청으로 알림 2개 생성, 알림 목록 조회, 읽지않은 알림 개수 조회, 알림 단건 읽음, 전체 알림 읽음)

readAt 동작 여부 테스트 완료
image


🚨 확인해야 할 사항 (Checklist)

PR을 제출하기 전에 아래 항목들을 확인해주세요.

  • 코드 포매팅 완료
  • 불필요한 파일/코드 제거
  • 로직 검증 완료
  • 프로젝트 빌드 성공
  • 린트/정적 분석 통과 (해당 시)

🙋 기타 참고 사항

기능명세에 대해서는 아래 Discussions 참고하면됩니다.
추가 기능 - 알림 기능 명세서

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 알림 목록 조회(커서 기반 페이지네이션), 개별/전체 읽음 처리, 읽지 않은 알림 수 조회 추가
    • 알림 응답 구조가 읽음 타임스탬프로 변경되어 읽음 시점 제공
  • 보안 개선

    • 리프레시 토큰 쿠키에 도메인 속성 설정
  • 문서·테스트

    • 알림 관련 API 문서화 및 E2E형 HTTP 테스트 컬렉션 추가
  • 오류 처리

    • 알림 전용 오류 코드 및 접근 거부/찾을 수 없음 예외 추가

✏️ Tip: You can customize this high-level summary in your review settings.

- 알림 목록 조회 (커서처리)
- 알림 단건 Read API 개발
- 전체 알림 Read API 개발
- 읽지 않은 알림 개수 조회 API 개발
- notificaitons Entity 일부 수정 (테이블, 필드)
- .http 테스트 작성 및 정상 동작 확인
@Be-HinD Be-HinD self-assigned this Dec 23, 2025
@Be-HinD Be-HinD added 🐞bug Something isn't working ✨enhancement New feature or request labels Dec 23, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 23, 2025

Walkthrough

알림 도메인(엔티티, DTO, 저장소, 서비스, 컨트롤러, 예외)과 에러코드가 추가되었고, 인증 컨트롤러의 리프레시 토큰 쿠키에 도메인(".wego.monster")이 설정되었습니다.

Changes

Cohort / File(s) 변경 요약
쿠키 도메인 설정
src/main/java/team/wego/wegobackend/auth/presentation/AuthController.java
리프레시 토큰 생성/삭제 시 Cookie에 .wego.monster 도메인 속성 추가
공통 에러 코드
src/main/java/team/wego/wegobackend/common/exception/AppErrorCode.java
USER_ACCESS_DENIED, NOT_FOUND_NOTIFICATION 열거형 상수 추가
알림 도메인 모델
src/main/java/team/wego/wegobackend/notification/domain/Notification.java
isReadreadAt(LocalDateTime)로 변경, 테이블명 notifications로 수정, markAsRead()가 타임스탬프 기록
알림 DTO들
src/main/java/team/wego/wegobackend/notification/application/dto/response/NotificationResponse.java, .../NotificationListResponse.java
isRead 필드 제거하고 readAt 적용, @QueryProjection 생성자 추가, 목록 응답 레코드 추가(커서 포함)
알림 서비스
src/main/java/team/wego/wegobackend/notification/application/NotificationService.java
알림 목록 조회(커서 페이징), 미읽음 카운트, 개별/전체 읽음 처리 메서드 추가(트랜잭션 경계 포함)
알림 컨트롤러 및 문서
src/main/java/team/wego/wegobackend/notification/presentation/NotificationController.java, .../NotificationControllerDocs.java
알림 목록, 미읽음 카운트, 단건 읽음, 전체 읽음 엔드포인트 및 OpenAPI 문서 인터페이스 추가
예외 클래스
src/main/java/team/wego/wegobackend/notification/exception/NotificationAccessDeniedException.java, .../NotificationNotFoundException.java
알림 접근 거부 및 미발견 전용 AppException 서브클래스 추가
저장소 (JPA/커스텀 구현)
src/main/java/team/wego/wegobackend/notification/repository/NotificationRepository.java, NotificationRepositoryCustom.java, NotificationRepositoryImpl.java
커스텀 인터페이스 도입, QueryDSL 기반 커서 조회 구현, 미읽음 카운트 쿼리 및 일괄 읽음(UPDATE) 쿼리 추가
설정 / 테스트 시나리오
src/main/resources/application.yml, src/test/http/noti/noti-api.http
로컬 프로필 활성화 설정, 알림 E2E HTTP 컬렉션 추가

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller as NotificationController
    participant Service as NotificationService
    participant Repo as NotificationRepository
    participant DB as Database

    rect rgb(230,240,255)
    Note over Client,DB: 알림 목록 조회 (커서 기반)
    Client->>Controller: GET /api/v1/notifications?cursor=&size=
    Controller->>Service: notificationList(userId, cursor, size)
    Service->>Repo: findNotificationList(userId, cursor, size)
    Repo->>DB: QueryDSL SELECT (join actor) with cursor & limit
    DB-->>Repo: List<NotificationResponse>
    Repo-->>Service: 반환
    Service->>Controller: NotificationListResponse (notifications + nextCursor)
    Controller-->>Client: 200 OK
    end

    rect rgb(230,255,230)
    Note over Client,DB: 단건 읽음 처리
    Client->>Controller: POST /api/v1/notifications/{id}/read
    Controller->>Service: readNotification(userId, id)
    Service->>Repo: findById(id)
    DB-->>Repo: Notification 엔티티
    Repo-->>Service: Notification
    Service->>Service: 소유자 검증
    alt 소유자 일치
        Service->>Service: notification.markAsRead() (readAt = now)
        Service->>Repo: save(notification)
        Repo->>DB: UPDATE read_at
        DB-->>Repo: OK
        Service-->>Controller: void
        Controller-->>Client: 204 No Content
    else 불일치
        Service-->>Controller: NotificationAccessDeniedException
        Controller-->>Client: 403 Forbidden
    end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 알림 밭에 톡톡, 당근 소식 쌓이고
읽음 시간 찍어 토끼가 춤추네.
커서 따라 발자국 남기고,
저장소는 조용히 노래해,
작은 알림이 세상을 움직여라 ✨📯

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 주요 변경 사항과 잘 일치합니다. 알림 도메인의 새로운 요구사항(엔드포인트 추가, 엔티티 변경 등)을 포괄적으로 반영하고 있습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/noti-list

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
src/main/java/team/wego/wegobackend/notification/presentation/NotificationController.java (1)

30-35: @Validated 어노테이션 누락으로 @Min/@Max 검증이 동작하지 않습니다.

@RequestParam에 적용된 @Min(1), @Max(100) 검증이 동작하려면 컨트롤러 클래스에 @Validated 어노테이션이 필요합니다.

🔎 제안된 수정
+import org.springframework.validation.annotation.Validated;
+
 @Tag(name = "SSE 엔드포인트", description = "SSE 연결을 위한 엔드포인트")
 @Slf4j
 @RestController
 @RequiredArgsConstructor
+@Validated
 @RequestMapping("/api/v1/notifications")
 public class NotificationController {
♻️ Duplicate comments (1)
src/main/java/team/wego/wegobackend/auth/presentation/AuthController.java (1)

144-153: 일관성 있음: 도메인이 생성 메서드와 동일하게 설정되었습니다.

쿠키 삭제 시 생성 시와 동일한 도메인을 사용하는 것이 올바른 구현입니다. 단, 위의 createRefreshTokenCookie 코멘트에서 제안한 도메인 외부화를 함께 적용하시면 일관성 있게 관리할 수 있습니다.

</review_comment_end -->

🧹 Nitpick comments (8)
src/main/java/team/wego/wegobackend/auth/presentation/AuthController.java (1)

130-139: 도메인 설정을 외부 설정으로 외부화하세요.

쿠키 도메인이 ".wego.monster"로 하드코딩되어 있습니다. 개발, 스테이징, 운영 환경마다 다른 도메인을 사용할 가능성이 있으므로, application.properties 또는 환경 변수로 외부화하는 것이 좋습니다.

🔎 제안 수정 방법

Step 1: application.properties에 도메인 설정 추가

cookie.domain=.wego.monster

Step 2: @Value 또는 @ConfigurationProperties로 주입

 @Slf4j
 @RestController
 @RequiredArgsConstructor
 @RequestMapping("/api/v1/auth")
 public class AuthController implements AuthControllerDocs {

     private final AuthService authService;
     private final JwtTokenProvider jwtTokenProvider;
+    @Value("${cookie.domain}")
+    private String cookieDomain;

     // ... 다른 코드 ...

     private Cookie createRefreshTokenCookie(String refreshToken) {
         Cookie cookie = new Cookie("refreshToken", refreshToken);
         cookie.setHttpOnly(true);
         cookie.setSecure(true);
         cookie.setPath("/");
-        cookie.setDomain(".wego.monster");
+        cookie.setDomain(cookieDomain);
         cookie.setMaxAge((int) jwtTokenProvider.getRefreshTokenExpiration());
         cookie.setAttribute("SameSite", "Strict");
         return cookie;
     }
src/main/java/team/wego/wegobackend/notification/domain/Notification.java (2)

50-51: LocalDateTime 대신 ZonedDateTime 사용을 고려하세요.

LocalDateTime은 타임존 정보가 없어서 서버가 여러 타임존에 배포된 경우 일관성 문제가 발생할 수 있습니다. ZonedDateTime을 사용하면 명확한 시점을 저장할 수 있습니다.

🔎 제안하는 변경사항
-import java.time.LocalDateTime;
+import java.time.ZonedDateTime;

-@Column(name = "read_at")
-private LocalDateTime readAt;
+@Column(name = "read_at")
+private ZonedDateTime readAt;

그리고 markAsRead() 메서드도 함께 수정:

 public void markAsRead() {
-    this.readAt = LocalDateTime.now();
+    this.readAt = ZonedDateTime.now();
 }

27-82: 가독성 향상을 위한 헬퍼 메서드 추가를 고려하세요.

readAt 필드의 null 체크를 여러 곳에서 반복하게 될 수 있습니다. isUnread() 또는 isRead() 헬퍼 메서드를 추가하면 코드 가독성이 향상됩니다.

🔎 제안하는 헬퍼 메서드
+public boolean isUnread() {
+    return this.readAt == null;
+}
+
+public boolean isRead() {
+    return this.readAt != null;
+}
+
 public void markAsRead() {
     this.readAt = LocalDateTime.now();
 }
src/main/java/team/wego/wegobackend/notification/application/dto/response/NotificationListResponse.java (1)

5-7: 간결하고 적절한 DTO 구현입니다.

커서 기반 페이지네이션을 위한 nextCursor 필드가 올바르게 설계되었습니다. 필요하다면 nextCursor가 null일 때(마지막 페이지)의 의미를 명확히 하는 Javadoc 추가를 고려해보세요.

src/main/java/team/wego/wegobackend/notification/repository/NotificationRepositoryImpl.java (2)

27-40: 오래된 주석 - 정리 필요

주석에 오타("rivate" → "private")가 있고, isReadreadAt으로 변경되었지만 주석이 업데이트되지 않았습니다. 불필요한 주석은 제거하거나 최신 상태로 유지해 주세요.


67-71: 메서드 이름이 Java 네이밍 컨벤션을 따르지 않습니다.

ItCursor는 대문자로 시작하여 클래스명처럼 보입니다. Java 메서드 네이밍 컨벤션에 따라 camelCase인 ltCursor 또는 lessThanCursor로 변경하는 것을 권장합니다.

🔎 제안된 수정
-    private BooleanExpression ItCursor(Long cursorNotificationId) {
+    private BooleanExpression ltCursor(Long cursorNotificationId) {
         return cursorNotificationId == null
             ? null
             : QNotification.notification.id.lt(cursorNotificationId);
     }

Line 60도 함께 수정:

-                ItCursor(cursorNotificationId)
+                ltCursor(cursorNotificationId)
src/main/java/team/wego/wegobackend/notification/application/NotificationService.java (2)

65-70: 사용되지 않는 변수 resultCount

markAllAsRead 결과값을 저장하지만 사용하지 않습니다. 로깅이나 반환값으로 활용하거나, 필요 없다면 제거해 주세요.

🔎 제안된 수정
     public void readAllNotification(Long userId) {
 
         User user = userRepository.findById(userId).orElseThrow(UserNotFoundException::new);
 
-        int resultCount = notificationRepository.markAllAsRead(user.getId());
+        notificationRepository.markAllAsRead(user.getId());
     }

27-37: 인증된 userId에 대한 User 조회의 필요성 검토

userId@AuthenticationPrincipal에서 제공되므로 유효한 사용자임이 보장됩니다. 모든 메서드에서 userRepository.findById()를 호출하는 것이 필요한지 검토해 주세요. 단, User 엔티티의 다른 필드가 필요하거나 존재 확인이 비즈니스 요구사항이라면 현재 방식이 적절합니다.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c59768 and e25260b.

📒 Files selected for processing (14)
  • src/main/java/team/wego/wegobackend/auth/presentation/AuthController.java
  • src/main/java/team/wego/wegobackend/common/exception/AppErrorCode.java
  • src/main/java/team/wego/wegobackend/notification/application/NotificationService.java
  • src/main/java/team/wego/wegobackend/notification/application/dto/response/NotificationListResponse.java
  • src/main/java/team/wego/wegobackend/notification/application/dto/response/NotificationResponse.java
  • src/main/java/team/wego/wegobackend/notification/domain/Notification.java
  • src/main/java/team/wego/wegobackend/notification/exception/NotificationAccessDeniedException.java
  • src/main/java/team/wego/wegobackend/notification/exception/NotificationNotFoundException.java
  • src/main/java/team/wego/wegobackend/notification/presentation/NotificationController.java
  • src/main/java/team/wego/wegobackend/notification/repository/NotificationRepository.java
  • src/main/java/team/wego/wegobackend/notification/repository/NotificationRepositoryCustom.java
  • src/main/java/team/wego/wegobackend/notification/repository/NotificationRepositoryImpl.java
  • src/main/resources/application.yml
  • src/test/http/noti/noti-api.http
🔇 Additional comments (13)
src/main/java/team/wego/wegobackend/notification/exception/NotificationAccessDeniedException.java (1)

6-11: 구현이 올바릅니다.

표준 예외 패턴을 따르고 있으며, USER_ACCESS_DENIED 에러 코드를 적절히 사용하고 있습니다.

src/main/java/team/wego/wegobackend/common/exception/AppErrorCode.java (1)

39-42: 에러 코드 추가가 적절합니다.

  • USER_ACCESS_DENIEDFORBIDDEN(403) 상태를 사용하여 권한 부족을 올바르게 표현하고 있습니다.
  • NOT_FOUND_NOTIFICATIONNOT_FOUND(404) 상태를 사용하여 리소스 부재를 정확히 나타냅니다.
  • 후행 쉼표(trailing comma) 추가는 향후 enum 확장 시 diff를 깔끔하게 유지하는 좋은 관례입니다.
src/main/java/team/wego/wegobackend/notification/domain/Notification.java (1)

79-81: 중복 읽음 처리 시 타임스탬프 덮어쓰기 동작을 확인하세요.

markAsRead()를 여러 번 호출하면 최초 읽은 시간이 덮어씌워집니다. 만약 "최초 읽음 시간"을 보존해야 한다면 조건부 설정이 필요합니다.

🔎 최초 읽음 시간 보존이 필요한 경우
 public void markAsRead() {
+    if (this.readAt == null) {
         this.readAt = LocalDateTime.now();
+    }
 }
src/main/java/team/wego/wegobackend/notification/exception/NotificationNotFoundException.java (1)

6-11: 구현이 올바릅니다.

표준 예외 패턴을 따르고 있으며, NOT_FOUND_NOTIFICATION 에러 코드를 적절히 사용하고 있습니다.

src/main/java/team/wego/wegobackend/notification/repository/NotificationRepositoryCustom.java (1)

6-13: 커스텀 리포지토리 인터페이스 설계가 적절합니다.

커서 기반 페이지네이션을 위한 메서드 시그니처가 명확하며, cursorNotificationId를 통한 다음 페이지 조회가 가능하도록 설계되었습니다.

src/main/java/team/wego/wegobackend/notification/repository/NotificationRepository.java (1)

11-17: countUnread 쿼리가 올바르게 구현되었습니다.

readAt is null 조건을 사용하여 읽지 않은 알림을 정확히 카운트하고 있으며, 새로운 필드 설계와 일관성을 유지하고 있습니다.

src/main/java/team/wego/wegobackend/notification/repository/NotificationRepositoryImpl.java (1)

56-64: actor가 null인 경우 알림이 누락될 수 있습니다.

join(notification.actor, user)는 inner join으로 동작하여, actor가 null인 알림은 결과에서 제외됩니다. 시스템 알림처럼 actor가 없는 경우도 조회되어야 한다면 leftJoin으로 변경이 필요합니다.

🔎 leftJoin 사용 제안
         .from(notification)
-        .join(notification.actor, user)
+        .leftJoin(notification.actor, user)
         .where(
src/main/java/team/wego/wegobackend/notification/presentation/NotificationController.java (2)

68-80: 알림 목록 조회 - 구현이 적절합니다.

커서 기반 페이지네이션과 size 검증이 잘 적용되었습니다. @Validated 추가 후 정상 동작할 것입니다.


110-113: 204 NO_CONTENT 응답에 body 포함은 비일관적입니다.

HTTP 204는 일반적으로 응답 본문이 없음을 의미합니다. ApiResponse.success(204, null)을 body로 반환하면 일부 클라이언트에서 혼란을 줄 수 있습니다. 프로젝트의 API 응답 패턴 일관성을 확인해 주세요.

src/main/java/team/wego/wegobackend/notification/application/dto/response/NotificationResponse.java (2)

47-61: from(User) 테스트 메서드에서 readAt 필드가 설정되지 않았습니다.

테스트용 알림 응답 생성 시 readAt이 null로 남습니다. 클라이언트에서 readAt을 기대하는 경우 문제가 될 수 있습니다. 의도적인 것인지 확인해 주세요.

🔎 readAt 명시적 설정 제안
         .type(NotificationType.TEST)
         .message("테스트 알림 응답")
+        .readAt(null)  // 명시적으로 읽지 않음 상태
         .relatedId(null)

63-90: QueryProjection 생성자 - LGTM!

QueryDSL projection을 위한 생성자가 올바르게 구현되었습니다. 필드 순서가 NotificationRepositoryImpl의 projection과 일치합니다.

src/main/java/team/wego/wegobackend/notification/application/NotificationService.java (2)

49-63: 단건 읽음 처리 로직 - 접근 제어가 잘 구현되었습니다.

사용자가 본인의 알림만 읽음 처리할 수 있도록 검증하고, 권한 없는 경우 적절한 예외를 발생시킵니다.


34-34: Java 21 호환성 확인: 문제없음

프로젝트의 build.gradle에서 languageVersion = JavaLanguageVersion.of(21)로 설정되어 있으므로, List.getLast() 사용에는 호환성 문제가 없습니다. 현재 코드는 정확합니다.

Likely an incorrect or invalid review comment.

Comment on lines +19 to +26
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("""
update Notification n
set n.readAt = CURRENT_TIMESTAMP
where n.receiver.id = :userId
and n.readAt is null
""")
int markAllAsRead(@Param("userId") Long userId);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

CURRENT_TIMESTAMP 사용 시 일관성 및 데이터베이스 호환성을 확인하세요.

Line 22에서 CURRENT_TIMESTAMP를 사용하고 있는데, 이는 다음 문제를 야기할 수 있습니다:

  1. 일관성 문제: 엔티티의 markAsRead() 메서드는 Java의 LocalDateTime.now()를 사용하는 반면, 벌크 업데이트는 DB의 CURRENT_TIMESTAMP를 사용합니다. 이 둘은 애플리케이션 서버와 DB 서버의 시간 차이로 인해 약간 다를 수 있습니다.

  2. 데이터베이스 호환성: CURRENT_TIMESTAMP는 MySQL에서 작동하지만, H2(테스트용), PostgreSQL 등 다른 데이터베이스에서는 함수명이나 동작이 다를 수 있습니다.

🔎 일관성을 개선하는 방법

방법 1 (권장): 애플리케이션 레벨에서 타임스탬프를 생성하여 파라미터로 전달:

-@Modifying(clearAutomatically = true, flushAutomatically = true)
-@Query("""
-    update Notification n
-    set n.readAt = CURRENT_TIMESTAMP
-    where n.receiver.id = :userId
-      and n.readAt is null
-""")
-int markAllAsRead(@Param("userId") Long userId);
+@Modifying(clearAutomatically = true, flushAutomatically = true)
+@Query("""
+    update Notification n
+    set n.readAt = :readAt
+    where n.receiver.id = :userId
+      and n.readAt is null
+""")
+int markAllAsRead(@Param("userId") Long userId, @Param("readAt") LocalDateTime readAt);

그리고 서비스 레이어에서:

LocalDateTime now = LocalDateTime.now();
notificationRepository.markAllAsRead(userId, now);

방법 2: JPA 표준 함수 사용 (DB 독립적):

-    set n.readAt = CURRENT_TIMESTAMP
+    set n.readAt = CURRENT_TIMESTAMP()

단, 이 경우에도 애플리케이션과 DB 시간 차이 문제는 여전히 존재합니다.

Comment on lines +5 to +6
profiles:
active: local
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, check the application.yml file structure and content
cat -n src/main/resources/application.yml

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 2573


🏁 Script executed:

# Check for profile-specific configuration files
fd -e yml -e yaml -e properties "^application-" src/main/resources/

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 52


🏁 Script executed:

# Look for any deployment or build configuration
find . -type f \( -name "*.yml" -o -name "*.yaml" -o -name "*.properties" \) | grep -E "(deploy|build|env|config)" | head -20

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 105


🏁 Script executed:

# Check for environment setup in gradle or maven build files
find . -type f \( -name "pom.xml" -o -name "build.gradle*" \) -exec head -50 {} \;

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 1692


🏁 Script executed:

# Check for Docker files
fd -e dockerfile "Dockerfile*" .

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 52


🏁 Script executed:

# Check for GitHub Actions workflows
find .github/workflows -type f -name "*.yml" -o -name "*.yaml" 2>/dev/null | head -10

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 100


🏁 Script executed:

# Look for kubernetes or deployment configs
find . -type f \( -name "*.k8s.yml" -o -name "deployment.yml" -o -name "docker-compose*" \)

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 90


🏁 Script executed:

# Check GitHub workflows for deployment scripts
cat .github/workflows/*.yml 2>/dev/null | head -100

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 1398


🏁 Script executed:

# Check docker-compose setup
cat docker-compose.yml

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 406


🏁 Script executed:

# Search for any production or profile-specific configs
find . -type f -name "*.yml" -o -name "*.yaml" | xargs grep -l "prod\|production" 2>/dev/null | head -10

Repository: WeGo-Together/WeGo_BackEnd

Length of output: 52


프로덕션 배포 시 local 프로필이 활성화되어 애플리케이션이 실패합니다.

배포 스크립트가 스프링 프로필을 오버라이드하지 않기 때문에, 현재 설정으로 프로덕션에 배포되면 다음 문제가 발생합니다:

  • Redis가 localhost:6379를 가리킴 (프로덕션 서버에 존재하지 않음) → 연결 실패
  • JPA ddl-auto: update가 활성화되어 운영 데이터베이스 스키마가 자동 수정됨 → 위험
  • 개발 전용 설정들이 프로덕션에서 실행됨

필수 조치:

  1. 프로필 분리: application-local.yml, application-prod.yml 생성
  2. 배포 스크립트 수정: 배포 시 --spring.profiles.active=prod 명시
    java -jar "$JAR" --server.port="$APP_PORT" --spring.profiles.active=prod
    
  3. 프로덕션 설정 파일: Redis, DB, JPA 설정을 프로덕션 환경에 맞게 구성
🤖 Prompt for AI Agents
src/main/resources/application.yml lines 5-6: 현재 프로파일이 "local"로 고정되어 있어 프로덕션 배포
시 잘못된 설정이 적용됩니다; 이를 해결하려면 로컬 전용 설정을 application-local.yml로 옮기고 production 전용 설정을
application-prod.yml로 생성한 뒤 기본 application.yml에서 active 프로파일을 제거(또는 비워 둠)하여 환경별
프로파일로 분리하고, 배포 스크립트에서 항상 --spring.profiles.active=prod 를 명시하도록 수정하며(production에서
Redis, DB, JPA 등 민감한 설정을 application-prod.yml에 맞게 구성) 개발용 ddl-auto: update 같은
위험한 설정은 production 파일에서 제거하거나 안전한 값으로 변경하세요.

Comment on lines +62 to +65
### 읽음 처리 (단건)
POST http://localhost:8080/api/v1/notifications/1/read
Content-Type: application/json
Authorization: Bearer {{accessToken}}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

하드코딩된 알림 ID 사용에 주의하세요.

Line 63에서 notificationId=1을 하드코딩하고 있습니다. 실제 생성된 알림 ID는 테스트 실행 시마다 달라질 수 있으므로, 이전 단계(Line 52-55의 알림 목록 조회)에서 반환된 ID를 동적으로 추출하여 사용하는 것이 좋습니다.

🔎 제안하는 개선사항

알림 목록 조회 후 응답에서 ID를 추출:

 ### 알림 목록 조회
 GET http://localhost:8080/api/v1/notifications?size=20
 Content-Type: application/json
 Authorization: Bearer {{accessToken}}
 
+> {%
+  client.global.set("notificationId", response.body.data.notifications[0].notificationId);
+%}
+
 ### 읽지 않은 알림 개수 조회
 GET http://localhost:8080/api/v1/notifications/unread-count
 Content-Type: application/json
 Authorization: Bearer {{accessToken}}
 
 ### 읽음 처리 (단건)
-POST http://localhost:8080/api/v1/notifications/1/read
+POST http://localhost:8080/api/v1/notifications/{{notificationId}}/read
 Content-Type: application/json
 Authorization: Bearer {{accessToken}}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/test/http/noti/noti-api.http around lines 62 to 65, the request hardcodes
notification ID `1`; replace this with the ID extracted from the earlier
notification-list response (lines ~52-55) so tests use the actual created
notification. Update the read-request to reference a variable (e.g.,
{{notificationId}}) and add a step after the list call that parses the list
response to set that variable from the first/desired item's id using your HTTP
client’s response-extraction feature (JSONPath or similar); ensure the
extraction handles empty lists and fails the test early if no ID is found.

+ SSE test 엔드포인트 제거
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/main/java/team/wego/wegobackend/notification/presentation/NotificationController.java (1)

98-108: 204 NO_CONTENT 응답에 body를 포함하는 것이 적절한지 확인하세요.

단건 읽음 처리와 동일한 이슈입니다. HTTP 204 NO_CONTENT 상태 코드 사용 시 응답 본문을 포함하지 않는 것이 표준이므로, API 설계 일관성과 HTTP 표준 준수 사이의 트레이드오프를 고려하세요.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e25260b and eec5a38.

📒 Files selected for processing (2)
  • src/main/java/team/wego/wegobackend/notification/presentation/NotificationController.java
  • src/main/java/team/wego/wegobackend/notification/presentation/NotificationControllerDocs.java
🔇 Additional comments (4)
src/main/java/team/wego/wegobackend/notification/presentation/NotificationController.java (3)

48-63: LGTM! 커서 기반 페이지네이션 구현이 적절합니다.

알림 목록 조회 엔드포인트가 커서 기반 페이지네이션과 적절한 유효성 검증(1-100 범위의 size)을 사용하여 올바르게 구현되었습니다.


65-78: LGTM! 읽지 않은 알림 개수 조회 구현이 올바릅니다.

간단하고 명확한 구현입니다.


85-96: 204 NO_CONTENT 응답에 body를 포함하는 것이 적절한지 확인하세요.

HTTP 204 NO_CONTENT는 RFC 7231 표준에 따라 응답 본문을 포함하지 않아야 합니다. 현재 ApiResponse.success(204, null)을 사용하고 있는데, 이는 API 일관성을 위한 것으로 보이지만 표준과 다를 수 있습니다. Spring은 204 응답에서 body를 무시할 수 있으나, 명시적으로 body 없이 반환하거나 다른 상태 코드(예: 200 OK)를 사용하는 것을 고려하세요.

대안:

  1. Body 없이 204 반환: ResponseEntity.noContent().build()
  2. 200 OK와 성공 메시지 반환: ResponseEntity.ok(ApiResponse.success(200, null))
src/main/java/team/wego/wegobackend/notification/presentation/NotificationControllerDocs.java (1)

22-52: LGTM! API 문서화 인터페이스가 잘 구성되었습니다.

모든 엔드포인트가 적절한 Swagger 어노테이션과 명확한 한글 설명으로 문서화되어 있으며, 메서드 시그니처가 컨트롤러 구현과 정확히 일치합니다.


private final SseEmitterService sseEmitterService;
private final NotificationService notificationService;
private final UserRepository userRepository; //TEST 의존성 주입
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

테스트용 의존성을 프로덕션 코드에서 제거하세요.

UserRepository가 "TEST 의존성 주입" 주석과 함께 선언되어 있지만, 컨트롤러 내에서 실제로 사용되지 않습니다. 테스트용 의존성은 프로덕션 코드에 포함되어서는 안 됩니다.

🔎 제안하는 수정
     private final SseEmitterService sseEmitterService;
     private final NotificationService notificationService;
-    private final UserRepository userRepository;    //TEST 의존성 주입
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private final UserRepository userRepository; //TEST 의존성 주입
private final SseEmitterService sseEmitterService;
private final NotificationService notificationService;
🤖 Prompt for AI Agents
In
src/main/java/team/wego/wegobackend/notification/presentation/NotificationController.java
around line 38, the private final UserRepository field marked "TEST 의존성 주입" is
unused in the controller; remove this test-only dependency from production code
by deleting the UserRepository field and removing it from the constructor
parameter list and assignments, then clean up the now-unused import(s) and any
references in the class or tests that relied on constructor injection (move test
wiring to test configuration or mocks instead).

import team.wego.wegobackend.common.security.CustomUserDetails;
import team.wego.wegobackend.notification.application.dto.response.NotificationListResponse;

@Tag(name = "알림 API", description = "알림와 관련된 API 리스트 \uD83D\uDC08")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

한글 문법 오류를 수정하세요.

"알림와"는 잘못된 표현입니다. "알림과"로 수정해야 합니다.

🔎 제안하는 수정
-@Tag(name = "알림 API", description = "알림와 관련된 API 리스트 🐈")
+@Tag(name = "알림 API", description = "알림과 관련된 API 리스트 🐈")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Tag(name = "알림 API", description = "알림와 관련된 API 리스트 \uD83D\uDC08")
@Tag(name = "알림 API", description = "알림과 관련된 API 리스트 \uD83D\uDC08")
🤖 Prompt for AI Agents
In
src/main/java/team/wego/wegobackend/notification/presentation/NotificationControllerDocs.java
around line 19, the @Tag description contains a Korean grammar error: replace
"알림와" with the correct "알림과" so the description reads "알림과 관련된 API 리스트 🐈"
(ensure the Unicode emoji remains intact).

@Be-HinD Be-HinD merged commit cce18f0 into main Dec 23, 2025
1 check passed
@Be-HinD Be-HinD deleted the feat/noti-list branch December 23, 2025 10:55
@github-project-automation github-project-automation bot moved this from Backlog to Done in WeGo-Together Backend Dec 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐞bug Something isn't working ✨enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant