既読管理
Speakeasy のチャットでは、ユーザーごとの既読状態を管理し、未読メッセージ数をリアルタイムに表示します。
データモデル
Section titled “データモデル”既読状態は user_room_state テーブルで管理されます。
| フィールド | 型 | 説明 |
|---|---|---|
roomId | UUID | ルーム ID(複合PK) |
userId | text | ユーザー ID(複合PK) |
userType | text | shop or customer(複合PK) |
lastReadAt | timestamp | 最終既読日時 |
未読カウントの計算
Section titled “未読カウントの計算”SQL クエリ
Section titled “SQL クエリ”SELECT cm.roomId, COUNT(*)::int AS unreadCountFROM chat_message cmLEFT JOIN user_room_state urs ON cm.roomId = urs.roomId AND urs.userId = :userId AND urs.userType = :userTypeWHERE cm.roomId IN (:roomIds) AND (urs.lastReadAt IS NULL OR cm.sentAt > urs.lastReadAt)GROUP BY cm.roomIdLEFT JOINにより、user_room_stateが存在しない場合も含むlastReadAt IS NULL— 一度も閲覧していないルームは全メッセージが未読sentAt > lastReadAt— 最終閲覧日時より新しいメッセージをカウント- 結果に含まれないルームは未読数 0
パフォーマンス
Section titled “パフォーマンス”- ルーム一覧取得時に 1回のバッチクエリ で全ルームの未読数を取得
- ルームごとに個別クエリを発行しない
chat_messageの(roomId, sentAt)インデックスを使用
Upsert ロジック
Section titled “Upsert ロジック”await db .insert(userRoomState) .values({ roomId, userId, userType, lastReadAt: new Date() }) .onConflictDoUpdate({ target: [userRoomState.roomId, userRoomState.userId, userRoomState.userType], set: { lastReadAt: new Date() } });自動既読の処理
Section titled “自動既読の処理”メッセージ送信時
Section titled “メッセージ送信時”送信者のメッセージは自動的に既読として扱われます。
// sendMessage トランザクション内await db .insert(userRoomState) .values({ roomId, userId: senderId, userType: senderType, lastReadAt: now }) .onConflictDoUpdate({ set: { lastReadAt: now } });配信メッセージは店舗スタッフ側で自動既読になります。
// broadcast queue 処理内// 店舗ユーザーの lastReadAt を一括更新await db .insert(userRoomState) .values( targetRoomIds.map((roomId) => ({ roomId, userId: shopUserId, userType: 'shop', lastReadAt: now })) ) .onConflictDoUpdate({ set: { lastReadAt: now } });顧客の既読状態
Section titled “顧客の既読状態”店舗スタッフは顧客の最終既読日時を取得できます。
Fizz RPC: getCustomerLastReadAt(roomId)SELECT lastReadAtFROM user_room_stateWHERE roomId = :roomId AND userType = 'customer'Counter での表示
Section titled “Counter での表示”チャットルーム一覧
Section titled “チャットルーム一覧”┌──────────────────────────┐│ 顧客A (3) 14:30 │ ← 未読バッジ│ 最新メッセージ... │├──────────────────────────┤│ 顧客B 13:00│ ← 未読なし│ 最新メッセージ... │└──────────────────────────┘ダッシュボード
Section titled “ダッシュボード”ダッシュボードでは未読ありのルームのみを表示します。
getChatRooms({ unreadOnly: true, limit: 5 });連絡先のチャットタブに未読バッジが表示されます。
[プロフィール] [チャット (2)] ← タブに未読数表示リアルタイム更新
Section titled “リアルタイム更新”WebSocket 経由で既読通知がリアルタイムに配信されます。
| イベント | 送信先 | 用途 |
|---|---|---|
read_receipt | 同じルームの参加者 | 既読インジケーター更新 |
message | ルーム参加者 | 未読カウント増加 |
- チャットルーム - ルーム管理
- リアルタイム通信 - WebSocket 配信
- Cellar チャットテーブル - user_room_state テーブル