【Firebase】アカウント削除の後始末にあたりトランザクションを対応した

投稿者:

本日こそ最終回

次の課題へ掛かります。

https://myapp-7bf76.web.app/#top

トランザクションを使う前

アカウント削除時に
・全体チャットデータの削除
・firestoreに出したプロフィール画像他の参照可能データの削除
これらを対応しなくてはならず。
以下のように記述していたら、案の定うまくいかなかったのです。

// アカウント削除
document.querySelector("#remove_account_form").addEventListener("submit", function(event) {
  if(confirm('アカウントを削除します。本当によろしいですか?')){
    document.querySelector('#remove_account_form button').setAttribute('disabled', true);
    document.querySelector('#remove_account_form button').innerText = '削除中...';

    var user = firebase.auth().currentUser;
    var credential = firebase.auth.EmailAuthProvider.credential(
      user.email,
      getElementValue('remove_account_password')
    );

    user.reauthenticateWithCredential(credential).then(function() {
      // User re-authenticated.

      user.delete().then(function() {
        var db = firebase.firestore();
        // チャットデータを削除
        var openchatsRef = db.collection("openchats");
        var query = openchatsRef.where("uid", "==", user.uid);
        query.get()
        .then(function(querySnapshot) {
          querySnapshot.forEach(function(doc) {

            db.collection("openchats").doc(doc.id).delete().then(function() {
              console.log("Document successfully deleted!");
            }).catch(function(error) {
              console.error("Error removing document: ", error);
            });
            // // doc.data() is never undefined for query doc snapshots
            // console.log(doc.id, " => ", doc.data());
          });
        })
        .catch(function(error) {
          console.log("Error getting documents: ", error);
        });

        // プロフィールデータを削除
        db.collection("users").doc(user.uid).delete().then(function() {
          console.log("Document successfully deleted!");
        }).catch(function(error) {
          console.error("Error removing document: ", error);
        });

        alert('アカウントを削除しました。ご利用ありがとうございました。');
        firebase.auth().signOut();
      }).catch(function(error) {
        var errorCode = error.code;
        var errorMessage = error.message;
        alert(errorCode + ', ' + errorMessage);
      });

    }).catch(function(error) {
      var errorCode = error.code;
      var errorMessage = error.message;
      alert(errorCode + ', ' + errorMessage);
    });
  }

  event.preventDefault();
}, false);

全体チャットから削除する対象のdocumentを検索してきてforEachで削除するまでは正しかったのですが、そちらの処理が終わる前にsignOutが走ってしまいます。

これを解決する為にはトランザクションを使います。

トランザクションを使ってみます

https://firebase.google.com/docs/firestore/manage-data/transactions?hl=ja#%E3%82%A6%E3%82%A7%E3%83%96

但し公式のサンプルはdocument idが指定されていますが、私の場合は検索してループさせないとなりませんでした。

// Create a reference to the SF doc.
var sfDocRef = db.collection("cities").doc("SF");

// Uncomment to initialize the doc.
// sfDocRef.set({ population: 0 });

return db.runTransaction(function(transaction) {
    // This code may get re-run multiple times if there are conflicts.
    return transaction.get(sfDocRef).then(function(sfDoc) {
        if (!sfDoc.exists) {
            throw "Document does not exist!";
        }

        // Add one person to the city population.
        // Note: this could be done without a transaction
        //       by updating the population using FieldValue.increment()
        var newPopulation = sfDoc.data().population + 1;
        transaction.update(sfDocRef, { population: newPopulation });
    });
}).then(function() {
    console.log("Transaction successfully committed!");
}).catch(function(error) {
    console.log("Transaction failed: ", error);
});

完遂した実装は以下の通り。
最初に22行目にもreturnをつけるのに気がつかず、37行目の公開プロフィール情報の削除の方が先に動いてしまい元の木阿弥でした。
22行目にreturnを付け、定義したtransactionが全て完了した段階でthenに飛ばすことができました。

// アカウント削除
document.querySelector("#remove_account_form").addEventListener("submit", function(event) {
  event.preventDefault();
  if(confirm('アカウントを削除します。本当によろしいですか?')){
    document.querySelector('#remove_account_form button').setAttribute('disabled', true);
    document.querySelector('#remove_account_form button').innerText = '削除中...';

    var user = firebase.auth().currentUser;
    var db = firebase.firestore();
    // チャットデータを削除
    // var openchatsRef = db.collection("openchats");

    var user = firebase.auth().currentUser;
    var credential = firebase.auth.EmailAuthProvider.credential(
      user.email,
      getElementValue('remove_account_password')
    );

    const openchatsRef = db.collection('openchats'); // .where("uid", "==", user.uid)
    const usersRef = db.collection('users').doc(user.uid);

    return db.runTransaction(function(transaction) {
      // 全体チャットデータの削除
      var query = openchatsRef.where("uid", "==", user.uid);
        query.get()
        .then(async function(querySnapshot) {
          querySnapshot.forEach(function(doc) {

            return transaction.get(openchatsRef.doc(doc.id)).then(function(sfDoc) {
              transaction.delete(openchatsRef.doc(doc.id));
            });
          });
          // console.log(querySnapshot.docs);
        });
      
	  // 公開プロフィール情報削除
      return transaction.get(usersRef).then(function(sfDoc) {
        transaction.delete(usersRef);
      });
    }).then(function() {

      // storageのプロフィール画像削除
      var storageRef = firebase.storage().refFromURL(user.photoURL);

      // Delete the file
      storageRef.delete().then(function() {
        // File deleted successfully
        console.log('File deleted successfully');

        user.reauthenticateWithCredential(credential).then(function() {
          user.delete().then(function() {
            alert('アカウントを削除しました。ご利用ありがとうございました。');
            firebase.auth().signOut();
            location.reload();
          });
        });
      }).catch(function(error) {
        // Uh-oh, an error occurred!
      });

    }).catch(function(error) {
      console.log(error.line);
      console.log("Transaction failed: ", error);
    });

  }

  event.preventDefault();
}, false);

加えて、43行目と46行目でしれっとプロフィール画像の処理を追加し、ひととおりやりたいことはやれたので、エキシビジョン的に動く状態で公開しておきます。
なおgithubはこちらに公開しています。

https://github.com/mayarin/firebase-sample

コメントを残す