0

The function tries to retrieve all the users and then loops through each one and finds the ones that are premium and saves it to an array. For some reason the array is returned empty but the console log displays the correct value, i think the prblem might be that the auth user get is a async function but I couldnt figure that out.

Function:

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();

exports.logPremiumUsers = functions.https.onRequest((req, res) => {
  const usersRef = admin.firestore().collection('users');
  const query = usersRef //.where('time', '>=', 100).where('time', '<=', 1000);
  const premiumUsers = [];
  query.get()
    .then((snapshot) => {
      snapshot.forEach(async (doc) => {
        const uid = doc.data().uid;
        console.log(uid)
        const userRecord = await admin.auth().getUser(uid)
        const userData = userRecord.toJSON();
        if (userData.customClaims.stripeRole === 'premiun') {
            console.log(userData)
            premiumUsers.push(userData);
        }
      });
      res.status(200).json(premiumUsers);
    })
    .catch((error) => {
      console.error(error);
      res.status(500).send('Error getting premium users');
    });
});

The console output:

i  functions: Beginning execution of "us-central1-logPremiumUsers"
⚠  Google API requested!
   - URL: "https://oauth2.googleapis.com/token"
   - Be careful, this may be a production service.
>  UKjZeASTvYeYm7cA6HvXc5JxqXn1
i  functions: Finished "us-central1-logPremiumUsers" in 1459.693751ms
>  {
>    uid: 'UKjZeASTvYeYm7cA6HvXc5JxqXn1',
>    email: 'a.kiselev.private@gmail.com',
>    emailVerified: false,
>    displayName: 'andrey',
>    photoURL: undefined,
>    phoneNumber: undefined,
>    disabled: false,
>    metadata: {
>      lastSignInTime: 'Wed, 15 Feb 2023 15:35:21 GMT',
>      creationTime: 'Tue, 14 Feb 2023 21:05:06 GMT',
>      lastRefreshTime: 'Thu, 16 Feb 2023 08:38:04 GMT'
>    },
>    passwordHash: undefined,
>    passwordSalt: undefined,
>    customClaims: { stripeRole: 'premiun' },
>    tokensValidAfterTime: 'Tue, 14 Feb 2023 21:05:06 GMT',
>    tenantId: undefined,
>    providerData: [
>      {
>        uid: 'a.kiselev.private@gmail.com',
>        displayName: 'andrey',
>        email: 'a.kiselev.private@gmail.com',
>        photoURL: undefined,
>        providerId: 'password',
>        phoneNumber: undefined
>      }
>    ]
>  }
Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
Andrey
  • 68
  • 2
  • 9

1 Answers1

1

You should not use async/await within a forEach() loop, see "JavaScript: async/await with forEach()" and "Using async/await with a forEach loop".

You can use Promise.all() as follows:

exports.logPremiumUsers = functions.https.onRequest(async (req, res) => {

    try {
        const usersRef = admin.firestore().collection('users');
        const query = usersRef //.where('time', '>=', 100).where('time', '<=', 1000);
        const premiumUsers = [];
        const promises = [];

        const snapshot = await query.get();
        snapshot.forEach((doc) => {
            const uid = doc.data().uid;
            console.log(uid)
            promises.push(admin.auth().getUser(uid));
        });

        const rawUsersArray = await Promise.all(promises);
        rawUsersArray.forEach(user => {
            if (userData.customClaims.stripeRole === 'premiun') {
                console.log(userData)
                premiumUsers.push(userData);
            }
        })

        res.status(200).json(premiumUsers);

    } catch (error) {
        console.error(error);
        res.status(500).send('Error getting premium users');
    }

});

UPDATE following your comment:

To return the data from the Firestore user docs we can take advantage on the fact that Promise.all() returns an array that is in the order of the promises passed.

exports.logPremiumUsers = functions.https.onRequest(async (req, res) => {

    try {
        const usersRef = admin.firestore().collection('users');
        const query = usersRef //.where('time', '>=', 100).where('time', '<=', 1000);
        const premiumUsers = [];
        const promises = [];

        const snapshot = await query.get();
        snapshot.forEach((doc) => {
            const uid = doc.data().uid;
            console.log(uid)
            promises.push(admin.auth().getUser(uid));
        });

        const rawUsersArray = await Promise.all(promises);
        rawUsersArray.forEach((user, idx) => {
            if (userData.customClaims.stripeRole === 'premiun') {
                // You want to return the data from the Firestore user docs
                // Since Promise.all returns an array that is in the order of the promises passed
                // we can use the idx as follows:
                premiumUsers.push(snapshot.docs[idx].data());
            }
        })

        res.status(200).json(premiumUsers);

    } catch (error) {
        console.error(error);
        res.status(500).send('Error getting premium users');
    }

});
Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
  • The answer doesnt really solve the problem, I need the retrieve the promise within the loop to have acess to both values at thesame time. Still though thank you for your answer. – Andrey Feb 16 '23 at 21:24
  • What do you mean by "both the values". Which values precisely? – Renaud Tarnec Feb 16 '23 at 23:27
  • What I want to do is return the premium users from firestore, only the auth user object has a stripeRole value which i need to check, but the main data is in the user doc from firestore. So i need to retrieve the firestore doc, get the auth doc, check if user is premium and then add the firestore dock to the list. – Andrey Feb 17 '23 at 04:53
  • Ok I understand. Note that it is not what the code in your question actually shows (`userData` is the **Auth** user object data). But I've update my answer to show you how to do that. – Renaud Tarnec Feb 17 '23 at 09:39
  • Hey @AndreyKiselev, did you have time to test the proposed solution? – Renaud Tarnec Feb 23 '23 at 09:23