Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/features/conversation/conversation-inquiry.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ type ConversationMessage {
conversationId: ID!
"""발신 주체. 말풍선 좌우 배치 기준이다."""
senderType: ConversationSenderType!
"""본문 형식. 이 값에 따라 bodyText·bodyHtml 중 하나가 채워진다."""
"""본문 형식. 읽어야 할 본문 필드를 가리킨다."""
bodyFormat: ConversationBodyFormat!
"""평문 본문. bodyFormat이 TEXT일 때 채워진다."""
"""평문 본문. bodyFormat이 TEXT면 이 값을 쓴다."""
bodyText: String
"""서식 본문. bodyFormat이 HTML일 때 채워진다."""
"""서식 본문. bodyFormat이 HTML이면 이 값을 쓴다."""
bodyHtml: String
"""발송 시각. 메시지 스트림 정렬은 id 기준이다."""
createdAt: DateTime!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,33 @@ describe('ConversationRepository (real DB)', () => {
expect(rows.map((r) => r.id)).toEqual([older.id]);
});

it('updated_at이 같으면 id 내림차순으로 이어서 끊는다', async () => {
// 커서의 보조 키 분기(updated_at 동률 → id < cursor.id)를 타는 케이스.
// 서로 다른 updated_at만 쓰면 이 분기가 한 번도 실행되지 않는다.
const store = await createStore(prisma);
const sameTime = new Date('2026-09-05T00:00:00Z');
const make = async () => {
const customer = await createAccount(prisma, { account_type: 'USER' });
return prisma.storeConversation.create({
data: {
account_id: customer.id,
store_id: store.id,
updated_at: sameTime,
},
});
};
const first = await make();
const second = await make();
expect(second.id > first.id).toBe(true);

const rows = await repo.listConversationsByStore({
storeId: store.id,
limit: 10,
cursor: { updatedAt: sameTime, id: second.id },
});
expect(rows.map((r) => r.id)).toEqual([first.id]);
});

it('id가 더 큰 오래된 대화도 커서 페이지에서 빠지지 않는다', async () => {
// 정렬은 updated_at desc인데 커서가 id 단독이면 `id < cursor`가 정렬과
// 무관한 행을 잘라내, id가 큰 오래된 대화가 목록에서 영영 빠졌다.
Expand Down
17 changes: 12 additions & 5 deletions src/features/seller/seller-conversation.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,26 @@ type SellerConversationConnection {
nextCursor: String
}

"""대화 메시지 1건. bodyFormat에 따라 bodyText 또는 bodyHtml 하나만 채워진다."""
"""
대화 메시지 1건. bodyFormat이 읽어야 할 본문 필드를 가리킨다.
발송 시 다른 형식의 본문을 함께 넘기면 그대로 저장되므로, 두 필드가 모두 채워져
있을 수 있다 — 표시할 본문은 bodyFormat으로 고른다.
Comment on lines +52 to +54

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 Correct the remaining exclusive-body description

The updated type description correctly states that both body fields may be stored, but SellerConversationMessage.bodyFormat immediately below still says that only one of bodyText or bodyHtml is populated. When sellerSendConversationMessage receives both fields, the service persists both, so GraphQL introspection remains internally contradictory and can still cause seller clients to assume the non-selected field is null; update that field description as part of this contract correction.

Useful? React with 👍 / 👎.

"""
type SellerConversationMessage {
id: ID!
conversationId: ID!
"""발신 주체. 말풍선 좌우 배치 기준이다."""
senderType: ConversationSenderType!
"""보낸 계정 ID. senderType이 SYSTEM이면 null이다."""
"""
보낸 계정 ID. 사람이 보낸 메시지에만 채워진다.
시스템 메시지는 물론, 인사말·FAQ 자동응답처럼 서버가 만든 STORE 메시지도 null이다.
"""
senderAccountId: ID
"""본문 형식. 이 값에 따라 bodyText·bodyHtml 중 하나가 채워진다."""
bodyFormat: ConversationBodyFormat!
"""평문 본문. bodyFormat이 TEXT일 때 채워진다."""
"""평문 본문. bodyFormat이 TEXT면 이 값을 쓴다."""
bodyText: String
"""서식 본문. bodyFormat이 HTML일 때 채워진다."""
"""서식 본문. bodyFormat이 HTML이면 이 값을 쓴다."""
bodyHtml: String
"""발송 시각."""
createdAt: DateTime!
Expand All @@ -84,7 +91,7 @@ type SellerConversationMessageConnection {
"""
input SellerSendConversationMessageInput {
conversationId: ID!
"""본문 형식. 이 값에 따라 아래 둘 중 어느 필드가 필수인지 정해진다."""
"""본문 형식. 이 값에 해당하는 본문 필드가 필수다(다른 쪽을 함께 넘기면 그대로 저장된다)."""
bodyFormat: ConversationBodyFormat!
"""평문 본문(최대 2000자). bodyFormat이 TEXT면 필수."""
bodyText: String
Expand Down
5 changes: 4 additions & 1 deletion src/features/user/user-order.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ type MyOrderItemDetail {
productId: ID!
"""상품명(주문 당시)."""
productName: String!
"""대표 이미지 URL(주문 당시). 이미지가 없으면 null."""
"""
대표 상품 이미지 URL. 주문 당시 스냅샷이 아니라 현재 상품의 첫 이미지를 읽는다 —
판매자가 이미지를 바꾸거나 지우면 값이 달라지거나 null이 된다.
"""
representativeImageUrl: String
"""주문 수량."""
quantity: Int!
Expand Down
Loading