チャットルーム
チャットルームは店舗と顧客の1対1コミュニケーションの単位です。
| フィールド | 説明 |
|---|---|
id | UUID(内部 PK) |
roomId | {shopId}:{contactId} 形式のユニークキー |
shopId | 店舗 ID |
contactId | 連絡先 ID |
lastMessage | 最新メッセージのプレビューテキスト |
lastMessageAt | 最新メッセージの送信日時 |
lastUserMessageAt | 顧客からの最新メッセージ日時 |
Upsert パターン
Section titled “Upsert パターン”ルーム作成は Upsert(存在すれば返却、なければ作成)で行います。
// roomId の生成const roomId = `${shopId}:${contactId}`;
// DB への Upsertawait db .insert(chatRoom) .values({ id: uuid(), shopId, contactId, roomId }) .onConflictDoNothing({ target: chatRoom.roomId });
// 作成されたかを確認const room = await db.query.chatRoom.findFirst({ where: { roomId }});作成トリガー
Section titled “作成トリガー”| トリガー | 処理 |
|---|---|
| Counter で連絡先のチャットタブを開く | createChatRoom({ shopId, contactId }) |
| purelovers からのメッセージ送信 | initChatRoom({ shopId, contactId }) |
| 一括配信の送信 | 対象連絡先のルームが自動作成 |
店舗スタッフ向け表示
Section titled “店舗スタッフ向け表示”店舗スタッフは 顧客からメッセージが来たルーム のみ表示されます。
WHERE shopId = {shopId} AND lastUserMessageAt IS NOT NULLORDER BY lastUserMessageAt DESC NULLS LASTこれにより、店舗側から一方的にメッセージを送っただけのルームは一覧に表示されません。
顧客向け表示
Section titled “顧客向け表示”顧客は 何らかのメッセージがあるルーム が表示されます。
WHERE contactId = {contactId} AND lastMessage IS NOT NULLORDER BY lastMessageAt DESC NULLS LASTカーソルページネーション
Section titled “カーソルページネーション”ルーム一覧はカーソルベースのページネーションを使用します。
// ソートキー: (lastMessageAt, createdAt, id)// カーソル: 前回最後のルーム IDconst cursor = await db.query.chatRoom.findFirst({ where: { id: cursorId } });
// カーソル以降のレコードを取得WHERE (lastMessageAt, createdAt, id) < (cursor.lastMessageAt, cursor.createdAt, cursor.id)LIMIT {limit + 1} // hasMore 判定用に +1ルーム一覧には以下の情報が付加されます。
| 情報 | 取得方法 |
|---|---|
| 連絡先名 | chatRoom.contact リレーション |
| 未読数 | fetchUnreadCounts() で一括取得 |
| ブロック状態 | contactChatNg テーブルから判定 |
| アバター | 連絡先の image フィールド |
const room = await db.query.chatRoom.findFirst({ where: { id: roomId }, with: { contact: { with: { purelovers: true, chatNg: true } }, shop: { with: { purelovers: true } } }});ブロック判定
Section titled “ブロック判定”| NGタイプ | 制限内容 |
|---|---|
SHOP_NG | 店舗によるブロック。双方向送信不可 |
USER_NG | 運営によるブロック。顧客→店舗の送信不可 |
NO_PURELOVERS_USER | purelovers 未連携。双方向送信不可 |
isBlocked フラグがルーム詳細に含まれ、UI でメッセージ入力を無効化します。
Fizz RPC: deleteChatRoom({ roomId })Vintage RPC: deleteChatRoom({ roomId })ルーム削除は物理削除です。関連するメッセージ、既読状態、添付ファイルもカスケードで削除されます。
Counter での表示
Section titled “Counter での表示”チャット画面構成
Section titled “チャット画面構成”┌─────────────────────────────────┐│ チャット │├──────────┬──────────────────────┤│ ルーム一覧 │ メッセージ表示 ││ │ ││ [顧客A] │ メッセージ... ││ [顧客B] │ メッセージ... ││ [顧客C] │ ││ │ ────────────── ││ │ [メッセージ入力] │├──────────┴──────────────────────┤│ ボトムナビ │└─────────────────────────────────┘- モバイル: ルーム一覧とメッセージ表示が画面遷移で切り替わる
- デスクトップ: 2カラムレイアウトで同時表示
- メッセージ - メッセージの送受信仕様
- 既読管理 - 未読カウントの計算
- チャットNG管理 - ブロック制御の詳細
- Cellar チャットテーブル - DB スキーマ