FirebaseのCloud Functionsで、Cloud Firestoreによるトリガーじゃない関数でFirestoreにデータを保存する

Firebaseとかいうスーパー便利サービスの使い方の一部を紹介します。

#Cloud Functions

従来、自分で借りたり用意したサーバー上で動かしていたもろもろの処理を、Firebase上に書いてしまうことができます。

サーバー用意しなくていいし、少量なら無料だし、関数をトリガーも使いやすいものがたくさん用意されています。

今日は、`Cloud Functions`を使って、`Analytics for Firebase`へのイベントの投稿をトリガーにして、`Cloud Firestore`にデータを保存するようなfunctionを書いてみようと思います。

###そんなの公式ページにサンプルあるでしょ？

ある…かな？

こちらの[Cloud Functions で可能な処理](https://firebase.google.com/docs/functions/use-cases?hl=ja)には、「`Realtime Database(Cloud Firestoreの前身)`へのデータの追加をトリガーにして、その追加されたデータに何かする」という例はありますが、新しいデータを`Cloud Firestore`に追加する例はパッと見つからなかったです。

#コード

いきなりですがコードです。中身については下で順次説明していきます。

```typescript:functions/src/index.ts
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';//※1 Admin SDK について
admin.initializeApp();//※1 Admin SDK について

exports.recordPostedScore = functions.analytics.event('post_score').onLog((event)=>{
  const firestore = admin.firestore();//※2 timestampについて
  const settings = { timestampsInSnapshots: true };//※2 timestampについて
  firestore.settings(settings);//※2 timestampについて
  const colref = firestore.collection('scores');
  colref.add({
    score: Math.round(Number(event.params.score)),
    date: new Date(),
    device_category: event.user.deviceInfo.deviceCategory,
    device_model: event.user.deviceInfo.deviceModel
  }).then(docref=>{
    console.log("Added ID is: " + docref.id);
  }).catch(e=>console.log(e));
});
```


#AdminSDKについて

```typescript
import * as admin from 'firebase-admin';//※1 Admin SDK について
admin.initializeApp();//※1 Admin SDK について
```

この部分です。

普段クライアント側でコーディングしてる場合、`Firestore`にデータを追加するには、[CollectionReference](https://cloud.google.com/nodejs/docs/reference/firestore/0.19.x/CollectionReference?hl=ja)とか[DocumentReference](https://cloud.google.com/nodejs/docs/reference/firestore/0.19.x/DocumentReference?hl=ja)を作成しないといけないのですが、

その辺のことができるのがこのAdmin SDKです。

こちらの[公式スタートガイド](https://firebase.google.com/docs/functions/get-started?hl=ja#header)に書いてあるように、`admin.initializeApp();`を実行しておく必要があります。`import`の直後にやっておけば間違いないですね。

また、この公式スタートガイドはimport文のかわりにrequire文が書かれており、若干書き方が古いです(2018-12-20現在)。

ターミナルから`firebase init functions`とした場合に作成される`functions/src/index.ts`にはこの記事にあるような書き方でimport文が書かれておりましたので、そちらの方が新しくて間違いないと思います。

##const firestore = admin.firestore();

```typescript
const firestore = admin.firestore();
```

これで、こちらの[Google Cloudのドキュメント](https://cloud.google.com/nodejs/docs/reference/firestore/0.19.x/Firestore?hl=ja)に書いてあるのと同じ`Firestore client`が得られますので、あとは

```typescript
colref = firestore.collection('scores');
```

などとすれば、`CollectionReference`が得られるので、クライアント側でプログラミングしてる時と同じように書けます。

`admin.firestore()`について詳しくはこちら。→[本家リファレンス](https://firebase.google.com/docs/reference/admin/node/admin.firestore?hl=ja)

#timestampについて

`Cloud Firestore`に保存するデータのうち、`timestamp`型のものは、`new Date()`をそのままつっこんではいけないようです。

そのために、こちらの

```typescript
  const firestore = admin.firestore();//※2 timestampについて
  const settings = { timestampsInSnapshots: true };//※2 timestampについて
  firestore.settings(settings);//※2 timestampについて
```

下二行のような設定作業が必要になります。

詳しくはこちらのQiita記事がわかりやすかったです。

[FirestoreのTimestampの仕様変更による警告と、その対処](https://qiita.com/teramotodaiki/items/b3592326579166003102)

#paramsとかdeviceCategoryとかの調べ方

あともう一つ困ったことがあったので、それについての話だけして終わります。

##リファレンスを見るときは、一つずつ、何型のインスタンスが返るのか確認し、その型の使い方を改めて調べることを繰り返す

ということです。

###実際にやってみましょう。

今回は、`Analytics for Firebase`へのイベント投稿をトリガーとして、「そのイベントについてきたscore値」「投稿したユーザーのデバイス情報」を`Cloud Firestore`に保存したいです。

でも、それらの情報が一体どういう形で取得できているのかがわからない！！

ちなみにいきなりですが最終的なコードは以下のようになります。

```typescript
exports.recordPostedScore = functions.analytics.event('post_score').onLog((event)=>{
//...中略...
colref.add({
    score: Math.round(Number(event.params.score)),
    date: new Date(),
    device_category: event.user.deviceInfo.deviceCategory,
    device_model: event.user.deviceInfo.deviceModel
  }//...後略...
);
```

この部分、大枠としては`functions.analytics.event(...).onLog(...)`の引数に、ハンドラ関数を渡す、という形をしています。

`Analytics for Firebase`へのイベント投稿をトリガーとして呼び出される関数は、[こちらの本家の例](https://firebase.google.com/docs/functions/analytics-events?hl=ja)によると、`functions.analytics.event('event_type').onLog((event)=>{});`という書き方をするようなのて、これでいい。

で、そのハンドラ関数の引数に、いろいろな情報が詰まったインスタンスを渡してくれるわけですね。

ところで、そのハンドラ関数の引数は`event`という名前にしてあるわけですが、**この`event`には一体何型のインスタンスが渡っているんでしょうか？**どう書けばそこから情報を取り出せるのでしょうか？

ということで[functions.analyticsのリファレンス](https://firebase.google.com/docs/reference/functions/functions.analytics?hl=ja#.event)を見ると、`functions.analytics.event(...)`というメソッドは
`functions.analytics.AnalyticsEventBuilder`を返すことがわかります。

では、[functions.analytics.AnalyticsEventBuilderのリファレンス](functions.analytics.AnalyticsEventBuilder)を見ると、

そこにやっと`onLog`メソッドがあって、その返り値は`functions.CloudFunction`だそうです。なるほど、これで`Cloud Functions`で実行可能な形になるわけですね。

で、その`onLog`の引数として渡すハンドラ関数の引数は、何型なのかというと、`
functions.analytics.AnalyticsEvent`だそうです。

**やっとこれで`event`の型がわかりました。**

で、
[functions.analytics.AnalyticsEventのリファレンス](https://firebase.google.com/docs/reference/functions/functions.analytics.AnalyticsEvent?hl=ja)を見ると`params`とか`user`とかのプロパティがあって、その中に今回記録したい情報があるらしいことが、**ここまで調べてようやくわかりました**。

さらに、`user`の中にどんな情報があるかは
[functions.analytics.UserDimensions](https://firebase.google.com/docs/reference/functions/functions.analytics.UserDimensions?hl=ja)を見る必要があるし、その中のデバイス情報についてはさらに[
functions.analytics.DeviceInfo](https://firebase.google.com/docs/reference/functions/functions.analytics.DeviceInfo?hl=ja)を見なきゃいけません。そこまで見てやっと返り値が`string or undefined`になります。


サンプルコード一つでポンとわからない場合、この程度のリファレンスの渡り歩きは日常茶飯事ですから、臆せずガンガン調べましょうね〜〜。