0
votes

Use case:

Users can upload images using UI (mobile, web). Firebase storage and insertion of database record for the file is done in UI. Users can upload a set number of files say for testing max 5. Solution is achieved through Firebase Storage to store files and Firebase Database to track the URLs of the image for future use in other screens.

I have a google cloud function that gets triggered when a user uploads an image in frontend to firebase storage. Once the file is uploaded I create a firebase database record under the uid. the google cloud function triggers onCreate().

Structure of Database

Users/{uid}/images/
{ name: file1.jpg, date_created: "2017-11-01", downloadURL : "//...."}

For every record creation I check the count of records under uid/images node and check if the snapshot is greater than MAX_LIMIT. If it is greater than MAX_LIMIT I attempt to remove the record from the database and in another GCF (listening to .onDelete() Trigger) I delete the firebase storage file.

The issue is Once the record count gets more than MAX_LIMIT... the snapshot behaves strange. It iteratively deletes all the records thus triggering GCF Delete function and removing all files from storage.

No issues with GCF delete function for storage.. it works charm... and removes all files from storage due to the code behavior in GCF onCreate DB function.

To keep short putting below just the function that is logic trouble.

//var database = admin.database(); passed to function
exports.handler = function(event, database, esClient)
{
  var usersRef = database.ref('users');
  console.log('inside Backend Function1 - 
  checkImageCounterFunction');
  console.log( 'event data =' + JSON.stringify(event.data.val()) );
  console.log('event data length =' + event.data.numChildren());
  console.log('event.params.uid =' + event.params.uid );

  var objLength = 0;
  usersRef.child(event.params.uid+'/images').once('value', 
  function(snapshot) {
      console.log('Count: ' + snapshot.numChildren());
      objLength = snapshot.numChildren();
      console.log('database ref length =' + objLength);
     if(objLength > 0) {
        if(objLength > serverConfig.max_image_counter){
          console.log('Maximum number of images upload found. 
          Deleting old images and records.');
          var readDBPromise = new Promise(function (resolve, reject) 
          {
           usersRef.child(event.params.uid+'/images')
           .orderByChild('date_created')
           .limitToFirst(objLength - serverConfig.max_image_counter)
           .on("value", function(snapshot) {
                console.log('snapshot.val() 
                      =>'+JSON.stringify(snapshot.val()) );
             snapshot.forEach(function(data) {
                     console.log('snapshot.for.data value 
                       ='+JSON.stringify(data) );
              console.log('item Key = ' + data.key + ' val ='+ 
              JSON.stringify(data.val()));
          usersRef.child(event.params.uid+'/images/'+data.key)
           .remove(function (error) {
              if (!error)
                  console.log('Success - Firebase Database remove 
                     {'+data.val().name+'}');
              else
                  console.log('Error - Firebase Database remove 
                     {'+data.val().name+'}');
              });
          });
          resolve();
        });
      });
      readDBPromise.then(function (result){
        console.log('Returning from function!');
        usersRef.child(event.params.uid+'/image_counter')
        .set(serverConfig.max_image_counter);
        return;
      });
    } //end if objLength > MAX
    else
      return usersRef.child(event.params.uid+'/image_counter')
              .set(objLength);
    }
    else
      console.log('objLength is 0 or negative = '+objLength);
      return;
    });
  //return;
 };
1
Logs - if any additional info required - 8:02:40.559 AM Success - Firebase Database remove {DashLanding1.jpg} 8:02:40.358 AM Success - Firebase Database remove {championship-profit-loss-2009-11.jpg} 8:02:39.958 AM info checkImageCounterFunction Success - Firebase Database remove {CashFlow.png} 8:02:39.358 AM Success - Firebase Database remove {BalanceSheet Report.png} 8:02:39.958 AM Success - Firebase Database remove {CashFlow.png}Vinayak Vanarse
I'm wondering about the use of on() to attach a listener in this line: .on("value", function(snapshot) {. Isn't once() what you need? It looks like later in the code you remove() sub-values from the location, which will cause the listener to fire multiple times.Bob Snyder
Yes you rigtht! thanks for the oversight. I totally missed that I am using .on() rather than .once(). Also, I am changing the logic as snapshot.forEach() gets called multiple times. Not sure why but I also noticed that my logic is redundant. As soon as the file number reaches MAX_LIMIT + 1 there will be an attempt to remove oldest file in database/storage. So, snapshot will always be just one record from DB. No need to perform forEach() loop.Vinayak Vanarse
One more issue found... I am watching google cloud logs and not sure if they are in realtime but google cloud function finishes and prints console.log last statement but the file remove success comes on google console.logs later. Not sure if it is an issueVinayak Vanarse
The query, set, and remove operations you are performing are asynchronous and return a Promise. You need to chain them together and return a Promise as the result of the Cloud Function trigger. It doesn't appear you are doing that.Bob Snyder

1 Answers

0
votes

From Bob Snyder's comment

The query, set, and remove operations you are performing are asynchronous and return a Promise. You need to chain them together and return a Promise as the result of the Cloud Function trigger. It doesn't appear you are doing that