Skip to content

feat: 알림 목록·읽음 API - #273

Open
sevineleven wants to merge 4 commits into
devfrom
feat/263-notification-list-read
Open

feat: 알림 목록·읽음 API#273
sevineleven wants to merge 4 commits into
devfrom
feat/263-notification-list-read

Conversation

@sevineleven

Copy link
Copy Markdown
Contributor

Situation

  • 앱의 알림 화면은 UI 만 있고 데이터가 전부 하드코딩이었다. 홈의 안읽음 배지도 그 가짜 목록을 세고 있어서, 사용자가 보는 숫자가 아무것도 가리키지 않았다.
  • 서버에 알림을 담을 자리도, 읽었는지 기억할 자리도 없었다.
  • 프론트(조영찬)가 목록·하나 읽음·전체 읽음 세 개를 요청하면서 "알림 종류를 정해 주시면 앱에서 아이콘·문구를 맞추겠다" 고 했다.

Task

엔드포인트를 여는 것보다 정할 것이 많았다.

정할 것 질문
알림 종류 어떤 값들을 보낼 것인가. 몇 개를 미리 정의하나
문구의 주인 "내일 정선으로 떠나요" 를 서버가 쓰나, 앱이 쓰나
읽음의 안전성 남의 알림을 ID 만으로 읽음 처리할 수 있으면 안 된다
목록의 상한 알림은 쌓이는 데이터인가
소유자 무엇으로 사람을 구분하나

알림을 만드는 일(여행 전날 배치)과 실제 푸시 발송은 이 PR 밖이다. 그래서 계약과 저장소를 먼저 확정하는 것이 이 작업의 전부다.

Action

응답 계약

엔드포인트 하는 일 응답 데이터
GET /api/v1/notifications 목록 + 안읽음 수 notifications[], unreadCount (+ 래퍼의 pageResponse)
PATCH /api/v1/notifications/{id}/read 하나 읽음 unreadCount
POST /api/v1/notifications/read-all 전체 읽음 unreadCount

알림 한 건은 id, type, courseId, read, createdAt 이다.

  • 읽음 응답에도 안읽음 수를 싣는다. 읽은 직후 앱이 고쳐야 하는 값이 그것이라, 안 주면 배지 하나 때문에 목록을 다시 부른다. 클라이언트가 떠안을 일을 서버가 미리 끝낸다.
  • 없는 알림과 남의 알림을 똑같이 404 로 답한다. 남의 것에 403 을 주면 "그 ID 는 존재한다" 를 알려주는 셈이라, ID 를 훑어 남의 알림 존재를 확인할 수 있다. 코스 상세가 이미 같은 규칙이다.
  • 이미 읽은 알림에 다시 보내도 성공이다. 사용자가 원한 상태가 이미 이뤄져 있고, 알림 화면은 같은 요청을 두 번 보내기 쉬운 자리다. 처음 읽은 시각은 덮어쓰지 않는다.
  • 안읽음 수는 페이지와 무관한 전체 수다. 페이지 안에서 세면 20개짜리 첫 페이지에서 배지가 20 에 멈춘다.

알림 종류를 하나만 정의했다

TRIP_TOMORROW(내일 여행) 하나다.

서버가 지금 실제로 만들 근거가 있는 종류가 그것뿐이다. 여행 날짜를 이미 들고 있어 판단에 외부가 필요 없고, 알림을 받는 시점이 사용자가 할 일(짐 싸기)과 맞는다.

쓰지도 않을 종류를 미리 나열하면 앱이 그 값을 기다리는 분기를 만들고, 영영 오지 않는 분기가 남는다. 값이 느는 것은 클라이언트에 안전한 변경이라 보낼 것이 생길 때 더한다. 추가 기준도 코드에 적어 뒀다: 서버가 그 사실을 이미 알고 있고, 그것을 알릴 주체가 이 레포에 있는가.

앱 쪽에 부탁: 모르는 type 은 무시하거나 기본 아이콘으로 그려 달라. 앱을 업데이트하지 않은 사용자에게도 새 종류가 갈 수 있다.

프론트 초안에서 문구 필드를 뺐다 (계약 변경)

초안에 있던 title·body 를 응답에서 뺐다. 프론트가 "type 에 아이콘·문구를 맞추겠다" 고 한 것을 그대로 계약에 반영한 것이다. 이 레포의 OpeningStatus 와 같은 방식으로, 응답에는 enum 이름만 싣는다.

서버가 문구를 소유 앱이 문구를 소유 (채택)
표현을 고칠 때 이미 쌓인 알림은 옛 문구로 남아 화면에 두 세대가 섞인다 다시 그리면 전부 새 문구
화면 폭·서체·강조 서버가 알 수 없다 앱이 쥐고 있다
푸시 발송(후속) 서버가 문구를 만들어 보낸다 data 메시지로 보내고 앱이 표시를 만든다
비용 "내일 정선으로 떠나요" 의 지역명을 앱이 채워야 한다

마지막 줄이 유일한 비용인데, 앱은 이미 그 값을 갖고 있다. 알림은 자기 코스를 가리키고(courseId), 내 코스 목록 응답에 지역명이 실려 있다. 서버가 알림마다 지역명을 다시 붙이면 알림 하나당 코스·지역을 되짚는 조회가 생긴다.

courseId 는 없을 수도 있고(코스와 무관한 알림), 지워진 코스를 가리킬 수도 있다. 알림은 코스가 사라져도 남는다.

저장

  • notification 테이블 하나. 소유 키는 코스·연차가 쓰는 X-Guest-Id 를 그대로 따랐다 — 소셜 로그인이 붙는 날 세 테이블을 함께 옮기는 것이 가장 안전하다.
  • 읽음을 boolean 이 아니라 시각(read_at)으로 뒀다. 저장 비용이 같은데 "언제 읽었나" 까지 답한다. NULL 이면 안 읽음이다.
  • 코스 참조는 도메인 경계를 넘으므로 raw ID 다. 코스가 지워져도 알림은 남아야 해서 FK 를 걸지 않았다(영속성 규약과 목적이 같은 방향).
  • 전체 읽음은 벌크 UPDATE 다. 행을 다 읽어 하나씩 고치면 쌓인 만큼 힙에 올린다.
  • 인덱스는 (guest_id, created_at) 하나. 목록은 소유자 안에서 최신순으로만 읽고, 안읽음 개수도 이 인덱스로 소유자 범위까지 좁힌 뒤 훑는다. 한 사람의 알림은 수백 건 규모라 인덱스를 더 두지 않았다.

목록을 페이지로 끊었다

알림은 지우지 않고 쌓이는 데이터라, 상한이 없으면 오래 쓴 사용자의 목록 한 번이 계속 커진다. 코스 목록이 실제로 그랬다(#105). page·size 를 규약대로 받고 기본 20·최대 100 은 Paging 이 단독으로 소유한다. 정렬 tie-break 에 id 를 넣어 같은 초에 만들어진 알림이 페이지 경계에서 겹치거나 빠지지 않게 했다.

검증

  • 남의 알림을 못 읽는 것을 테스트로 잠갔다. 404 를 준 뒤 조용히 읽음 처리해버리는 것이 최악이라, 404 응답만이 아니라 남의 알림이 그대로 안 읽음으로 남는지까지 확인한다. 전체 읽음도 남의 것을 건드리지 않는지 따로 본다.
  • 안읽음 수가 페이지 크기에 갇히지 않는지는 size=1 로 3건을 조회해 확인한다.
  • 응답 DTO 매핑 단위 테스트는 의도적으로 두지 않았다. readAtread 접기 말고는 옮겨 담기뿐이고, 그 접기는 도메인 단위 테스트와 통합 테스트의 read 단언이 양쪽에서 잡는다.

Result

  • 알림 화면과 홈 배지가 하드코딩을 걷어내고 이 API 를 부를 수 있다. 다만 지금은 목록이 비어 있다 — 알림을 만드는 주체가 아직 없기 때문이고, 그것이 아래 후속이다.
  • 후속으로 나눠 둔 것:
  • 작업을 마치기 전 자문 셋:
    • 운영에서 버티는가 — 테이블 하나, 인덱스 하나가 늘었다. 부팅 시 적재하는 것은 없다. 행은 사용자당 여행 수만큼만 늘고, 조회는 소유자 인덱스로 좁힌 뒤 페이지로 끊는다.
    • 외부 API 한도 — 해당 없음. 이 PR 은 외부를 부르지 않는다.
    • 코스의 완성도 — 해당 없음. 코스 생성 경로를 건드리지 않았다.

연관 이슈

- 앱 알림 화면이 하드코딩 데이터로 돌고 있어(홈 배지도 그 가짜 목록을 셌다) 조회·읽음을 서버에 만든다
- 알림 종류는 TRIP_TOMORROW 하나만 정의했다. 서버가 지금 실제로 만들 근거가 있는 것이 그것뿐이라,
  쓰지 않을 종류를 미리 나열하면 앱에 영영 오지 않는 분기가 남는다
- 프론트 초안의 title·body 를 응답에서 뺐다. 프론트가 "type 에 아이콘·문구를 맞추겠다" 고 한 것을
  계약에 반영한 것이다. 문구를 서버에 굳히면 표현을 고칠 때 이미 쌓인 알림이 옛 문구로 남아
  화면에 두 세대가 섞인다(OpeningStatus 와 같은 방식으로 enum 이름만 싣는다)
- 지역명을 대신 실어주는 안은 접었다. 알림 하나당 코스·지역을 되짚는 조회가 붙는데, 앱은 내 코스
  목록에서 이미 그 값을 갖고 있다
- 없는 알림과 남의 알림을 똑같이 404 로 답한다. 403 으로 나누면 ID 를 훑어 남의 알림 존재를
  확인할 수 있다(코스 상세와 같은 규칙)
- 이미 읽은 알림의 재요청은 성공으로 둔다. 원한 상태가 이미 이뤄져 있고, 알림 화면은 같은 요청을
  두 번 보내기 쉬운 자리다
- 읽음 응답에도 안읽음 수를 싣는다. 안 주면 배지 하나 때문에 목록을 다시 부른다
- 안읽음 수는 페이지와 무관한 전체 수다. 페이지 안에서 세면 첫 페이지 크기에서 배지가 멈춘다
- 알림은 쌓이는 데이터라 목록을 page·size 로 끊었다. 정렬 tie-break 에 id 를 넣어 같은 초에
  만들어진 알림이 페이지 경계에서 겹치거나 빠지지 않게 했다
- 전체 읽음은 벌크 UPDATE 다. 행을 다 읽어 하나씩 고치면 쌓인 만큼 힙에 올린다
- 읽음을 boolean 이 아니라 시각으로 저장한다. 비용이 같은데 "언제 읽었나" 까지 답한다
- 소유 키는 코스·연차와 같은 X-Guest-Id 다. 인증이 붙는 날 세 테이블을 함께 옮기는 것이 안전하다
- 남의 알림은 404 응답만 보지 않고, 그 알림이 그대로 안 읽음으로 남는지까지 단언한다.
  404 를 준 뒤 조용히 읽음 처리해버리는 것이 최악이라 그쪽을 잠근다
- 전체 읽음이 남의 알림을 건드리지 않는지 따로 본다
- 안읽음 수가 페이지 크기에 갇히지 않는지 size=1 로 3건을 조회해 확인한다
- 응답 DTO 매핑 단위 테스트는 의도적으로 두지 않았다. readAt → read 접기 말고는 옮겨 담기뿐이고,
  그 접기는 도메인 단위 테스트와 통합 테스트의 read 단언이 양쪽에서 잡는다.
  (처음엔 두려 했으나 영속 전 엔티티는 id 가 없어 응답 record 의 long id 를 채울 수 없었다)
- 통합 테스트에 클래스 레벨 @transactional 을 걸지 않는다. 컨벤션 훅이 controller 패키지의
  @transactional 을 막고, 다른 컨트롤러 통합 테스트도 소유자를 갈라 격리한다
@sevineleven sevineleven added the feat 새 기능 (외부에 보이는 변화) label Aug 13, 2026
@sevineleven sevineleven linked an issue Aug 13, 2026 that may be closed by this pull request
@sevineleven sevineleven self-assigned this Aug 13, 2026
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@sevineleven, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 78 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 26c823ab-ce3e-4315-abf5-58411599c43a

📥 Commits

Reviewing files that changed from the base of the PR and between d981645 and 99f2fbb.

📒 Files selected for processing (17)
  • src/main/java/com/offway/core/notification/controller/NotificationApi.java
  • src/main/java/com/offway/core/notification/controller/NotificationController.java
  • src/main/java/com/offway/core/notification/controller/dto/NotificationResponse.java
  • src/main/java/com/offway/core/notification/controller/dto/NotificationsResponse.java
  • src/main/java/com/offway/core/notification/controller/dto/UnreadCountResponse.java
  • src/main/java/com/offway/core/notification/domain/Notification.java
  • src/main/java/com/offway/core/notification/domain/NotificationErrorCode.java
  • src/main/java/com/offway/core/notification/domain/NotificationException.java
  • src/main/java/com/offway/core/notification/domain/NotificationType.java
  • src/main/java/com/offway/core/notification/repository/NotificationJpaRepository.java
  • src/main/java/com/offway/core/notification/repository/NotificationRepository.java
  • src/main/java/com/offway/core/notification/repository/NotificationRepositoryImpl.java
  • src/main/java/com/offway/core/notification/service/NotificationService.java
  • src/main/java/com/offway/core/notification/service/dto/MyNotifications.java
  • src/main/resources/db/migration/V20260814014306__create_notification.sql
  • src/test/java/com/offway/core/notification/controller/NotificationIntegrationTest.java
  • src/test/java/com/offway/core/notification/domain/NotificationTest.java

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.

- createdAt 이 같은 5건을 size=2 로 세 페이지 훑어, 이어 붙인 결과가 넣은 것과
  정확히 일치하는지(중복·누락 없음, 최신순) 확인한다
- 원래 의도는 PR 이 주장한 id 2차 정렬을 잠그는 것이었으나, 실측 결과 쿼리에서
  IdDesc 를 떼도 이 테스트는 통과한다. 인덱스가 (guest_id, created_at) 이고
  InnoDB 가 그 뒤에 PK 를 붙여, 인덱스 역순 스캔 플랜에서는 id 내림차순이 우연히
  따라오기 때문이다. 잘못된 초록을 남기지 않도록 그 사실을 테스트 주석에 적었다
- tie-break 를 지키는 것은 테스트가 아니라 쿼리의 ORDER BY 라는 점도 함께 남겼다
@sevineleven

Copy link
Copy Markdown
Contributor Author

리뷰 — 알림 목록·읽음 API (#263)

CodeRabbit 이 레이트리밋이라 대신 봤다. 주장 하나하나를 코드와 실행으로 확인했고, 확인 못 한 것은 그렇다고 적었다.

검증한 것

확인 항목 방법 결과
전체 테스트 ./gradlew cleanTest test (Testcontainers MySQL 8.4) 1344건 통과 · 실패 0 · skip 21(E2E)
컨벤션 훅 변경된 java·sql 전수 convention-check.sh 차단 0
origin/dev 와의 거리 git rev-list --left-right --count 0 behind — 벌어지지 않았다
요청당 쿼리 수 SPRING_JPA_SHOW_SQL=true 로 통합 테스트 실행 후 statement 계수 아래 ④
tie-break 가 테스트로 잠기는가 쿼리에서 IdDesc 를 떼고 재실행 안 잠긴다 — 아래 ②
남의 알림 404 가 자명한 통과인가 findByIdAndGuestId 경로 + 단언 내용 확인 자명하지 않다(아래)

좋았던 점 (근거를 확인한 것만)

  • 404 통일이 코드로도 성립한다. findOwnedfindByIdAndGuestId 라, 서비스가 "없음" 과 "남의 것" 을 구분할 정보를 애초에 받지 못한다. 호출부 실수로 403 이 새어 나갈 여지가 없는 구조다.
  • 남의 알림 404 테스트가 자명한 통과가 아니다. 404 응답만 보는 게 아니라 소유자 쪽에서 unreadCount 가 그대로 1 인지를 다시 확인한다 — "404 를 주고 뒤로는 읽음 처리" 라는 최악의 경우를 실제로 잡는 단언이다.
  • 이미 읽은 알림 재요청이 UPDATE 를 안 날린다. 실측으로 확인했다(select + count 2쿼리, update 없음). markRead 가 boolean 을 돌려주는 설계가 말로만이 아니라 쿼리로 나타난다.
  • read-all 뒤 재-count 의 근거가 실제로 성립한다. InnoDB REPEATABLE READ 에서 read view 는 UPDATE 가 아니라 그 뒤 첫 consistent read 가 만든다. 그래서 벌크 UPDATE 와 COUNT 사이에 커밋된 새 알림은 실제로 집계된다 — "항상 0 이니 세지 말자" 가 아니라 세는 쪽이 맞다.
  • 인덱스가 정렬을 그대로 준다. (guest_id, created_at) 에 InnoDB 가 PK 를 덧붙이므로 ORDER BY created_at DESC, id DESC 가 인덱스 역순 스캔으로 해결된다(filesort 없음).

① 응답에서 title·body 를 뺀 것 — 결정은 지지, 다만 앱이 그릴 수 있는지는 미결 (사람이 프론트와 합의)

문구를 서버에 굳히지 않는 이유("이미 쌓인 알림이 옛 문구로 남아 두 세대가 섞인다")는 타당하고 OpeningStatus 와 결이 같다. 고치지 않았다. 다만 "앱은 이미 그 값을 갖고 있다" 는 근거는 코드로 보면 네 군데서 새다.

  1. courseId 가 null 인 TRIP_TOMORROW 를 막는 것이 없다. Notification 빌더는 courseId 를 선택으로 두고, NotificationType 은 코스가 필요한지를 모른다. 지금 정의된 유일한 종류가 코스 없이 만들어질 수 있다 — 그러면 "내일 □□으로 떠나요" 의 □□ 를 채울 방법이 없다.
  2. 코스 목록은 페이지네이션이다(기본 20). 오래된 코스를 가리키는 알림의 지역명은 첫 페이지에 없다. 개별 조회(GET /api/v1/courses/{courseId})로 채우면 알림 N건에 호출 N번이고, 이건 CLAUDE.md 의 "클라이언트가 떠안을 일을 백엔드가 미리 끝내둔다" 와 반대 방향이다.
  3. 코스 삭제는 hard delete 다. 지워진 코스를 가리키는 알림은 상세도 404 라 지역명을 영영 못 얻는다. "알림은 코스가 사라져도 남는다" 는 의도와 정면으로 만난다.
  4. 지역명 자체가 nullable 이다. CourseSummaryResponse.regionName 이 "모르면 null" 이다.

정리하면 선택지는 셋이다. 어느 쪽이든 프론트와 합의할 항목이라 코드는 건드리지 않았다.

내용 대가
A. 현행 유지 앱이 courseId 로 지역명을 찾는다 위 1~4 각각에 앱 쪽 fallback 문구가 필요 — "여행이 내일이에요" 같은 지역명 없는 문구를 프론트가 준비해야 한다
B. 지역명만 응답에 싣는다 regionName(또는 regionId) 을 알림에 함께 문구가 아니라 데이터라 옛 문구 문제가 없다. 대신 알림 생성 시점의 지역명이 굳는다(지역명은 사실상 안 바뀐다). 알림 만들 때 한 번 채우면 조회 시 추가 조회는 없다
C. 도메인이 불변식으로 강제 TRIP_TOMORROWcourseId 필수 위 1번만 막는다. 2~4 는 그대로 남는다

의견을 붙이면 B + C 가 비용 대비 가장 깨끗해 보인다(생성 시점에 한 번 채우는 것이라 조회 경로에 조회가 늘지 않는다). 다만 "type 만 주면 앱이 맞추겠다" 는 합의가 이미 있으니, 프론트가 fallback 문구를 준비할 수 있다면 A 도 성립한다. 결정은 사람이 한다.

② 페이지 경계 tie-break — 테스트로 잠겨 있지 않았다 (minor, 테스트 추가함)

PR 본문은 "정렬 tie-break 에 id 를 넣어 같은 초에 만들어진 알림이 페이지 경계에서 겹치거나 빠지지 않게 했다" 고 하는데, 그걸 확인하는 테스트가 없었다. size=1 테스트는 createdAt 이 10분씩 벌어져 있어 tie 상황이 아니다.

같은 createdAt 5건을 size=2 로 세 페이지 훑는 테스트를 추가했다(f3cc5d4). 그런데 이 테스트도 tie-break 를 잠그지는 못한다 — 쿼리에서 IdDesc 를 떼고 돌려봤더니 그대로 통과한다. 위에 적은 대로 인덱스가 (guest_id, created_at + PK) 라, 지금 플랜에서는 id 내림차순이 우연히 따라오기 때문이다.

그래서 사실은 이렇다: tie-break 를 지키는 것은 테스트가 아니라 쿼리의 ORDER BY 자체이고, 인덱스나 플랜이 바뀌면 조용히 깨질 수 있는 자리다. 잘못된 초록을 남기지 않으려고 그 한계를 테스트 주석에 그대로 적어 뒀다. 추가한 테스트는 페이지네이션 계약(최신순·중복·누락) 회귀용으로 남긴다.

unreadCount 비용 — 실측했고, 문제 없다 (info)

SPRING_JPA_SHOW_SQL 로 실제 statement 를 셌다.

요청 쿼리 비고
목록 (결과가 첫 페이지에 다 들어감) 2 — content + unreadCount Spring Data 가 count 쿼리를 생략한다. 알림 20건 미만인 대다수 사용자가 여기 해당
목록 (페이지가 갈림) 3 — content + count(*) + unreadCount
하나 읽음 (처음) 3 — select + update + count
하나 읽음 (이미 읽음) 2 — select + count update 안 나감
전체 읽음 2 — bulk update + count

"read-all 뒤에는 항상 0 이라 셀 필요 없다" 는 성립하지 않는다(위 REPEATABLE READ 근거). 늘어난 것은 요청당 인덱스 count 한 번이고, 그 대가로 앱이 목록을 다시 부르지 않는다 — 계산이 맞다. 지적 없음.

④ 그 외 (nit — 고치지 않았다)

  • PATCH /{id}/readPOST /read-all 의 동사가 갈린다. api-convention 은 액션에 POST /{id}/action 을 든다. 같은 도메인의 같은 동작인데 하나는 PATCH, 하나는 POST 다. 이미 프론트와 합의된 계약이면 그대로 두는 게 낫고, 아직이면 지금이 맞추기 쉬운 때다.
  • Notification.course() 를 아무도 안 쓴다. 응답 DTO 는 getCourseId() 를 직접 읽고, course() 는 단위 테스트만 호출한다. 위 ①-C 를 택하면 이 자리가 쓸모를 얻는다.
  • markReadrepository.save(...) 는 같은 트랜잭션 안 managed 엔티티라 dirty checking 으로도 반영된다. 동작에 문제는 없고, port 를 명시적으로 거치는 편이 읽기 좋다는 판단이면 그대로 둬도 된다.

고친 것

  • f3cc5d4 test: 알림 목록의 페이지 경계 계약 검증 추가 — 위 ②. 푸시했고, 전체 테스트 1344건 통과를 다시 확인했다.

머지는 하지 않았다.

@sevineleven

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@sevineleven

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@sevineleven

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 새 기능 (외부에 보이는 변화)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] notification — 알림 목록·읽음 처리 API

1 participant