Open9

Firestore学び直しスクラップ

mcz9mmmcz9mm

Day1

公式ドキュメントを読もう

Cloud Firestoreについて

  • コレクションとドキュメントの関係性
    • 個人アプリで作る設計を想定して構成を書き出してみる
  • Rulesをしっかり設定すること(勉強します)
  • 読み取り回数に気を付ける必要がありそう(料金に影響するので)

Rulesについて調べる

su-さんのブログ最高、、!
https://techblog.sgr-ksmt.dev/2018/12/11/194022/

こちらも読む
https://zenn.dev/sgr_ksmt/books/2f83a604d636b241cf3c

実践Firestoreもポチってみたのであとで読む
https://www.amazon.co.jp/実践Firestore-技術の泉シリーズ(NextPublishing)-福田-雄貴-ebook/dp/B0851BGDQG/ref=rvi_2/356-5044272-8322969?pd_rd_w=exfyK&pf_rd_p=a4dc92d7-7100-437e-b3e3-2349e8298523&pf_rd_r=WRCYE260FF4DKY79R614&pd_rd_r=9634ee75-0c4b-4480-aabb-d9d91c407928&pd_rd_wg=SeFOG&pd_rd_i=B0851BGDQG&psc=1

Firebase プロジェクトの作成+Flutter経由で軽くFirestoreを触ってみる

まずは匿名認証
Auth側の設定でanonymousログインを許可することを忘れずに

final FirebaseAuth firebaseAuth = FirebaseAuth.instance;

@override
void initState() {
  Future(() async {
    var user = await signInAnon();
    print('+++++USER INFO+++++');
    print(user.user?.uid);
    print(user.user?.isAnonymous);
  });
  super.initState();
}

Future<UserCredential> signInAnon() async {
  UserCredential user = await firebaseAuth.signInAnonymously();
  return user;
}

データを追加してみる

Swiftが読みやすかった
https://firebase.google.cn/docs/firestore/manage-data/add-data?hl=ja#swift_1

/users/{uid}に追加してみる

Setの場合は更新やデータがなければ追加をしてくれる
(追加ならAddでもOK)

CollectionReference users = FirebaseFirestore.instance.collection('users');
Future<void> addUser() {
  return users.doc(uid).set({
        'id': uid,
        'name': name, 
        'age': age,
      })
      .then((value) => print("success set user"))
      .catchError((error) => print("Failed to add user: $error"));
}

users/{uid}だけでなく、中身のusersのドキュメントにもiduidを割り振るようにしてみた。

参考
https://zenn.dev/saikou_kunisaki/articles/5a3470cbb6fbb7

上記ページを参考にRulesも少し改変してみることに。
createとupdateは認証時のuidとuserの中身のidが一致する場合のみ許可するようにしてみた。

    match /users/{userId} {
        allow get: if true;
        allow create: if request.auth.uid == userId && request.auth.uid == request.resource.data.id;
        allow update: if request.auth.uid == userId && request.auth.uid == request.resource.data.id;
        allow delete: if request.auth.uid == userId;
    }

User→Collectionにデータを追加

今度は作成したUserに紐づくCollectionを作成してみる

user→worksというような関係
worksのドキュメントIDを自動割り振りではなくuidと同じように一致させた方が良いのかな?
と思い、UUIDを生成してセットしてみた。
この辺はちゃんと調べた方が良さそう。

追記:データ取得時にこのような形で取得できるので不要そう。
キーでドキュメント指定したいならキーが必要、そうで無いなら不要でOKらしい!

id: xxx
doc: { ... }

CollectionReference users = FirebaseFirestore.instance.collection('users');
  Future<void> addWork() {
    var id = const Uuid().v1();
    return users.doc(uid).collection('works').doc(id)
        .set({
          'id': id,
          'name': 'AAAA',
          'value': 4500,
          'date': DateTime.now(),
        })
        .then((value) => print("Work Added"))
        .catchError((error) => print("Failed to add Data: $error"));
  }

今日はここまで

mcz9mmmcz9mm

Day2

セキュリティルールについて
Firebase CLIでローカルテストを行う

Firebase CLIの設定

こちらから
https://firebase.google.com/docs/cli/#install-cli-mac-linux

FirestoreのSecurity RuleをLocalでテスト~実施

$ // インストール
$ curl -sL https://firebase.tools | bash
$ // 適宜
$ firebase logout
$ firebase login
$ // プロジェクト一覧
$ firebas projects:list

Firestore エミュレータをインストール
https://firebase.google.com/docs/firestore/security/test-rules-emulator

公式動画があるので見ながら設定

途中の進捗

テストコードを書いてみる
get()は非同期なのでawaitを書き忘れずに

    it("Can read items in the read=only collection", async() => {
        const db = firebase.initializeTestApp({projectId: MY_PROJECT_ID}).firestore();
        const testDoc = db.collection("readonly").doc("testDoc");
        await firebase.assertSucceeds(testDoc.get());
    })

失敗テスト

    it("Can't write items in the read=only collection", async() => {
        const db = firebase.initializeTestApp({projectId: MY_PROJECT_ID}).firestore();
        const testDoc = db.collection("readonly").doc("testDoc");
        await firebase.assertFails(testDoc.set({foo: "bar"}));
    })

認証しているユーザーのみ作成できるかどうか

    match /users/{userId} {
        allow get: if true;
        allow create: if request.auth.uid == userId && request.auth.uid == request.resource.data.id;
        allow update: if request.auth.uid == userId && request.auth.uid == request.resource.data.id;
        allow delete: if request.auth.uid == userId;
    }
    it("認証情報(UID)無しでユーザー作成できない", async() => {
        const db = firebase.initializeTestApp({projectId: MY_PROJECT_ID}).firestore();
        const testDoc = db.collection("users").doc("user_abc");
        await firebase.assertFails(testDoc.set({foo: "bar"}));
    })

    it("認証情報(UID)ありでユーザー作成できる", async() => {
        const UID = "user_abc";
        const myAuth = {uid: UID, email: "abc@gmail.com"};
        const db = firebase.initializeTestApp({projectId: MY_PROJECT_ID, auth: myAuth}).firestore();
        const testDoc = db.collection("users").doc(UID);
        await firebase.assertSucceeds(testDoc.set({id: UID, foo: "bar"}));
    })

関数化しておくと良い👍

const myId = "user_abc";
const thierId = "user_xyz";
const myAuth = {uid: myId, email: "abc@gmail.com"};
function getFirestore(auth) {
    return firebase.initializeTestApp({projectId: MY_PROJECT_ID, auth: auth}).firestore();
}

queryでの取得ができるかテスト

    match /posts/{postId} {
      allow read: if (resource.data.visiblity == "public") || (resource.data.authorId == request.auth.uid);
    }
    it("posts/でpublicになっているものを取得できる", async() => {
        const db = getFirestore(null);
        const testQuery = db.collection("posts").where("visiblity", "==", "public");
        await firebase.assertSucceeds(testQuery.get());
    })

    it("authorの場合posts/を取得できる", async() => {
        const db = getFirestore(myAuth);
        const testQuery = db.collection("posts").where("authorId", "==", myId);
        await firebase.assertSucceeds(testQuery.get());
    })

http://localhost:4000を開く

エミュレータ上のデータを編集できる

mcz9mmmcz9mm

最近やってなかったので復活

Cloud Functionsでランキング形式のデータを取得するようにする

$ firebase login
$ firebase init functions

必要な情報を設定して作成

functions/index.jsを編集していく

const functions = require('firebase-functions');

// Create and Deploy Your First Cloud Functions
// https://firebase.google.com/docs/functions/write-firebase-functions

exports.helloWorld = functions.https.onRequest((request, response) => {
  response.send("Hello from Firebase!");
});
$ firebase deploy --only functions

デプロイが成功するとFirebaseのコンソール上に関数が追加される。

mcz9mmmcz9mm

Day5

定期実行もしくはAPI経由での実行、トリガー経由での実行などFunctionsはさまざまなタイミングで呼び出せる

今回は定期実行として発火させていく。
5分ごと、60分ごと、毎週月曜の何時など、具体的に指定ができるので書いておく。
https://firebase.google.com/docs/functions/schedule-functions?hl=ja
https://cloud.google.com/appengine/docs/standard/python/config/cronref?hl=ja

exports.updateRankingFunction = functions
  .region('asia-northeast1')
  .pubsub.schedule('every 60 mins')
  .onRun(async (context) => {
    // code here
});
mcz9mmmcz9mm

Day6

rankingのcollectionsを想定したデータの更新を作ってみる

const rankingRef = admin.firestore().collection('ranking');
async function updateRanking() {
  usersRef
    .orderBy('score', 'desc')
    .limit(100)
    .get()
    .then((qSnapshot) =>
  {
    qSnapshot.forEach(async (ref) => {
      try {
        var result = await rankingRef.add({
          name : ref.data()['name'],
          score : ref.data()['score'],
        });
        functions.logger.log("Rank Result:", result);
      } catch (e) {
        console.log(`Error: ${JSON.stringify(e)}`)
      }
    });
  });
}