feat(user): 알림센터 알림 탭 대응 — event·연관 정보 노출, 3개월 필터, 커서 전환 - #267
Conversation
figma 알림센터(알림 탭) 화면 대응. 알림 항목이 이벤트 라벨(주문확정/제작완료/
픽업완료/리뷰 좋아요)과 "[매장] '상품'…" 서브라인, 딥링크를 구성할 수 있도록
기존 범용 myNotifications API를 확장한다. 하단 안내 문구("최근 3개월 내의
알림만 확인할 수 있어요")에 맞춰 노출 범위도 서버가 강제한다.
변경점
- SDL: NotificationItem에 event·orderId·storeId·productId·reviewId·
storeName·productName 추가, NotificationEvent enum 노출
- 페이지네이션: offset → 키셋 커서("<createdAtMs>:<id>", created_at·id desc)
전환. FE 실사용 전이라 breaking 전환을 지금 수행(레포 컨벤션 정합)
- 3개월 노출 필터: myNotifications 목록·totalCount와
viewerCounts.unreadNotificationCount에 created_at >= now-3개월 공통 적용
(삭제 아님 — 조회 필터만, 사용자 확정 정책)
- 문구 상수를 figma 톤으로 갱신("주문이 확정되었어요." 등). 주문번호 prefix는
식별 필요 가능성에 대비해 유지(사용자 확정 — 표시 여부는 FE 판단)
- 주문 상태 알림 생성 시 store_id·product_id 저장(신규 row부터). 과거 row는
조회 시 order.items 폴백으로 매장·상품 정보 보강, 상품명은 주문 시점 스냅샷
- 시드: 4종 이벤트 + 3개월 경과 알림으로 재구성
회귀 테스트
- service spec 12케이스(커서 연속 조회·타이브레이크, 잘못된 커서 거절,
3개월 필터, 직접 연결·주문 폴백 매핑, 배지 수 일치)
- 매퍼 helper 순수 단위 4케이스, input spec 6케이스, resolver 통합 2케이스,
order.repository spec에 연관 ID 저장 검증 추가
🩺 NestJS Doctor — 90/100 (Excellent)진단 288건 (error 0).
architecture / security 상위 항목
|
🧹 knip — dead-code 리포트전체 리포트
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b435de9a02
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const productName = | ||
| row.product?.name ?? orderItem?.product_name_snapshot ?? null; |
There was a problem hiding this comment.
Prefer the order snapshot for order-notification product names
When a product is renamed after checkout, new order-status notifications expose the renamed product rather than the name captured by the order. updateOrderStatusBySeller now stores product_id on every such notification, so row.product?.name always wins over product_name_snapshot here; this contradicts the GraphQL contract that order notifications use the order-time snapshot. Select the snapshot whenever the notification is backed by an order, while retaining the live product name for directly linked review notifications.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
반영: a48c907 — 주문 연결 알림은 product 직접 연결이 있어도 product_name_snapshot 우선으로 수정. 주문 없는 알림(리뷰 좋아요)만 현재 상품명 사용. 매퍼 spec에 동시 존재 케이스 추가.
Coverage report
Test suite run success1800 tests passing in 210 suites. Report generated by 🧪jest coverage report action from df00c03 |
Codex 지적 반영: 주문 상태 알림에 product_id를 저장하면서 현재 상품명이 주문 시점 스냅샷을 덮어쓰게 된 문제. 주문 연결 알림은 직접 연결(product)이 있어도 order.items의 product_name_snapshot을 우선해 SDL 계약(주문 시점 스냅샷)을 지킨다. 주문이 없는 알림(리뷰 좋아요)만 현재 상품명을 쓴다. - 매퍼 단위 spec에 "주문 연결 + 직접 연결 동시 존재 → 스냅샷 우선" 케이스 추가
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a48c9077b7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const createdAtMs = Number(match[1]); | ||
| if (!Number.isSafeInteger(createdAtMs)) { | ||
| throw new BadRequestException(USER_NOTIFICATION_ERRORS.INVALID_CURSOR); | ||
| } | ||
| return { createdAt: new Date(createdAtMs), id: BigInt(match[2]) }; |
There was a problem hiding this comment.
Reject cursor timestamps outside the Date range
A cursor such as 9000000000000000:1 passes Number.isSafeInteger, but JavaScript converts that timestamp to an Invalid Date because it exceeds the supported Date range. The invalid value then reaches the Prisma created_at filters and produces a Prisma validation/internal GraphQL error instead of the declared Invalid notification cursor bad-request response. Validate the constructed date with Number.isNaN(date.getTime()) before returning it.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
반영: Date 변환 후 getTime() NaN 검사 추가로 BadRequest 거부. spec에 9000000000000000:1 케이스 추가.
Codex 지적 반영: 안전 정수라도 Date 지원 범위(±8.64e15ms)를 넘는 커서 timestamp는 Invalid Date가 되어 Prisma 필터에서 내부 오류로 번진다. new Date 변환 후 getTime() NaN 검사로 형식 오류(BadRequest)로 선제 거부. - 커서 거절 spec에 "9000000000000000:1" 케이스 추가
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 05bcf0138b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (Number.isNaN(createdAt.getTime())) { | ||
| throw new BadRequestException(USER_NOTIFICATION_ERRORS.INVALID_CURSOR); | ||
| } | ||
| return { createdAt, id: BigInt(match[2]) }; |
There was a problem hiding this comment.
Reject cursor IDs outside the database range
A cursor such as 1700000000000:999999999999999999999999999999 passes the regex and is converted by BigInt without any bounds check, but the value cannot be represented by the database BigInt/UnsignedBigInt query parameter used in the id < cursor.id filter. This causes a Prisma connector/value-out-of-range error instead of the declared Invalid notification cursor bad-request response. Validate the ID against the supported database range before returning the parsed cursor.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
반영: 커서 id를 UNSIGNED BIGINT 상한(2^64-1)과 비교해 초과 시 BadRequest 거부. spec 케이스 추가.
Codex 지적 반영: 커서 id 세그먼트가 DB UNSIGNED BIGINT 상한(2^64-1)을 넘으면 Prisma 커넥터 범위 오류로 번진다. 파싱 시 상한 초과를 형식 오류(BadRequest)로 선제 거부한다. - MAX_UNSIGNED_BIGINT 상수 추가(user.constants), 커서 거절 spec 케이스 추가
배경
figma 알림센터(알림 탭) 화면 대응 1/4. 기존
myNotifications는 초기 스캐폴드 산물이라 화면이 요구하는 이벤트 라벨·서브라인·딥링크 정보가 없었다.변경점
NotificationItem에event(NotificationEvent enum 노출)·orderId·storeId·productId·reviewId·storeName·productName추가. 서브라인 조립("[매장] '상품'…")은 FE 담당."<createdAtMs>:<id>"커서(created_at·id desc, id 타이브레이크). FE 실사용 전이라 breaking 전환을 지금 수행.viewerCounts.unreadNotificationCount에created_at ≥ now-3개월공통 적용(삭제 아님).store_id·product_id저장. 과거 row는 조회 시order.items폴백으로 보강(상품명은 주문 시점 스냅샷).테스트
yarn validate전체 통과 (210 suites / 1799 tests)