The Round

合同会社ナイツオの開発ブログ

[PR] 5分から相談できるGCP™ 開発コンサル!→こちら

Firebaseでチャットアプリを作る日記(8日目)〜 メッセージの登録、一覧取得

本日はメッセージの登録と一覧取得を行います。

データ構造

  • 親ドキュメント - rooms/{room}
  • コレクション - messages
  • id - 自動採番
プロパティ 説明
from string 投稿者
text string 内容
members array 参加者リスト
createdAt date and time 投稿日時

ルール追加

セキュリティルールの追加も忘れてはいけません。前回テストモードを解除したので追加しないとパーミッションエラーとなります。

基本roomと同様のルールですので解説は割愛します。

参考: Firebaseでチャットアプリを作る日記(7日目)〜 Firestore セキュリティルールを書く - The Round

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    (中略)

    match /rooms/{room}/messages/{message} {
      allow create: if request.auth.uid != null &&
        request.auth.token.email_verified && 
        request.auth.token.email == request.resource.data.from && 
        request.auth.token.email in request.resource.data.members;
      allow read: if request.auth.uid != null &&
        request.auth.token.email_verified &&
        request.auth.token.email in resource.data.members;
    }
  }
}

メッセージ登録

コード

export function addMessage(room, user, text) {
  console.log('adding message', room, user, text);
  
  let db = firebase.firestore();

  let message = {
    from: user.email,
    text: text,
    members: room.members,
    createdAt: firebase.firestore.FieldValue.serverTimestamp()
  };

  return db.collection("rooms").doc(room.id).collection("messages").add(message)
  .then(function(docRef) {
    return docRef.get(); // ここではIDしか取得できないのでgetしなおす
  })
  .then(function(docRef) {
    let added = docRef.data();
    added.id = docRef.id;

    // 登録したmessageをUIに追加(Svelte)
    messagesOf(room.id).update(list => list.concat(added));

    return added;
  });
}

roomの登録とほぼ同じなので、大部分の説明は割愛します。

ちょっと特殊な点は、firebase.firestore.FieldValue.serverTimestamp() でサーバー側の日時を保存していることくらいです。

メッセージ一覧参照

コード

export function getMessages(roomId, email) {
  let db = firebase.firestore();

  return db.collection("rooms").doc(roomId).collection("messages").where("members", "array-contains", email).orderBy("createdAt")
    .get()
    .then(function(querySnapshot) {
        let _messages = [];
        querySnapshot.forEach(function(msgRef) {
          let msg = msgRef.data();
          msg.id = msgRef.id;
          _messages = [..._messages, msg];
        });

        // 登録したmessageをUIに追加(Svelte)
        messagesOf(roomId).set(_messages);

        return _messages;
    })
    .catch(function(error) {
        console.log("Error getting messages: ", error);
    });
}

こちらもroomの一覧取得とほぼ同様です。

インデックス登録

実は、上記コードをそのまま実行するとエラーとなります。

f:id:knightso:20191220222509p:plain

roomは件数が少ない為全件取得してフロントエンドでソートしているのですが、messageは orderBy("createdAt") を指定してFirestore側でソートしている為、members と併せた複合インデックスが必要となっています。

エラーメッセージの中にリンクが貼られているのでクリックすると、該当インデックスを作成する為のコンソール画面へジャンプできます。便利ですね(^^)

「インデックスを作成」ボタンをクリックするとインデックスが作成されました(ビルドに少し時間がかかります)

f:id:knightso:20191220223127p:plain

インデックスビルド完了後にクエリを実行することで無事に結果を取得することができました\(^o^)/

ちなみにインデックスですが、通常はファイル(firestore.indexes.json)に全て記述しておいて、$ firebase deploy --only firestore:indexes コマンドでデプロイできる様にするのが定石かと思います。

コンソールから作成した場合でも $ firebase firestore:indexes コマンドでJSON形式を出力できるので、それをfirestore.indexes.jsonにコピーしておくのがよいと思います。

$ firebase firestore:indexes>firestore.indexes.json

生成される内容

{
  "indexes": [
    {
      "collectionGroup": "messages",
      "queryScope": "COLLECTION",
      "fields": [
        {
          "fieldPath": "members",
          "arrayConfig": "CONTAINS"
        },
        {
          "fieldPath": "createdAt",
          "order": "ASCENDING"
        }
      ]
    }
  ],
  "fieldOverrides": []
}

まとめ

messageの登録と一覧取得ができました。

ただ実は、Firestoreアクセスの部分は動作確認できているのですがフロント制御のコード(Svelte)でちょっとハマっていてroom移動時に画面にmessage一覧が出せない状態になっています(´・ω・`)

今週末はプライベートの急用も入ってしまった為、目標の毎日更新途切れる可能性大となっております(´・ω・`) 予め謝っておきます 🙇🙇🙇