Firebase Real Time Database Structure for File Upload

走远了吗. 提交于 2019-12-31 04:11:38

问题


I am working on my first Firebase project using AngularFire2. Below is the overall design of my learning project.

  • Users uploads photos and it's stored in the Firebase storage as images.
  • The uploaded photos are listed in the homepage sorted based on timestamp.

Below is the structure that I have now when I started. But I feel difficulty when doing joins. I should be able to get user details for each uploads and able to sort uploads by timestamp.

User:
- Name
- Email
- Avatar

Uploads:
  - ImageURL
  - User ID
  - Time

I read few blogs de-normalising the data structure. For my given scenario, how best can i re-model my database structure?

Any example for creating some sample data in the new proposed solution will be great for my understanding.

Once the image upload is done, I am calling the below code to create an entry in the database.

addUpload(image: any): firebase.Promise<any> {
  return firebase.database().ref('/userUploads').push({
    user: firebase.auth().currentUser.uid,
    image: image,
    time: new Date().getTime()
  });
}

I am trying to join 2 entities as below. i am not sure how can I do it efficiently and correctly.

 getUploads(): any {
    const rootDef = this.db.database.ref();
    const uploads = rootDef.child('userUploads').orderByChild('time');

    uploads.on('child_added',snap => {
      let userRef =rootDef.child('userProfile/' + snap.child('user').val());
      userRef.once('value').then(userSnap => {
        ???? HOW TO HANDLE HERE
      });
    });

return ?????;
}

I would like to get a final list having all upload details and its corresponding user data for each upload.


回答1:


This type of join will always be tricky if you write it from scratch. But I'll try to walk you through it. I'm using this JSON for my answer:

{
  uploads: {
    Upload1: {
      uid: "uid1",
      url: "https://firebase.com"
    },
    Upload2: {
      uid: "uid2",
      url: "https://google.com"
    }
  },
  users: {
    uid1: {
      name: "Purus"
    },
    uid2: {
      name: "Frank"
    }
  }
}

We're taking a three-stepped approach here:

  1. Load the data from uploads
  2. Load the users for that data from users
  3. Join the user data to the upload data

1. Load the data uploads

Your code is trying to return a value. Since the data is loaded from Firebase asynchronously, it won't be available yet when your return statement executes. That gives you two options:

  1. Pass in a callback to getUploads() that you then call when the data has loaded.
  2. Return a promise from getUploads() that resolves when the data has loaded.

I'm going to use promises here, since the code is already difficult enough.

function getUploads() {
  return ref.child("uploads").once("value").then((snap) => {
    return snap.val();
  });
}

This should be fairly readable: we load all uploads and, once they are loaded, we return the value.

getUploads().then((uploads) => console.log(uploads));

Will print:

{
  Upload1 {
    uid: "uid1",
    url: "https://firebase.com"
  },
  Upload2 {
    uid: "uid2",
    url: "https://google.com"
  }
}

2. Load the users for that data from users

Now in the next step, we're going to be loading the user for each upload. For this step we're not returning the uploads anymore, just the user node for each upload:

function getUploads() {
  return ref.child("uploads").once("value").then((snap) => {
    var promises = [];
    snap.forEach((uploadSnap) => {
      promises.push(
         ref.child("users").child(uploadSnap.val().uid).once("value")
      );
    });
    return Promise.all(promises).then((userSnaps) => {
      return userSnaps.map((userSnap) => userSnap.val());
    });
  });
}

You can see that we loop over the uploads and create a promise for loading the user for that upload. Then we return Promise.all(), which ensures its then() only gets called once all users are loaded.

Now calling

getUploads().then((uploads) => console.log(uploads));

Prints:

[{
  name: "Purus"
}, {
  name: "Frank"
}]

So we get an array of users, one for each upload. Note that if the same user had posted multiple uploads, you'd get that user multiple times in this array. In a real production app you'd want to de-duplicate the users. But this answer is already getting long enough, so I'm leaving that as an exercise for the reader...

3. Join the user data to the upload data

The final step is to take the data from the two previous steps and joining it together.

function getUploads() {
  return ref.child("uploads").once("value").then((snap) => {
    var promises = [];
    snap.forEach((uploadSnap) => {
      promises.push(
         ref.child("users").child(uploadSnap.val().uid).once("value")
      );
    });
    return Promise.all(promises).then((userSnaps) => {
      var uploads = [];
      var i=0;
      snap.forEach((uploadSnap) => {
        var upload = uploadSnap.val();
        upload.username = userSnaps[i++].val().name;
        uploads.push(upload);
      });
      return uploads;
    });
  });
}

You'll see we added a then() to the Promise.all() call, which gets invoked after all users have loaded. At that point we have both the users and their uploads, so we can join them together. And since we loaded the users in the same order as the uploads, we can just join them by their index (i). Once you de-duplicate the users this will be a bit trickier.

Now if you call the code with:

getUploads().then((uploads) => console.log(uploads));

It prints:

[{
  uid: "uid1",
  url: "https://firebase.com",
  username: "Purus"
}, {
  uid: "uid2",
  url: "https://google.com",
  username: "Frank"
}]

The array of uploads with the name of the user who created that upload.

The working code for each step is in https://jsbin.com/noyemof/edit?js,console




回答2:


I did the following based on Franks answer and it works. I am not sure if this is the best way for dealing with large number of data.

getUploads() {

    return new Promise((resolve, reject) => {
      const rootDef = this.db.database.ref();
      const uploadsRef = rootDef.child('userUploads').orderByChild('time');
      const userRef = rootDef.child("userProfile");
      var uploads = [];

      uploadsRef.once("value").then((uploadSnaps) => {

        uploadSnaps.forEach((uploadSnap) => {

          var upload = uploadSnap.val();

          userRef.child(uploadSnap.val().user).once("value").then((userSnap) => {
            upload.displayName = userSnap.val().displayName;
            upload.avatar = userSnap.val().avatar;
            uploads.push(upload);
          });
        });

      });

      resolve(uploads);
    });

}


来源:https://stackoverflow.com/questions/44220215/firebase-real-time-database-structure-for-file-upload

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!