-
Notifications
You must be signed in to change notification settings - Fork 0
chore: 릴리즈 — 알림센터 API (알림 탭·구매자 채팅·실시간 subscription) #272
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
b435de9
feat(user): 알림센터 알림 탭 대응 — event·연관 정보 노출, 3개월 필터, 커서 전환
chanwoo7 a48c907
fix(user): 주문 연결 알림 상품명은 스냅샷 우선 (PR #267 리뷰 반영)
chanwoo7 05bcf01
fix(user): Date 범위 밖 커서 timestamp 거부 (PR #267 리뷰 반영)
chanwoo7 df00c03
fix(user): UNSIGNED BIGINT 상한 넘는 커서 id 거부 (PR #267 리뷰 반영)
chanwoo7 b40f8a7
Merge pull request #267 from CaQuick/feat/notification-center-alerts
chanwoo7 46a2543
feat(conversation): 구매자 문의 채팅 기반 — 진입 컨텍스트·메시지 전송·FAQ 자동응답·인사말
chanwoo7 897a8a8
fix(conversation): 대화 재사용 복구·레이스 재조회·SellerStore 인사말 노출 (PR #268 리뷰 반영)
chanwoo7 590ba92
fix(conversation): 인사말 초기화를 row 잠금 + 메시지 수 기준으로 직렬화 (PR #268 리뷰 반영)
chanwoo7 fbadab5
fix(conversation): 대화 생성을 메시지 트랜잭션과 원자화 (PR #268 리뷰 반영)
chanwoo7 0224c0d
fix(conversation): 트랜잭션 내 사전 조회를 tx 클라이언트로 (PR #268 리뷰 반영)
chanwoo7 fc2cc2d
Merge pull request #268 from CaQuick/feat/conversation-buyer-messaging
chanwoo7 0d87e11
feat(conversation): 알림센터 대화 탭 — 대화 목록·채팅 상세 조회·읽음 처리
chanwoo7 ac6b487
fix(conversation): 읽음 마커 고수위 갱신·커서 상한·목록 배치 조회 (PR #269 리뷰 반영)
chanwoo7 e0d5d64
fix(conversation): 읽음 마커를 커밋 순서와 정렬 + parseId 상한 검증 (PR #269 리뷰 반영)
chanwoo7 c641b39
fix(conversation): 구매자 mutation이 전달한 자동응답은 읽음 처리 (PR #269 리뷰 반영)
chanwoo7 07714f0
fix(conversation): 미읽음 백로그가 있으면 전송 시 읽음 마커 유지 (PR #269 리뷰 반영)
chanwoo7 490dba9
fix(common): 커서 timestamp에 MySQL DATETIME 상한 검증 (PR #269 리뷰 반영)
chanwoo7 628d942
fix(conversation): 미읽음 판정을 잠금 조회로 스냅샷 우회 (PR #269 리뷰 반영)
chanwoo7 ff07964
Merge pull request #269 from CaQuick/feat/conversation-buyer-list-detail
chanwoo7 d01ea4d
feat(conversation): 실시간 subscription — graphql-ws + Redis PubSub
chanwoo7 17884c9
fix(conversation): Redis 장애 격리·이벤트 최신 상태 발행 (PR #270 리뷰 반영)
chanwoo7 b3d3e42
fix(conversation): 목록 이벤트 스냅샷을 단일 트랜잭션으로 (PR #270 리뷰 반영)
chanwoo7 f2c5e13
fix(conversation): 커밋 후 발행 전체 격리 + 이벤트 순서 계약 명시 (PR #270 리뷰 반영)
chanwoo7 e028c61
fix(conversation): 목록 이벤트에 lastReadAt 실어 폐기 규칙 확장 + 스냅샷 매장명 (PR #270 …
chanwoo7 a474fa5
Merge pull request #270 from CaQuick/feat/conversation-subscriptions
chanwoo7 4826dac
fix: 릴리즈 리뷰 반영 (대화 메시지 시각을 DB 시계로 채번)
chanwoo7 a4a8ad2
fix: 릴리즈 리뷰 반영 (시계 컷오버 대비 대화 단위 단조 시각 보정)
chanwoo7 a94857a
fix: 릴리즈 리뷰 반영 (단조 시각 채번을 잠금 조회로)
chanwoo7 15a8d5c
Merge pull request #273 from CaQuick/fix/release-review-conversation-…
chanwoo7 5fbc2c1
fix: 릴리즈 리뷰 반영 (3개월 하한 월말 롤오버 클램프, 시드 포맷)
chanwoo7 108f57b
Merge pull request #274 from CaQuick/fix/release-review-cutoff-and-fo…
chanwoo7 1b6d2eb
fix: 릴리즈 리뷰 반영 (대화 목록 페이지·부가 정보를 단일 스냅샷으로)
chanwoo7 6db8b6f
fix: 릴리즈 리뷰 반영 (페이지 스냅샷 트랜잭션 격리 수준 명시)
chanwoo7 7ea51fb
Merge pull request #276 from CaQuick/fix/release-review-conversation-…
chanwoo7 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
prisma/migrations/20260901175648_add_store_greeting_message/migration.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| -- AlterTable | ||
| ALTER TABLE `store` ADD COLUMN `greeting_message` VARCHAR(500) NULL; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,166 @@ | ||
| /** | ||
| * 시드 대화 + FAQ 칩 (figma notification-center 대화 탭·문의 채팅 재현). | ||
| * | ||
| * - storeA: 커스텀 인사말 + FAQ 5종(질문 칩) 등록. | ||
| * user1 대화 = 인사말 → 칩 질문("케이크 보관 방법") → HTML 자동응답. | ||
| * - storeB: 인사말 미설정(기본 문구 사용 경로 검증). | ||
| * user1 대화 = 인사말 → 유저 자유 텍스트 → 판매자 답장 3건(안읽음 배지 재현, | ||
| * last_read_at은 유저 메시지 시점까지만). | ||
| */ | ||
| import type { PrismaClient } from '@prisma/client'; | ||
|
|
||
| import type { SeededStores } from './stores'; | ||
| import type { SeededUser } from './users'; | ||
|
|
||
| export async function seedConversations( | ||
| prisma: PrismaClient, | ||
| ctx: { users: SeededUser[]; stores: SeededStores }, | ||
| ): Promise<void> { | ||
| const user1 = ctx.users[0]; | ||
| if (!user1) throw new Error('seedUsers must run before seedConversations'); | ||
| const [storeA, storeB] = ctx.stores.stores; | ||
| if (!storeA || !storeB) { | ||
| throw new Error('seedStores must run before seedConversations'); | ||
| } | ||
|
|
||
| const now = Date.now(); | ||
| const hour = 60 * 60 * 1000; | ||
| const day = 24 * hour; | ||
|
|
||
| // ── storeA: 커스텀 인사말 + FAQ 칩 ── | ||
| await prisma.store.update({ | ||
| where: { id: storeA.id }, | ||
| data: { | ||
| greeting_message: | ||
| '안녕하세요! {nickname} 고객님.\n{storeName} 입니다 😄\n무엇을 도와드릴까요?', | ||
| }, | ||
| }); | ||
|
|
||
| const faqRows = [ | ||
| { | ||
| title: '날짜 변경', | ||
| answer_html: | ||
| '<p>픽업 1일 전까지 채팅으로 요청해 주시면 일정 확인 후 변경해 드려요.</p>', | ||
| }, | ||
| { | ||
| title: '케이크 보관 방법', | ||
| answer_html: | ||
| '<p>🎂 <strong>케이크 보관 방법</strong></p><ul><li>냉장보관시 최대 3일</li><li>생크림 케이크는 당일 드시는 걸 권장해요</li></ul>', | ||
| }, | ||
| { | ||
| title: '가게 위치 정보', | ||
| answer_html: '<p>매장 상세의 찾아오는 길 안내를 확인해 주세요.</p>', | ||
| }, | ||
| { | ||
| title: '제일 많이 물어보는 질문', | ||
| answer_html: | ||
| '<p>레터링 문구는 주문 시 요청사항에 남겨 주시면 반영돼요.</p>', | ||
| }, | ||
| { | ||
| title: '예약 가능 일정', | ||
| answer_html: | ||
| '<p>캘린더에서 픽업 가능 날짜·시간대를 확인할 수 있어요.</p>', | ||
| }, | ||
| ]; | ||
| const faqs = [] as { id: bigint; title: string; answer_html: string }[]; | ||
| for (const [i, row] of faqRows.entries()) { | ||
| faqs.push( | ||
| await prisma.storeFaqTopic.create({ | ||
| data: { store_id: storeA.id, sort_order: i + 1, ...row }, | ||
| }), | ||
| ); | ||
| } | ||
|
|
||
| // ── user1 ↔ storeA: 칩 문답 대화 ── | ||
| const convA = await prisma.storeConversation.create({ | ||
| data: { | ||
| account_id: user1.id, | ||
| store_id: storeA.id, | ||
| last_message_at: new Date(now - 1 * day), | ||
| last_read_at: new Date(now - 1 * day), | ||
| }, | ||
| }); | ||
| const keepFaq = faqs[1]; | ||
| await prisma.storeConversationMessage.createMany({ | ||
| data: [ | ||
| { | ||
| conversation_id: convA.id, | ||
| sender_type: 'STORE', | ||
| body_format: 'TEXT', | ||
| body_text: | ||
| '안녕하세요! seedTester1 고객님.\n[SEED] 케이크샵 A 입니다 😄\n무엇을 도와드릴까요?', | ||
| created_at: new Date(now - 1 * day - 2 * 60 * 1000), | ||
| }, | ||
| { | ||
| conversation_id: convA.id, | ||
| sender_type: 'USER', | ||
| sender_account_id: user1.id, | ||
| body_format: 'TEXT', | ||
| body_text: keepFaq?.title ?? '케이크 보관 방법', | ||
| created_at: new Date(now - 1 * day - 60 * 1000), | ||
| }, | ||
| { | ||
| conversation_id: convA.id, | ||
| sender_type: 'STORE', | ||
| body_format: 'HTML', | ||
| body_html: keepFaq?.answer_html ?? '<p>보관 안내</p>', | ||
| created_at: new Date(now - 1 * day), | ||
| }, | ||
| ], | ||
| }); | ||
|
|
||
| // ── user1 ↔ storeB: 자유 문의 + 판매자 답장 3건(안읽음) ── | ||
| const convB = await prisma.storeConversation.create({ | ||
| data: { | ||
| account_id: user1.id, | ||
| store_id: storeB.id, | ||
| last_message_at: new Date(now - 5 * hour), | ||
| // 유저가 마지막으로 읽은 시점 = 본인 메시지 직후 → 판매자 답장 3건 안읽음 | ||
| last_read_at: new Date(now - 8 * hour), | ||
| }, | ||
| }); | ||
| await prisma.storeConversationMessage.createMany({ | ||
| data: [ | ||
| { | ||
| conversation_id: convB.id, | ||
| sender_type: 'STORE', | ||
| body_format: 'TEXT', | ||
| body_text: | ||
| '안녕하세요! seedTester1 고객님.\n[SEED] 도넛샵 B 입니다 😄\n무엇을 도와드릴까요?', | ||
| created_at: new Date(now - 9 * hour), | ||
| }, | ||
| { | ||
| conversation_id: convB.id, | ||
| sender_type: 'USER', | ||
| sender_account_id: user1.id, | ||
| body_format: 'TEXT', | ||
| body_text: '주문한 도넛 픽업 시간을 30분 늦출 수 있을까요?', | ||
| created_at: new Date(now - 8 * hour), | ||
| }, | ||
| { | ||
| conversation_id: convB.id, | ||
| sender_type: 'STORE', | ||
| sender_account_id: storeB.seller_account_id, | ||
| body_format: 'TEXT', | ||
| body_text: '고객님이 말씀해 주신 문의사항에 대한 답변 드리겠습니다.', | ||
| created_at: new Date(now - 7 * hour), | ||
| }, | ||
| { | ||
| conversation_id: convB.id, | ||
| sender_type: 'STORE', | ||
| sender_account_id: storeB.seller_account_id, | ||
| body_format: 'TEXT', | ||
| body_text: '네, 30분 늦은 픽업 가능합니다.', | ||
| created_at: new Date(now - 6 * hour), | ||
| }, | ||
| { | ||
| conversation_id: convB.id, | ||
| sender_type: 'STORE', | ||
| sender_account_id: storeB.seller_account_id, | ||
| body_format: 'TEXT', | ||
| body_text: '방문 시 매장 카운터에서 주문번호를 말씀해 주세요.', | ||
| created_at: new Date(now - 5 * hour), | ||
| }, | ||
| ], | ||
| }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the second
yarn prisma:seedrun, this newreview_id(and the neworder_idvalues below) preventsresetSeedScopefrom being idempotent: that function deletes the seeded reviews and orders before deleting the user's notifications, while theNotification.reviewandNotification.orderrelations have no cascading delete. MySQL therefore rejects the first referenced review/order deletion with a foreign-key constraint error. Move notification cleanup ahead of review/order cleanup, or otherwise clear these references first.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
false positive: notification의 review_id/order_id/store_id/product_id FK는 모두 ON DELETE SET NULL (migrations/20260209192020 L114~L126). 리뷰/주문을 먼저 삭제해도 알림 컬럼이 NULL로 풀릴 뿐 FK 거부가 발생하지 않음. yarn prisma:seed 2회 연속 실행으로 멱등성 실검증 완료(PR #268 본문 참조).