Skip to content

feat(conversation): 알림센터 대화 탭 — 대화 목록·채팅 상세 조회·읽음 처리 - #269

Merged
chanwoo7 merged 7 commits into
developfrom
feat/conversation-buyer-list-detail
Sep 1, 2026
Merged

feat(conversation): 알림센터 대화 탭 — 대화 목록·채팅 상세 조회·읽음 처리#269
chanwoo7 merged 7 commits into
developfrom
feat/conversation-buyer-list-detail

Conversation

@chanwoo7

@chanwoo7 chanwoo7 commented Sep 1, 2026

Copy link
Copy Markdown
Member

배경

figma 알림센터(대화 탭) 화면 대응 3/4. PR #268의 대화 기반 위에 구매자 조회 경로를 얹는다.

변경점

  • myConversations: 마지막 메시지 최신순 키셋 커서 목록 — 매장 프로필·마지막 메시지 미리보기(HTML은 태그 제거 plain text)·안읽은 수신 메시지 수(unreadCount, 목록의 "(3)" 표기용). 메시지 없는 대화는 제외.
  • conversationMessages: 본인 대화 검증 후 최신순(id desc) 키셋 커서 메시지 목록. 조회 시 last_read_at 자동 갱신 — 별도 읽음 mutation 없는 정책(사용자 확정), 의도적 쓰기 부수효과로 주석 명시.
  • 커서 유틸 공용화: "<timestampMs>:<id>" 파싱·조립을 common/utils/keyset-cursor로 이동(형식·안전 정수·Date 범위·UNSIGNED BIGINT 상한 방어 일원화). user 알림 커서도 동일 유틸로 리팩토링.
  • ConversationBaseService: 활성 USER 판정 공유(inquiry/center 공통).

테스트

  • center service 6케이스(정렬·미리보기·unreadCount·본인 메시지 제외·커서·빈 대화 제외·읽음 부수효과·소유권)
  • 커서 유틸 5케이스 / 미리보기 매퍼 4케이스 / resolver 통합 1케이스 / input 6케이스

figma 알림센터(대화 탭) 화면 대응 3/4.

변경점
- Query myConversations: 마지막 메시지 최신순 키셋 커서 목록 — 매장
  프로필·마지막 메시지 미리보기(HTML은 태그 제거 plain text)·안읽은 수신
  메시지 수(unreadCount, "(3)" 표기용). 메시지 없는 대화는 제외
- Query conversationMessages: 본인 대화 검증 후 최신순(id desc) 키셋 커서
  메시지 목록. 조회 시 last_read_at 자동 갱신(별도 mutation 없는 읽음 처리,
  사용자 확정 정책 — 의도적 쓰기 부수효과로 주석 명시)
- "<timestampMs>:<id>" 커서 파싱·조립을 common/utils/keyset-cursor로 공용화
  (형식·안전 정수·Date 범위·UNSIGNED BIGINT 상한 방어 일원화) —
  user 알림 커서도 동일 유틸로 리팩토링
- ConversationBaseService로 활성 USER 판정 공유(inquiry/center 공통)

회귀 테스트
- center service 6케이스(정렬·미리보기·unreadCount·본인 메시지 제외·커서·
  빈 대화 제외·읽음 부수효과·소유권), 커서 유틸 단위 5케이스,
  미리보기 매퍼 4케이스, resolver 통합 1케이스, input spec 6케이스
@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: f2747b8f-af2b-4484-9448-4fe595029657

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

🧹 knip — dead-code 리포트

Duplicate exports (1)
전체 리포트
Duplicate exports (1)
KEYWORD_RANK_SNAPSHOT_SIZE|MAX_POPULAR_KEYWORDS_LIMIT  src/features/search/constants/search.constants.ts

청소 후보(오탐 가능) · 기준 docs/guide/architecture-conventions.md

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

🩺 NestJS Doctor — 90/100 (Excellent)

진단 298건 (error 0).

Category error warning info
architecture 0 0 9
correctness 0 150 0
performance 0 24 16
schema 0 0 86
security 0 13 0
architecture / security 상위 항목
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal type 'IAuditLogRepository'.
  • warning security/security/no-exposed-env-vars: Direct 'process.env.NODE_ENV' access in 'AuthController'. Use ConfigService instead.
  • warning security/security/require-guards-on-endpoints: Endpoint 'start' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'callback' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'refresh' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'logout' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'sellerLogin' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'sellerRefresh' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'sellerLogout' has no @UseGuards() at class or method level.
  • warning security/security/require-guards-on-endpoints: Endpoint 'devIssueToken' has no @UseGuards() at class or method level.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal module '@/features/conversation/repositories/conversation.repository'.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal type 'ConversationRepository'.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal module '@/features/order/repositories/order.repository'.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal type 'OrderRepository'.
  • info architecture/architecture/no-barrel-export-internals: Barrel file re-exports internal module '@/features/product/repositories/product.repository'.

오탐 포함 가능 · 기준 docs/guide/architecture-conventions.md

Comment on lines +9 to +12
return html
.replace(/<[^>]*>/g, ' ')
.replace(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&')
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

Coverage report

St.
Category Percentage Covered / Total
🟢 Statements 97.86% 5775/5901
🟢 Branches 93.72% 2285/2438
🟢 Functions 96.76% 1136/1174
🟢 Lines 98.43% 5254/5338

Test suite run success

1855 tests passing in 221 suites.

Report generated by 🧪jest coverage report action from 628d942

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0d87e118e4

ℹ️ 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".


// 채팅 상세 진입/조회 = 읽음으로 간주 — 별도 mutation 없이 여기서
// last_read_at을 갱신한다(조회의 의도적 쓰기 부수효과, 사용자 확정 정책).
await this.repo.markConversationRead({ conversationId, now: new Date() });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Advance the read marker only through messages actually fetched

If a store message commits after listConversationMessages returns but before this update runs, that message is absent from the response yet its created_at precedes the new last_read_at; the unread query's strict created_at > last_read_at predicate will then permanently treat it as read. Update the marker atomically from the fetched message high-water mark, rather than from wall-clock time after the read.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

반영: 마커를 응답에 내려준 최신 메시지 created_at까지만 단조 전진(updateMany + lt 조건). 과거 페이지 후퇴 방지 spec 추가. ms 동률 엣지는 다음 조회에 포함되므로 허용(주석).

}

const limit = input?.limit ?? DEFAULT_CONVERSATION_MESSAGES_LIMIT;
const cursor = input?.cursor ? parseId(input.cursor) : undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject message cursors outside the unsigned BIGINT range

When input.cursor exceeds 2^64-1, parseId still accepts it because it only rejects negative values, and the resulting bigint is passed into Prisma's filter for an unsigned BIGINT column. Such a client-supplied cursor therefore reaches the connector as an out-of-range value and produces an internal database error instead of the expected bad-request response; apply the same upper-bound validation introduced for timestamp/id cursors.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

반영: parseIdCursor(상한 2^64-1 검증) 공용 유틸 추가 후 메시지 커서에 적용. spec 추가.

Comment on lines +184 to +186
return Promise.all(
rows.map(async (row) => {
const [lastMessage, unreadCount] = await Promise.all([

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Batch conversation-list extras instead of issuing 100 queries

At the allowed 50-item page size, this nested Promise.all issues two independent database queries per conversation, so one list request generates 100 extra queries concurrently in addition to the base queries. Under concurrent notification-center traffic this can saturate the connection pool and substantially increase latency; fetch the latest messages and unread aggregates in batched queries instead.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

반영: 최신 메시지 id groupBy → 본문 in 조회 → 안읽음 OR-분기 groupBy 고정 3쿼리로 배치(페이지 크기와 무관).

@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codex 지적 3건 반영.

- P1: last_read_at을 벽시계가 아니라 "실제 내려준 최신 메시지 created_at"
  까지만 전진 — 조회 후 커밋된(응답에 없는) 메시지가 영구 읽음 처리되는
  레이스 방지. 과거 페이지 조회로 마커가 후퇴하지 않도록 단조 증가 조건.
  ms 동률 메시지는 다음 조회에 함께 내려가므로 허용(주석 명시)
- P2: 메시지 커서에 UNSIGNED BIGINT 상한 검증(parseIdCursor 공용 유틸
  추가) — parseId는 음수만 걸러 커넥터 범위 오류로 번지던 문제
- P2: 목록 부가 정보(마지막 메시지·안읽음 수)를 per-row 2N 쿼리에서
  고정 3쿼리(최신 id 집계 → 본문 일괄 → 안읽음 OR-분기 groupBy)로 배치

- 회귀 spec: 마커 후퇴 방지·커서 상한 거절·parseIdCursor 단위 케이스 추가

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ac6b487ad5

ℹ️ 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".

OR: rows.map((row) => ({
conversation_id: row.id,
...(row.last_read_at
? { created_at: { gt: row.last_read_at } }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use a commit-ordered read watermark

Fresh evidence after the prior fix: when a store-send request captures its created_at before this read but its transaction commits afterward, another fetched message can have a later timestamp and advance last_read_at past the still-invisible message. After that delayed transaction commits, this strict created_at > last_read_at predicate permanently excludes the unfetched message from unreadCount; synchronizing reads with sends or tracking a watermark that reflects messages actually observed is required.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

반영: 전송(구매자/판매자) 시각 채번을 대화 잠금 획득 이후로 옮기고, 읽기도 같은 잠금을 잡는 listBuyerMessagesAndMarkRead 단일 트랜잭션으로 재구성 — 대화 단위로 잠금 순서=시각 순서=커밋 순서가 되어 미가시 메시지를 건너뛰는 마커 전진이 불가능해짐(NTP 전제 주석).

input?: ConversationMessagesInput,
): Promise<ConversationMessageConnection> {
await this.requireActiveUser(accountId);
const conversationId = parseId(conversationIdRaw);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Bound the conversation ID before querying

When conversationIdRaw exceeds 2^64-1, parseId accepts it because it only rejects negative values, and findConversationByIdAndAccount then sends the out-of-range bigint to an unsigned BIGINT column. This new query therefore returns an internal connector error for a client-controlled ID instead of a bad-request or not-found response; apply the same unsigned upper-bound validation already used for the message cursor.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

반영: 공용 parseId에 UNSIGNED BIGINT 상한 검증 추가(클라이언트 ID 입력 전반 커버). spec 케이스 추가.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ac6b487ad5

ℹ️ 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".

OR: rows.map((row) => ({
conversation_id: row.id,
...(row.last_read_at
? { created_at: { gt: row.last_read_at } }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use a commit-ordered read watermark

Fresh evidence after the prior fix: when a store-send request captures its created_at before this read but its transaction commits afterward, another fetched message can have a later timestamp and advance last_read_at past the still-invisible message. After that delayed transaction commits, this strict created_at > last_read_at predicate permanently excludes the unfetched message from unreadCount; synchronizing reads with sends or tracking a watermark that reflects messages actually observed is required.

Useful? React with 👍 / 👎.

input?: ConversationMessagesInput,
): Promise<ConversationMessageConnection> {
await this.requireActiveUser(accountId);
const conversationId = parseId(conversationIdRaw);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Bound the conversation ID before querying

When conversationIdRaw exceeds 2^64-1, parseId accepts it because it only rejects negative values, and findConversationByIdAndAccount then sends the out-of-range bigint to an unsigned BIGINT column. This new query therefore returns an internal connector error for a client-controlled ID instead of a bad-request or not-found response; apply the same unsigned upper-bound validation already used for the message cursor.

Useful? React with 👍 / 👎.

Codex 지적 2건 반영.

- P1: 시각 채번이 잠금 밖이라, 먼저 채번되고 늦게 커밋된 메시지를 마커가
  건너뛰는 레이스가 남아 있었다. 세 경로를 대화 row 잠금으로 정렬:
  · 전송(구매자/판매자): 잠금 획득 "이후"에 created_at 채번 → 대화 단위로
    잠금 순서 = 시각 순서 = 커밋 순서(NTP 전제)
  · 읽기: listBuyerMessagesAndMarkRead 한 트랜잭션에서 같은 잠금을 잡아
    미커밋 전송을 기다린 뒤 조회·마커 전진(단조 증가 조건 유지)
  now 파라미터는 repository 내부 채번으로 대체(전송 경로 시그니처 정리)
- P2: 공용 parseId에 UNSIGNED BIGINT 상한 검증 추가 — conversationId 등
  클라이언트 ID 입력 전반에서 커넥터 범위 오류가 형식 오류로 바뀐다
  (id-parser spec 케이스 추가)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e0d5d6472d

ℹ️ 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".

this.prisma.storeConversationMessage.groupBy({
by: ['conversation_id'],
where: {
sender_type: { not: ConversationSenderType.USER },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Mark mutation-delivered store messages as read

When a buyer starts a conversation or taps an FAQ chip, the mutation payload returns the generated greeting and/or FAQ answer for immediate display, but createBuyerMessages leaves last_read_at null or unchanged. This predicate therefore counts those just-delivered STORE messages as unread, so myConversations can show an unread badge immediately after the buyer receives the mutation response; advance the read marker for the automated messages delivered by the buyer mutation, or otherwise prevent them from entering the unread aggregate.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

반영: 구매자 전송 트랜잭션에서 last_read_at을 배치 시각까지 전진 — mutation 응답으로 즉시 표시된 인사말/FAQ 자동응답은 읽음 처리, 이후 판매자 메시지만 미읽음. spec 추가.

Codex 지적 반영: 인사말·FAQ 자동응답은 mutation 응답으로 구매자 화면에
즉시 표시되는데 last_read_at이 그대로라 목록 미읽음 배지에 잡혔다.
전송 트랜잭션의 대화 갱신에서 last_read_at을 이번 배치 시각까지 전진 —
이후 도착하는 판매자 메시지만 미읽음으로 남는다.

- 전송 후 last_read_at == last_message_at 검증 spec 추가

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c641b39f63

ℹ️ 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".

// 표시되므로, 여기까지를 읽음으로 전진시킨다 — 방금 받은 자동응답이
// 목록 미읽음 배지로 잡히는 불일치 방지(리뷰 반영). 항상 최신
// 시각이라 단조 증가 조건이 필요 없다.
last_read_at: now,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve unread replies when sending a buyer message

When an existing conversation has unread STORE messages, calling sendConversationMessage advances last_read_at to now even though its payload contains only the newly created USER message. The unread query subsequently excludes every earlier store reply via created_at > last_read_at, so sending without first loading the history permanently clears messages the buyer was never shown; only advance the marker for messages actually delivered by the mutation without swallowing pre-existing unread replies.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

반영: 전송 트랜잭션에서 이전 미읽음 수신 메시지를 세어 0건일 때만 last_read_at 전진. 백로그가 있으면 마커 유지(자동응답이 잠시 미읽음에 포함되는 쪽을 감수 — 상세 진입 시 해소). 보존 spec 추가.

Codex 지적 반영: 직전 수정이 기존 대화의 미읽음 판매자 답장까지 전송
시점에 읽음 처리해 버렸다(단일 워터마크 특성). 전송 트랜잭션에서 "이번
전송 이전" 미읽음 수신 메시지를 세어 0건일 때만 마커를 전진 — 백로그가
있으면 유지해 안 본 답장이 사라지지 않는다(방금 받은 자동응답이 잠시
미읽음에 포함되는 쪽을 감수, 채팅 상세 진입 시 함께 해소).

- 백로그 보존 spec 추가

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 07714f0f8b

ℹ️ 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".

Comment on lines +33 to +35
const timestamp = new Date(timestampMs);
if (Number.isNaN(timestamp.getTime())) {
throw new BadRequestException(errorMessage);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject cursors beyond the MySQL DATETIME range

A cursor such as 253402300800000:1 (year 10000) passes both the safe-integer and JavaScript Date checks, but myConversations then uses it against a MySQL DateTime(3) column, whose maximum year is 9999. Prisma/MySQL therefore raises an internal date-conversion or connector error instead of the intended invalid-cursor response; validate the timestamp against the database column's supported range before returning it.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

반영: parseTimestampIdCursor에 MySQL DATETIME 상한(9999-12-31T23:59:59.999Z) 검증 추가, 경계 spec 포함. 라운드 상한에 도달해 이후에는 필수 체크 pass 시 머지 진행 예정.

Codex 지적 반영: JS Date는 ±275760년까지 허용해 연도 10000 같은 값이
검증을 통과한 뒤 MySQL DATETIME(3) 변환에서 커넥터 오류로 번진다.
parseTimestampIdCursor에 9999-12-31 23:59:59.999 UTC 상한 추가.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 490dba99c2

ℹ️ 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".

where: { id: conversationId, deleted_at: undefined },
select: { last_read_at: true },
});
const pendingUnread = await tx.storeConversationMessage.count({

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Take the unread count from a post-lock snapshot

Fresh evidence after the previous locking fix: under MySQL's default REPEATABLE READ isolation, the earlier findFirst in lockOrCreateConversation can establish a snapshot before lockExisting() waits for an in-flight seller send. After that seller transaction commits, this count still reads the old snapshot and misses the new STORE message, so pendingUnread is zero and the buyer send advances last_read_at past a reply the buyer never saw, permanently removing it from unreadCount. Acquire the lock before any consistent read, use a locking/current read for this check, or otherwise ensure the count observes the post-lock state.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

반영: 인사말 판정·pendingUnread를 FOR SHARE 잠금 조회로, last_read_at은 FOR UPDATE 잠금 조회 결과로 수령 — 스냅샷이 아닌 최신 커밋 기준. 라운드 상한 초과로 이후에는 CI pass 시 머지 진행.

Codex 지적 반영: 트랜잭션 초입의 일반 조회가 만든 REPEATABLE READ
스냅샷 때문에, 잠금 대기 중 커밋된 판매자 답장을 pendingUnread가 못 보고
마커가 그 답장을 지나칠 수 있었다.

- 인사말 판정(메시지 수)·미읽음 판정을 FOR SHARE 잠금 조회로 전환 —
  잠금 조회는 최신 커밋을 읽는다. raw라 deleted_at IS NULL 수동 명시
- last_read_at은 lockOrCreateConversation의 FOR UPDATE 결과에서 수령
  (동일 이유로 스냅샷이 아닌 최신 값)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 628d942e3e

ℹ️ 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".

OR: rows.map((row) => ({
conversation_id: row.id,
...(row.last_read_at
? { created_at: { gt: row.last_read_at } }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Add an ID tie-breaker to the read watermark

Fresh evidence after the prior watermark fix: when a seller send obtains the conversation lock in the same millisecond as the message represented by last_read_at, both values are stored at DateTime(3) precision, so this strict created_at > last_read_at predicate permanently excludes the new STORE message from unreadCount. The lock makes timestamps nondecreasing, not strictly increasing; persist an ID tie-breaker with the watermark or otherwise handle equal timestamps instead of relying on gt.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

미반영(한도 내 수용): DateTime(3) 동일 밀리초 경계의 배지 누락 엣지로, 메시지 자체는 채팅 이력에 정상 노출되고 상세 진입 시 해소됨. 완전 해소는 last_read_message_id 컬럼 신설(스키마 확장)이 필요해 본 PR 범위와 비례하지 않음 — 필요해지면 별도 이슈로. 코드 주석에 한계 명시됨. 라운드 상한 도달로 머지 진행.

@chanwoo7
chanwoo7 merged commit ff07964 into develop Sep 1, 2026
10 of 11 checks passed
@chanwoo7
chanwoo7 deleted the feat/conversation-buyer-list-detail branch September 1, 2026 20:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants