Search code examples
node.jsfirebase-realtime-databasegoogle-cloud-functionsfirebase-admin

Firebase Admin in Cloud Functions data manipulation problem


Firebase Realtime Database structure

Database organizations

freepacks contains 2 important elements:

  1. current, that is the quizpack ID that I will download from the mobile app (retrieving it from quizpacks).
  2. history, that is a node where I add all the quizpacks ID that I put in current over time with a scheduled function in Cloud Functions.

What I need to do EVERY TIME THE CLOUD FUNCTION EXECUTES

Step 1: Add value of current in history with the current timestamp.

Step 2: Substitute the current value with another quizpack ID that is not in history.

Done

How I tried to accomplish this target

const functions = require('firebase-functions');
const admin = require('firebase-admin');

admin.initializeApp();

exports.scheduledFunction = functions.pubsub.schedule('* * * * *').onRun((context) => {

    // Current timestamp
    const dt = new Date();
    const timestamp = `${
        (dt.getMonth()+1).toString().padStart(2, '0')}/${
        dt.getDate().toString().padStart(2, '0')}/${
        dt.getFullYear().toString().padStart(4, '0')} ${
        dt.getHours().toString().padStart(2, '0')}:${
        dt.getMinutes().toString().padStart(2, '0')}:${
        dt.getSeconds().toString().padStart(2, '0')}`

    // Download the entire node 'freepacks'
    return admin.database().ref('quiz/freepacks').once('value').then((currentSnap) => {
        const currentPack = currentSnap.val().current;

        // Add current to history
        admin.database().ref('quiz/freepacks/history/' + currentPack).set(timestamp);
        
        // Download entire history node
        admin.database().ref('quiz/freepacks/history').once('value').then((historySnap) => {
            const history = historySnap.val();
            console.log('HISTORY: ' + history);

            // Download entire quizpacks node
            admin.database().ref('quiz/quizpacks').once('value').then((quizpacksSnap) => {
                for(quizpack in Object.keys(quizpacksSnap.val())) {
                    console.log('ITERANDO: ' + quizpack);
                    // Add the new current if it isn't in history
                    if (historySnap[quizpack] == undefined) {
                        admin.database().ref('quiz/freepacks/current').set(quizpack);
                        break;
                    }
                }
            });

        })

    });
    
});

What I get from the previous code

Starting point:

Starting point

First execution: history updates well but updating current didn't work

First execution

Second execution ad so on... current doesn't update anymore (it is stuck on 0)

My experience with JavaScript and Firebase Admin is ~0... What is the problem with my code? Thanks in advance for the help!


Solution

  • First thing is all read/write operations return promises hence you should handle them. In this answer I used async-await syntax. .ref("quiz/freepacks") fetches the complete node i.e. current and the history so you don't have to query the history node again as in original code. Other changes are just Javascript tweaks such as using .find() instead of for-loop and so on..

    Try changing your function to:

    exports.scheduledFunction = functions.pubsub
      .schedule("* * * * *")
      .onRun(async (context) => {
        // Getting Date
        const dt = new Date();
        const timestamp = `${(dt.getMonth() + 1).toString().padStart(2, "0")}/${dt
          .getDate()
          .toString()
          .padStart(2, "0")}/${dt.getFullYear().toString().padStart(4, "0")} ${dt
          .getHours()
          .toString()
          .padStart(2, "0")}:${dt.getMinutes().toString().padStart(2, "0")}:${dt
          .getSeconds()
          .toString()
          .padStart(2, "0")}`;
    
        // Download the entire node 'freepacks'
        const currentSnap = await firebase
          .database()
          .ref("quiz/freepacks")
          .once("value");
    
        // Checking current free pack ID and array of previous free packs
        const currentPack = currentSnap.val().current || "default";
        const freeHistoryIDs = [
          ...Object.keys(currentSnap.val().history || {}),
          currentPack,
        ];
    
        // Add current free pack to free history
        await firebase
          .database()
          .ref("quiz/freepacks/history/" + currentPack)
          .set(timestamp);
    
        // Download entire quizpacks node
        const quizpacksSnap = await firebase
          .database()
          .ref("quiz/quizpacks")
          .once("value");
    
        const quizPackIDs = Object.keys(quizpacksSnap.val() || {});
    
        const newPack = quizPackIDs.find((id) => !freeHistoryIDs.includes(id));
        console.log(newPack);
        if (!newPack) {
          console.log("No new pack found")
        }
        return firebase.database().ref("quiz/freepacks/current").set(newPack || "none");
      });