FirestoreのデータをAPIを叩いて取得・登録するシェルを作ってみる

#Firestoreのデータを別のプロジェクトにコピーしたい!!
Firestoreのデータ別プロジェクトにコピーしたいと思ったのですが、Firestoreのエクスポート/インポート機能はBlaseプランでないと利用できないそうなので、API叩いてデータ取得/登録するちょっとしたシェルを作ってみました。

# 環境
Docker
ubuntu18.04

# やってみる
## データ取得をしてみる
[Cloud Firestore RESTAPIの使用](https://firebase.google.com/docs/firestore/use-rest-api?hl=ja)を参考にデータを取得してみます。
サンプルによると、YOUR_PROJECT_ID部を参照先のプロジェクトIDに変更し、参照したいドキュメントパスをつなげればよいみたいです。

```text:公式ドキュメントの取得サンプル
https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/cities/LA
```

test1-b1a03プロジェクトのtestコレクションを取得するようにして実行してみます。

```terminal
$ curl 'https://firestore.googleapis.com/v1/projects/test1-b1a03/databases/(default)/documents/test/'
{
  "documents": [
    {
      "name": "projects/test1-b1a03/databases/(default)/documents/test/1",
      "fields": {
        "string": {
          "stringValue": "hoge"
        },
        "array": {
          "arrayValue": {
            "values": [
              {
                "stringValue": "hoge1"
              },
              {
                "stringValue": "hoge2"
              }
            ]
          }
        },
        "id": {
          "integerValue": "1"
        }
      },
      "createTime": "2019-05-10T10:00:31.788827Z",
      "updateTime": "2019-05-10T10:00:31.788827Z"
    },
    {
      "name": "projects/test1-b1a03/databases/(default)/documents/test/22",
      "fields": {
        "string": {
          "stringValue": "hoge"
        },
        "array": {
          "arrayValue": {
            "values": [
              {
                "stringValue": "hoge1"
              },
              {
                "stringValue": "hoge2"
              }
            ]
          }
        },
        "id": {
          "integerValue": "1"
        }
      },
      "createTime": "2019-05-10T10:01:20.999100Z",
      "updateTime": "2019-05-10T10:01:20.999100Z"
    },
    ~~省略~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  ]
}
```

データを取得することができました。
パラメータでpageSizeを指定すると、取得件数を設定することでできるようです。
pageSizeを1件で実施してみます。

```terminal
$ curl 'https://firestore.googleapis.com/v1/projects/test1-b1a03/databases/(default)/documents/test/?pageSize=1'
{
  "documents": [
    {
      "name": "projects/test1-b1a03/databases/(default)/documents/test/1",
      "fields": {
        "string": {
          "stringValue": "hoge"
        },
        "array": {
          "arrayValue": {
            "values": [
              {
                "stringValue": "hoge1"
              },
              {
                "stringValue": "hoge2"
              }
            ]
          }
        },
        "id": {
          "integerValue": "1"
        }
      },
      "createTime": "2019-05-10T10:00:31.788827Z",
      "updateTime": "2019-05-10T10:00:31.788827Z"
    }
  ],
  "nextPageToken": "AJebfAewl38xJK0s9D09W4VACvDSfTZqLCTNJqeNF-74DOW5VQgnHLyu2YVk8CzNvSZjMGcB6u-KblZVFcaB9Fz34VZ-la5pcKIWwg"
}
```
1件のみ取得することができました。

取得対象のコレクションは2件以上存在するため、取得結果にnextPageTokenが付与されています。
同一URLにpageTokenパラメータを付与し実行することで、次のドキュメントを取得することができるみたいです。

```
$ curl 'https://firestore.googleapis.com/v1/projects/test1-b1a03/databases/(default)/documents/test/?pageSize=1&pageToken=AJebfAewl38xJK0s9D09W4VACvDSfTZqLCTNJqeNF-74DOW5VQgn
HLyu2YVk8CzNvSZjMGcB6u-KblZVFcaB9Fz34VZ-la5pcKIWwg'
{
  "documents": [
    {
      "name": "projects/test1-b1a03/databases/(default)/documents/test/22",
      "fields": {
        "string": {
          "stringValue": "hoge"
        },
        "array": {
          "arrayValue": {
            "values": [
              {
                "stringValue": "hoge1"
              },
              {
                "stringValue": "hoge2"
              }
            ]
          }
        },
        "id": {
          "integerValue": "1"
        }
      },
      "createTime": "2019-05-10T10:01:20.999100Z",
      "updateTime": "2019-05-10T10:01:20.999100Z"
    }
  ],
  "nextPageToken": "AJebfAcF29IQ4MJvLEkTNGxoXvIxKG0Tvpt0GE3Vz-RxeOQgjOIz4IqKsLu4aqXZLVF7sGnmXjXS8nvthFfqNvsTeiESh4vfpLBGQlc"
}
```

## 登録してみる
[Cloud Firestore API](https://firebase.google.com/docs/firestore/reference/rest?hl=ja)を参考に実施してきます。
登録を行いたいので[createDocument](https://firebase.google.com/docs/firestore/reference/rest/v1beta1/projects.databases.documents/createDocument?hl=ja)を使用します。

今回は"test1-b1a03"プロジェクトの"test2"コレクションを対象に、documentIdが"test"のデータを登録するようにしてみます。
また、登録データについては先ほどFireStoreから取得したデータを利用し実行してきます。

### リクエストURLの作成
test1-b1a03プロジェクトのtest2コレクションを対象とするようにパスを作成しました。
また、documentIdパラメータを付与し、登録データのドキュメントIDがtestとなるようにしました。

```terminal:リクエスト先
'https://firestore.googleapis.com/v1beta1/projects/test1-b1a03/databases/(default)/documents/test2?documentId=test'
```

### 登録データの作成

先ほど取得したデータのnameフィールドと改行削除し、登録用のデータを作成しました。

```json:登録データ(変更前)
{
  "name": "projects/test1-b1a03/databases/(default)/documents/test/22",
  "fields": {
    "string": {
      "stringValue": "hoge"
    },
    "array": {
      "arrayValue": {
        "values": [
          {
            "stringValue": "hoge1"
          },
          {
            "stringValue": "hoge2"
          }
        ]
      }
    },
    "id": {
      "integerValue": "1"
    }
  },
  "createTime": "2019-05-10T10:01:20.999100Z",
  "updateTime": "2019-05-10T10:01:20.999100Z"
}
```


```json:登録データ(変更後)
{"fields":{"string":{"stringValue":"hoge"},"array":{"arrayValue":{"values":[{"stringValue":"hoge1"},{"stringValue":"hoge2"}]}},"id":{"integerValue":"1"}},"createTime":"2019-05-10T10:01:20.999100Z","updateTime":"2019-05-10T10:01:20.999100Z"}
```

### 実行
先ほど作成したリクエストURLと登録データをつなげPOST送信してみます。

```terminal
$ curl -s 'https://firestore.googleapis.com/v1beta1/projects/test1-b1a03/databases/(default)/documents/test2?documentId=test' -X POST -H 'Content-Type: application/json' -d '
{"fields":{"string":{"stringValue":"hoge"},"array":{"arrayValue":{"values":[{"stringValue":"hoge1"},{"stringValue":"hoge2"}]}},"id":{"integerValue":"1"}},"createTime":"2019-05-10T10:01:20.99910
0Z","updateTime":"2019-05-10T10:01:20.999100Z"}'
{
  "name": "projects/test1-b1a03/databases/(default)/documents/test2/test",
  "fields": {
    "string": {
      "stringValue": "hoge"
    },
    "id": {
      "integerValue": "1"
    },
    "array": {
      "arrayValue": {
        "values": [
          {
            "stringValue": "hoge1"
          },
          {
            "stringValue": "hoge2"
          }
        ]
      }
    }
  },
  "createTime": "2019-05-13T01:17:46.832439Z",
  "updateTime": "2019-05-13T01:17:46.832439Z"
}
```

上手くできているようなので、FireConsoleより、FireStoreを確認していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359665/a165d3f0-b1dd-605b-0bc3-572ec349341d.png)

上手く登録することができました！

## シェルを作ってみる
取得と登録の方法がわかったのでシェルを作っていきます。
処理としては、コピー元/先のプロジェクトIDとコレクションIDを引数で渡し、取得APIでデータ取得、取得したデータをループさせてAPI登録するような簡単なシェルを作ってみます。

シェルの作成には、Jsonの処理に[jq](https://stedolan.github.io/jq)を利用しました。
また、日本語にも対応させたいため、[【Docker】ubuntuの環境で日本語入力を可能にする](https://qiita.com/n_oshiumi/items/cfe91c60730f602b38eb)を参考に日本語設定をしました。

以下、作ったシェルです。

```shell:firestoreCopy.sh
#!/bin/bash

# パラメータ数チェック
if [ $# -ne 4 ]; then
  echo "4 parameters are required"
  exit 1
fi

# パラメータ取得
exportProjectId=$1
exportCollectId=$2
importProjectId=$3
importCollectId=$4

# 1あたりの取得件数
pageSizeCnt=10

# ページトークン初期値
nPageToken=""

main (){
   # ページトークンがなくなるまでループ
   while [ "$nPageToken" != "null" ]]; do
      # データ取得/登録処理
      copyData
   done
}

copyData () {
    # データ取得用ルクエスト作成
    exportRequest="curl -s 'https://firestore.googleapis.com/v1/projects/${exportProjectId}/databases/(default)/documents/${exportCollectId}?pageSize=${pageSizeCnt}&pageToken=${nPageToken}'"
    
    # データ取得
    eval $exportRequest > ./tmp.data
    
    # document配下取得
    cat ./tmp.data | jq -cr '.documents' > ./tmp.documents
    
    # ページトークン取得
    nPageToken=$(cat ./tmp.data | jq -cr '.nextPageToken')
    
    # 要素数取得
    len=$(cat ./tmp.documents | jq -cr length)
    
    # 要素分ループ
    for i in $( seq 0 $(($len - 1)) ); do
      # ドキュメント部取得
      cat ./tmp.documents | jq .[$i]  > ./tmp.row
    
      # name取得
      name=$(cat ./tmp.row | jq -r '.name')
    
      # field取得
      cat ./tmp.row | jq -c '.fields' > ./tmp.fields
      
      # createTime、updateTime取得
      createTime=$(cat ./tmp.row | jq -c '.createTime')
      updateTime=$(cat ./tmp.row | jq -c '.updateTime')
    
      # name部より複製用にID取得
      docId=${name##*/}
    
      # 登録リクエスト送信
      eval "curl -s 'https://firestore.googleapis.com/v1beta1/projects/${importProjectId}/databases/(default)/documents/${importCollectId}?documentId=${docId}' -X POST -H 'Content-Type: application/json'  -d '{"fields": `cat ./tmp.fields`,  "createTime": ${createTime},  "updateTime": ${updateTime}}'"
    
    done
}

main
```

## シェルを動かしてみる

それでは実際に動かしていきます。
以下の順にパラメータを設定して実行します！
1. コピー元プロジェクト
2. コピー元コレクション
3. コピー先プロジェクト
4. コピー先コレクション 

```
# sh firestoreCopy.sh test1-b1a03 test test2-7efaa copy-test
{
  "name": "projects/test2-7efaa/databases/(default)/documents/copy-test/1",
  "fields": {
    "string": {
      "stringValue": "hoge"
    },
    "id": {
      "integerValue": "1"
    },
    "array": {
      "arrayValue": {
        "values": [
          {
            "stringValue": "hoge1"
          },
          {
            "stringValue": "hoge2"
          }
        ]
      }
    }
  },
  "createTime": "2019-05-13T03:46:58.336806Z",
  "updateTime": "2019-05-13T03:46:58.336806Z"
}
    ~~省略~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

```

FireConsoleを参照して、データが登録されていることを確認します。
※左がコピー元、右がコピー先のFirestoreになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359665/191da85c-7275-3d67-6182-7a1d8f716d3b.png)

参照元と同じ数、同じドキュメントIDのデータが作成されていることが確認できました!

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359665/1aa481f7-95fc-52f6-3028-8102363fbb26.png)
分かり辛いですが、改行(ho ge1部分)もコピーされていることができました!

# まとめ
簡易的なFirestoreのデータコピーシェルを作成してみました。
ちょっとしたデータのコピーや、既にあるコレクションを複製してバックアップを作りたい。
などの時に利用できればと思います。