🌆

Cloud Firestoreの再帰削除を実現するために長い旅をした話

2022/07/05に公開
2

はじめに

長い旅だったけど、色々発見があったので顛末を記事に残してみる
結論だけ知りたい人は最後を見てください

すべての始まり

https://firebase.google.com/docs/firestore/solutions/delete-collections?hl=ja

解決策: 呼び出し可能な Cloud Functions の関数を使用してデータを削除する

//ドキュメントにあるサンプルコード
const firebase_tools = require('firebase-tools');
const app = admin.initializeApp();
// ...中略
    const path = data.path;
    await firebase_tools.firestore
      .delete(path, {
        project: process.env.GCLOUD_PROJECT,
        recursive: true,
        yes: true,
        token: functions.config().fb.token
      });

なるほど?

困ったことその1: (firebase-toolsの)TS型定義が無い

型定義を含んでなくて @types/firebase-tools も無い

https://github.com/firebase/firebase-tools

TypeScript 96.8%
JavaScript 2.6%
Other 0.6%

え、TSで書いてあるの?型定義無いのナンデ
あ、CLIだからなのね..
でも一応READMEに Using as a Module の記述あるから一応想定してるみたいだし型定義欲しいね
とりあえず //@ts-ignore で雑にしのいでみる

困ったことその2: Unhandled error TypeError: Cannot read property 'token' of undefined

functions.config() // = {}
//.みたいなので、functions.config().fb.tokenはエラー

なるほど?

https://stackoverflow.com/questions/46429630/typeerror-cannot-read-property-token-of-undefined

If you want, you can just paste the token string directly into the code, replacing functions.config().fb.token. The authors of that page probably figured you didn't want to do that, so they suggested you should create a configuration item for it instead.

If you don't want to paste it in, and you actually want to configure the function to get that value from configuration, you will have to set the config on the command line using functions:config:set like this:

firebase functions:config:set fb.token=<YOUR-TOKEN>

The deploy your function again so it can use that value.

If you found the documentation confusing, I suggest using the "send feedback" link at the top right of the page.

token自分で発行しなきゃいけないのね、なるほど

firebase login:ci

いつものね でも..

firebase login:ci の token使いたくない問題

有効期限切れになるから、サービスアカウント使いたいね

Firebase Admin Node.js SDK のレポジトリ見てたらトークン取れそうな関数生えてたので

https://github.com/firebase/firebase-admin-node/blob/72928b5fea3cad303361494a67de2ea786bb4171/src/app/credential-internal.ts#L93-L197

参考:

https://cloud.google.com/docs/authentication/production?hl=ja

いつものこれでJSON発行して..

import * as firebase_tools from "firebase-tools";
const app = admin.initializeApp();
// ...中略
+   const serviceAccount = require("../hoge-ffffffffffff.json");
+   const accessToken = await admin.credential
+         .cert(serviceAccount)
+         .getAccessToken();
    const path = data.path;
    await firebase_tools.firestore
      .delete(path, {
        project: process.env.GCLOUD_PROJECT,
        recursive: true,
        yes: true,
-       token: functions.config().fb.token
+       token: accessToken.access_token
      });

これでどうじゃ!forceが足りない?なるほど?

import * as firebase_tools from "firebase-tools";
const app = admin.initializeApp();
// ...中略
    const serviceAccount = require("../hoge-ffffffffffff.json");
    const accessToken = await admin.credential
          .cert(serviceAccount)
          .getAccessToken();
    const path = data.path;
    await firebase_tools.firestore
      .delete(path, {
        project: process.env.GCLOUD_PROJECT,
        recursive: true,
        yes: true,
+       force: true,
        token: accessToken.access_token
      });

動いた、わーい。

Admin SDK が内部的に使ってるトークン使えばもう少しスマートに書けるのでは説

admin.initializeApp();

これが..

https://github.com/firebase/firebase-admin-node/blob/72928b5fea3cad303361494a67de2ea786bb4171/src/app/lifecycle.ts#L32-L36

見つけたー

import * as firebase_tools from "firebase-tools";
const app = admin.initializeApp();
// ...中略
-   const serviceAccount = require("../hoge-ffffffffffff.json");
-   const accessToken = await admin.credential
-         .cert(serviceAccount)
-         .getAccessToken();
+   const accessToken = await app.options.credential!.getAccessToken();
    const path = data.path;
    await firebase_tools.firestore
      .delete(path, {
        project: process.env.GCLOUD_PROJECT,
        recursive: true,
        yes: true,
-       force: true,
        token: accessToken.access_token
      });

ヽ(゚∀゚)メ(゚∀゚)メ(゚∀゚)ノ

ここで発覚する衝撃の事実: Admin SDKで再帰削除できる(10.x)

え!

https://googleapis.dev/nodejs/firestore/latest/Firestore.html#recursiveDelete

memo: ドキュメントのバージョン表記は @google-cloud/firestore 、10.xは firebase-admin の話..ドキュメントは同じ。

- import * as firebase_tools from "firebase-tools";
const app = admin.initializeApp();
+ const db = admin.firestore();
// ...中略
-   const accessToken = await app.options.credential!.getAccessToken();
    const path = data.path;
-   await firebase_tools.firestore
-     .delete(path, {
-       project: process.env.GCLOUD_PROJECT,
-       recursive: true,
-       yes: true,
-       force: true,
-       token: accessToken.access_token
-     });
+   const docRef = db.doc(path);
+   await db.recursiveDelete(docRef);

大改造ビフォーアフター

完成品

const app = admin.initializeApp();
const db = admin.firestore();
// ...中略
    const docRef = db.doc(path);
    await db.recursiveDelete(docRef);

なるほどね?

最後に

時間かえしt(ry
というか最初のfb.tokenの件は前にも同じことに詰まったような気がしてきた(ぁ

備忘録、そして同じことに困った誰かに届くことを願って..。

Discussion

monomono

あ、え?ドキュメントに、載ってな...(9.xから更新されてない)
https://googleapis.dev/nodejs/firestore/latest/DocumentReference.html

こちらに載っています:
https://googleapis.dev/nodejs/firestore/latest/Firestore.html#recursiveDelete

Yukimasa FunaokaYukimasa Funaoka

あ、確かに。ありがとうございます。
(普通にDocumenentReferenceに生えてる訳ない。書いたとき脳味噌溶けてたかも。間違い情報書くの割と罪深いと思ってるのでほんと反省。今度から日をおいて見直すようにしよう..バージョンは..あ、これはfirrebase-adminの話だけどドキュメントは@google-cloud/firestore基準になってるからね..わかりにくいからついでに修正しよう..)