Home > Software engineering >  Firebase Firestore cloud functions: filtering documents by date conditions in node.js
Firebase Firestore cloud functions: filtering documents by date conditions in node.js

Time:04-05

I need to filter documents in Firestore by date within a cloud function using node.js. I'd like to return only those documents that have a timestamp (or date string) more than 30 days from the current date in a field named last_used. At present, not all documents have this field. One method I have thought about employing is writing a trigger function to give each document a last_used field and set the timestamp to 01/01/2022, run this once and then the scheduled function can update the field when required with the current date. The scheduled function currently selects a player document at random each day, moves the required fields into the current-live-player collection and updates the last_used field for the selected player document within available-players.

Context: I'm building a game in which a player document is selected at random each day from the available-players collection and placed into the current-live-player collection. current-live-player is made up of just this one document and updated each day at 00:00 London time. I only want Firestore to select a player document from available-players that meets my aforementioned condition: the date within last_used is more than 30 days prior to the current date.

This is what I have so far:

const functions = require("firebase-functions");
const admin = require("firebase-admin");
const { firestore } = require("firebase-admin");
admin.initializeApp();

const db = admin.firestore();
// Potential trigger function to give all player objects a last_used field
exports.addLastUsedToAll = functions.https.onRequest((request, response) => {
  // Logic to add a last_used field with a date of 01/01/2022
});

// Select a player function that runs every day at 00:00 London time
exports.pickCurrentLivePlayer = functions.pubsub.schedule("0 0 * * *")
    .timeZone("Europe/London")
    .onRun(async () => {

      // select a random player from available players (uses the auto-generated IDs)
      // ifelse catches rare cases that could cause an error
      const availablePlayers = db.collection("available-players");
      const key = availablePlayers.doc().id;
      
      availablePlayers.where(admin.firestore.FieldPath.documentId(), '>=', key).limit(1).get()
      .then(snapshot => {
          if(snapshot.size > 0) {
              snapshot.forEach(doc => {
                console.log(doc.id, '=>', doc.data());
                console.log('snapshot.size > 0 ', doc.id, '=>', doc.data());
                // replace live player
                const newPlayerData = doc.data();
                const app_first_name = newPlayerData.app_first_name;
                const uid = newPlayerData.uid;
                db.collection("live-player").doc("current-live-player").set({app_first_name: app_first_name, uid: uid});
                // set selected player's last_used value to current time
                db.collection("available-players").doc(uid).update({
                    last_used: admin.firestore.Timestamp.now()          
                })
              });
          }
          else {
              const player = availablePlayers.where(admin.firestore.FieldPath.documentId(), '<', key).limit(1).get()
              .then(snapshot => {
                  snapshot.forEach(doc => {
                      console.log(doc.id, '=>', doc.data());
                      console.log('snapshot.size > 0 ', doc.id, '=>', doc.data().name);
               // replace live player
                const newPlayerData = doc.data();
                const app_first_name = newPlayerData.app_first_name;
                const uid = newPlayerData.uid;
                db.collection("live-player").doc("current-live-player").set({app_first_name: app_first_name, uid: uid});
                // set selected player's last_used value to current time
                db.collection("available-players").doc(uid).update({
                    last_used: admin.firestore.Timestamp.now()
                })
                  });
              })
              .catch(err => {
                  console.log('Error getting documents', err);
              });
          }
      })
      .catch(err => {
          console.log('Error getting documents', err);
      });
      return null;
    });

I hope that all makes sense, any help would be greatly appreciated. I know that it can be tricky to convert timestamps and then filter by date in javascript, but I'd like to use the Firestore timestamp to ensure that the functions are always running off the server time.

Edit:

In response to Frank van Puffelen's answer, the timestamp calculations are now working and each document in available players now has a last_used field populated with a timestamp. I now need to query available_players to show those documents in which the timestamp is more than 30 days ago. The issue now is that I cannot query the collection before running my random selection. The order should be: 1) calculate today's date minus 30 days; 2) query available_players for all documents with a timestamp in the last_used field more than 30 days in the past; 3) generate a key from this filtered selection; 4) query the filtered selection to give me one random document and process the rest of the update logic etc. It falls down where the filtered data is now a query snapshot rather than a collection reference and the code I have no longer runs (at least I think that's the issue!). The code worked perfectly before attempting to add this 'filter by date' functionality. I've provided a minimal repro here:

// calculate time interval
const now = admin.firestore.Timestamp.now();
const intervalInMillis = 30 * 24 * 60 * 60 * 1000;
const cutoffTime = admin.firestore.Timestamp.fromMillis(now.toMillis() - intervalInMillis);

// get collectionReference and filter to those more than 30 days ago
const allPlayers = db.collection("available-players");
const availablePlayers = await allPlayers.where('last_used', '<=', cutoffTime).get();

// generate key
const key = availablePlayers.doc().id;

// select a random player from the filtered selection
availablePlayers.where(admin.firestore.FieldPath.documentId(), '>=', key).limit(1).get()
      .then(snapshot => {
..etc etc

I somehow need to be able to query the data to filter it by date and then run the random selection chunk of code.

CodePudding user response:

To get the players that were last used more than 30 days ago would be something like this:

const availablePlayers = db.collection("available-players");
const now = admin.firestore.Timestamp.now();
const intervalInMillis = 30 * 24 * 60 * 60 * 1000;
const cutoffTime = admin.firestore.Timestamp.fromMillis(now.toMillis() - intervalInMillis);

const query = availablePlayers.where(last_used, "<=", cutoffTime);
  • Related