Firestoreの自動バックアップでつまづいたこと

基本は公式の手順に従うまでですが、ところどころ躓いたのでメモ。

## 公式の手順
手動バックアップ
https://firebase.google.com/docs/firestore/manage-data/export-import?hl=ja

自動バックアップ
https://firebase.google.com/docs/firestore/solutions/schedule-export?hl=ja

### バケットの作成方法
上記どちらを行う場合も、まず先にバケットを作る必要がありますが以下を参考に作成しました。
https://nipo.sndbox.jp/develop-blog/firestore-backup

StorageはGCP管理画面の左メニューのプロダクト>ストレージの中にあります。
![スクリーンショット 2021-01-04 21.00.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242610/0ea59346-8be5-9cee-475b-e582f46240ab.png)

私はバケットの名前は [PROJECT_ID]-exportとしました。
![スクリーンショット 2021-01-04 20.59.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242610/1bcec1a5-1068-94a5-33f4-e3947df76c69.png)



## ローカルにファイル一式をinitする

Cloud Functions のソースファイルを入れるフォルダを作る。私は`/Users/takuya/Development/[PROJECT_NAME]-functions`という階層・名前で作成しました。最初に箱を作らないとinitしたときに今いる場所に色んなルールファイルなどができちゃいます。

```terminal
$ cd [PROJECT_NAME]-functions # さっき作ったフォルダに移動
$ firebase init functions --project PROJECT_ID
# PROJECT_ID は小文字
```
JavaScriptかTypeScriptを選ぶように言われますが、公式の手順ではJavascriptを選択（ただし凄い人たちはCloud FunctionsではTypeScriptを使うことを勧めている人が多い印象）。

ESLintを使うか聞かれデフォルトでは`N`（すなわちNo）となってますが私は入れました。コードのバグを見つけてくれるものでありTypeScriptだとデフォルト`Y`なので、コンパイルが不要なJavaScriptではいらない（VSCodeなどのエディタで警告してくれるから）のかな？

![スクリーンショット 2021-01-04 20.51.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242610/ad6d6416-04bd-7334-e5fb-c7ce23a40621.png)


## index.jsを編集

ここを編集せずにデプロイするとエラーになるので編集後にデプロイするように。

またバックアップ時間は私は unix-cron 文字列形式`（* * * * *）`で記述しました。
https://cloud.google.com/scheduler/docs/images/schedule-fields.png?hl=ja
<img src="https://cloud.google.com/scheduler/docs/images/schedule-fields.png?hl=ja" />
アメリカ時間であることに注意。

また Node.js 10 からは`process.env.GCP_PROJECT`の一文を追加しないと`Cannot read property 'toString' of undefined`というエラーが出てしまうのも公式には記述無く。

```index.js
const functions = require("firebase-functions");
const firestore = require("@google-cloud/firestore");
const client = new firestore.v1.FirestoreAdminClient();

// Replace BUCKET_NAME
const bucket = "gs://バケット名";

exports.scheduledFirestoreExport = functions.pubsub
  .schedule("45 19 * * *") // 好きな時間帯：私は19:45（日本時間2:45）にしました
  .onRun((context) => {
    const projectId = process.env.GCP_PROJECT || process.env.GCLOUD_PROJECT; // 公式のコードに追加追加
    const databaseName = client.databasePath(projectId, "(default)");

    return client
      .exportDocuments({
        name: databaseName,
        outputUriPrefix: bucket,
        // Leave collectionIds empty to export all collections
        // or set to a list of collection IDs to export,
        // collectionIds: ['users', 'posts']
        collectionIds: [],
      })
      .then((responses) => {
        const response = responses[0];
        console.log(`Operation Name: ${response["name"]}`);
        return response;
      })
      .catch((err) => {
        console.error(err);
        throw new Error("Export operation failed");
      });
  });
```

## Cloud Functions をデプロイ

```terminal
$ firebase deploy --only functions
```

`--only functions`を付けないと既存のFireStoreやRealtime Databaseなどが上書きされてしまうはずなので注意！ 


## Cloud Scheduler の場所

公式のリンクでは変なトップ画面に飛ばされる。
まず「APIとサービス」で「Cloud Scheduler」を検索して追加する。
追加後のダッシュボードでは利用状況や認証情報などしか見れずここでは実行できない。

ホームに戻り左側メニューの下の方にスクロールすると「Cloud Scheduler」があるのでクリックするとここにジョブができているはず。`今すぐ実行`ボタンが見つかる。

下記のようにピン留めしておくと上の方に「Cloud Scheduler」が来て以後使いやすい。
![スクリーンショット 2021-01-04 20.20.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242610/885cf7e4-2752-4181-f565-25d24b001897.png)

あとは公式通りの手順で問題なく行けました。


## 参考
- https://stackoverflow.com/questions/62857952/cannot-read-property-tostring-of-undefined-when-firestore-would-run-backups