0

I have a database structure attempt to deal with the classic events / participants schema. I have events with participants, and participants attend events. I believe for a query that delivers details of all events a participant has attended to scale properly, it is not good practice to query all the events and filter them to find a participant. Comments are welcome on this view. The users are in one collection and the events are in another. So I have added a subEvent subcollection to users to store the document ref for the events they participate in. I now want to retrieve those document references from a query in users and retrieve the detailed event records in the second query in events. Here’s the scheme with the event ids in both.

enter image description here

enter image description here

My first query looks like this:-

const asnapshot = await db.collection("users").doc(_req.params.id).collection("subEvents").get();

And the second:-

const doc = await db.collection("events").doc(results_from_asnapshot).get();

Where results_from_asnapshot is just a placeholder for the document ids from the first. Note I mean ids plural as the first query will return multiple results, and the second one only by one. To deal with this I am attempting to create a result from a snapshot that contains all the events, and then loop though them in the second query to build a complete detailed list of events that the participant has attended. Several problems have emerged that I would like help with. One is the output from the first query that is acceptable in the second, and there seem to be conflicting answers as to what will work see. Some say a string like this will work, and others do not. I had doc.data() is not a function error plaguing me until for no reason, on a test query like this it inexplicably went away.

const doc = await db.collection("events").doc("0adFOpcHZZONGDu3Qw7n").get();

And secondly how to properly loop the second query. And the third issue is why the second query, which is preceded by await, does it warn in the linter that await will be ignored. I know that one query must end before the next can start so why not use await? Here goes my vastly flawed attempt, one of the hundreds that I have tried, you will note it is a node.js firebase function:

const functions = require("firebase-functions");
const express = require("express");

const admin = require("firebase-admin");
admin.initializeApp();

const appWhen = express();
const db = admin.firestore();

appWhen.get("/eventsDetails/:id", async (_req, res) => {
  functions.logger.log("Function started");

const snapshot = await db.collection("users").
doc(_req.params.id).collection("subEvents").get();

const userevents = [];
snapshot.forEach((doc) => {
const id = doc.id;
const data = doc.data().eventID; 
userevents.push({id, ...data});
});
functions.logger.log("userevents", JSON.stringify(userevents));//EDIt Clearer log
const eventlist = [];
userevents.forEach((event) => {
 const doc = db.collection("events").doc(event).get();// Edit removed await
 const id = doc.id;
 const eventData = doc.data();
 functions.logger.log("eventData", eventData);
 eventlist.push({id, ...eventData});
 });
res.status(200).send(JSON.stringify({id: id, ...eventlist}));
});

Any help most welcome.

Marc Anthony B
  • 3,635
  • 2
  • 4
  • 19
user2818170
  • 107
  • 1
  • 10

1 Answers1

0

There are some issues with your code that I will point out.

  1. If you want to read the document in sequence, you cannot use forEach indeed. Just use a modern for … of loop instead, in which await will work as expected:

    for (const event of userevents) {
       ...
    }
    
  2. If you're using a reference field from the result, you only need to execute the reference and get the data.

    const doc = await event.eventID.get();
    
  3. On this line of code: res.status(200).send(JSON.stringify({id: id, ...eventlist}));, I'm not sure where you're getting the variable id as all the variables id are inside a loop in which you'll get an error.


Here's a sample working snippet for your reference:

const functions = require("firebase-functions");
const express = require("express");

const admin = require("firebase-admin");
admin.initializeApp();

const appWhen = express();
const db = admin.firestore();

appWhen.get("/eventsDetails/:id", async (_req, res) => {
  
  functions.logger.log("Function started");

  const snapshot = await db.collection("users").doc(_req.params.id).collection("subEvents").get();

  const userevents = [];

  snapshot.forEach((doc) => {
    const id = doc.id;
    // renamed to eventID as this is an object of reference.
    const eventID = doc.data().eventID;
    // removed ellipsis as this is an object and doesn't need to use Spread syntax
    userevents.push({id, eventID});
  });

  functions.logger.log("userevents", JSON.stringify(userevents));
  
  const eventlist = [];
  // In here, you can see that I didn't use `.foreach`.
  // `await` will not work using `.foreach`
  // If you want to use `.foreach`, you have to use the Promise `.then` instead.
  for (const event of userevents) {
    // Checks if there's a eventID given.
    if (event.eventID) {
      // Use the reference and use `get()` to fetch the data.
      const doc = await event.eventID.get();
      if (doc.exists) {
        const id = doc.id;
        const eventData = doc.data();
        eventlist.push({id, ...eventData});
      } else {
        // doc.data() will be undefined in this case
        console.log("No such document!");
      }
    } else {
        // .. Do something
    }
  }
  // In here, I'm not sure what ID you want to use as you haven't declared the variable `id` globally
  // So using the variable ID will return an error.
  res.status(200).send(JSON.stringify({id: ??, ...eventlist}));
});

When you query for a document that contains a reference type field, the field will show up in a DocumentSnapshot as a DocumentReference type object. You can treat this just like any other DocumentReference that you build on your own. You can call get() on it to get the referred document as you would normally do. You don't have to do anything to parse the path to the document (unless you want to). That's one advantage of using a reference type instead of storing as a string type field as you will be building a new query again to get/fetch a document. You may also want to check the documentation for Firestore DocumentReference for more information.

Marc Anthony B
  • 3,635
  • 2
  • 4
  • 19
  • Great progress thx. All fine up to second log for userevents which shows userevents [{"id":"0adFOpcHZZONGDu3Qw7n"},{"id":"cVlm4ehIrmNlN8ydvIeP"}]; but then fails at the get clause with TypeError: Cannot read properties of undefined (reading 'get'). – user2818170 Sep 21 '22 at 17:14
  • Hi @user2818170 , I've updated my answer. I've changed the variable name and removed the ellipsis from the 1st iteration as it was not necessary to use spread syntax to get the Firestore reference. The updated code should now work. – Marc Anthony B Sep 21 '22 at 23:51
  • Getting the same error. In my tests as a result of your reply, I added a log for event and concluded that event.id isolated the document id correctly. I question your event.evenID.get(); and changed that back to const doc = db.collection("events").doc(event.id).get(); It began not to fail at that point, but the dreaded doc.data() is not a function, which has plagued me from the start, and became the new fail point in the logs. – user2818170 Sep 23 '22 at 05:24
  • What's the data type of your eventID? Can you confirm that its a reference data type? Also, Can you provide your `package.json`. I'll try to reproduce whats happening on your end. – Marc Anthony B Sep 23 '22 at 05:41
  • I think you're a bit confused. If you've used the snippet that I've provided, the `event.id` there will be the document ids of `subEvents` subcollection not the `eventID` field which contains the reference for your document in events collection. Also, i doubt that you're not using the one that I provided. `doc.data() is not a function` will be the result if you did not use `await`. By using `const doc = await db.collection("events").doc(event.id).get(); console.log(doc.data())` will return `undefined` instead as it cannot find/fetch the `event.id` on your `events` collection. – Marc Anthony B Sep 23 '22 at 06:22
  • Check this [image](https://i.stack.imgur.com/w8XGm.png). I collated 3 different logs from the 3 different approaches we're discussing. Also, [here's](https://i.stack.imgur.com/U3d3V.png) a sample firestore collection and documents that I've used that is based on your given sample. – Marc Anthony B Sep 23 '22 at 06:44
  • Both highlighted fields in my original question are firestore references. I am copying and pasting your code to my functions index.js and performing a firebase deploy. I am testing your exact code, and the errors are reported faithfully. EventID is derived and forms a part of the question. – user2818170 Sep 24 '22 at 04:15
  • 1
    It works. Possibly I had a missing eventID in a record in the DB. Might check that to get an error message that reflects that should it happen again. . Removed ?? altogether as I do not need it. Thanks a million. – user2818170 Sep 24 '22 at 16:55
  • If you do have a minute to explain this correct line of code, I would be very grateful. const doc = await event.eventID.get(); I would have expected a line with db.(collection) in it. – user2818170 Sep 24 '22 at 19:30
  • Correction , I expected this:- db.collection ...... – user2818170 Sep 24 '22 at 19:37
  • I see. In that case, you must check first if the eventID exists on the document. I've updated my answer to explain how reference works. – Marc Anthony B Sep 25 '22 at 16:54