Christoffer
Christoffer

Reputation: 2411

Get the first document from Firestore subcollection with primary document?

I am using Node.js (which I am very new at) and Google Cloud Firestore database to save documents according to:

One Users document, i.e. a User, has many Tweets in a subcollection 'Tweets'. I am interested in retrieving a User together with the last Tweet in the subcollection so I get a JSON-file like this. In other words, this is what I want to get:

users: {
    {
    name:'john',
    twitter_username:'johnny',
    tweets: {
    text: "I am called johnny, this is my tweet",
    created_at: "2021-06-29 12:00:00"
    },
    },
    {
    name:'anne',
    twitter_username:'anne',
    tweets: {
    text: "I am called anne, this is another tweet",
    created_at: "2019-06-28 12:00:00"
    },
    }
}

I have this function:

   function getUserData() {
       return db.collection('users').get()
            .then((querySnapshot) => {
                var docs = querySnapshot.docs.map(doc => [doc.data(), doc.id]);         
                //console.log(docs);
                return docs

which, if I could fetch and replace doc.id (i.e. the User document ID) with the last tweet, would solve it I guess. But how can I do that?

Any other solution, possibly with for loops, would be fine as well. I have spent hours on this seemingly easy problem but can't get it to return both the User-data and the tweet-data.

Edit 2:

I realized I could do this:

function getUserData() {
   return db.collection('users').get()
        .then((querySnapshot) => {
            var docs = querySnapshot.docs.map(doc => [doc.data(), doc.id, getStuff(doc.id)])
            console.log(docs)
            return docs                          
        });
}

function getStuff(doc_id) {
   return db.collection('users').doc(doc_id).collection('tweets').limit(1).get()
        .then((querySnapshot) => {
            var docs = querySnapshot.docs.map(doc => doc.data());         
            console.log("TWEETS", doc_id, docs[0]['text']);
            return docs[0]['text']
        });   
}

which produces a log result as:

TWEETS DAU86mxIhmD6qQQpH4F God’s country!
TWEETS JQHTO0jUjAodMQMR6wI I’m almost there Danny! 

from the getStuff-function.

The only issue now is that I can't get the map function to wait for getStuff so the return docs return a Promise { <pending> } for getStuff(doc.id).

I am not to familiar with Promises and await/async and I can't get that to work. Solving this Promise pending -> twitter text would then solve my problem. How do I do that?

Upvotes: 0

Views: 169

Answers (1)

Tarik Huber
Tarik Huber

Reputation: 7388

If you want to get the data of a single user you could write the code like this:

const getUserData = async (userUid) => {
  const userSnap = await db.collection("users").doc(userUid).get();
  const tweetSnaps = await db
    .collection("tweets")
    .orderBy("created_at", "desc")
    .limit(1)
    .get();

  let tweet = {};

  tweetSnaps.forEach((doc) => {
    tweet = doc.data();
  });

  return {
    ...userSnap.data(),
    ...tweet,
  };
};

We first get the user and then query for the last tweet and get that. We sort the tweets collection by created_at and limit it for a single doc.

If you want to get the same data for all users at once we would need to change the code a little bit but the logic would be the same.

If the data is saved in separate collections you can't get them in a single database request.

UPDATE for Edit 2

Here your code how it should look like with correct async/await:

const getUserData = async () => {
  const querySnapshot = await db.collection("users").get();

  const docs = querySnapshot.docs;

  for (let i = 0; i < docs.length; i++) {
    const element = docs[i];
    doc.data(),
    doc.id,
    await getStuff(doc.id),
  }

  console.log(docs);
  return docs;
};

const getStuff = async (doc_id) => {
  const querySnapshot = await db
    .collection("users")
    .doc(doc_id)
    .collection("tweets")
    .limit(1)
    .get();

  var docs = querySnapshot.docs.map((doc) => doc.data());
  console.log("TWEETS", doc_id, docs[0]["text"]);
  return docs[0]["text"];
};

Upvotes: 1

Related Questions