Getting firestore data from a Google Cloud Function with array-contains-any - javascript

A web page calls a Firebase function that should get several records from a Firestore collection, Everything runs without error but no data is returned. When I run the query in the page directly (not via the function), the right data is returned.
In the web page:
var getUserConfigFiles = firebase.functions().httpsCallable('getUserConfigFiles');
getUserConfigFiles()
.then((result) => {
console.log("Yay - Firebase result ===>",result);
})
.catch((error) => {
console.warn("error",error.code,error.message,error.details)
});
In index.js:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const cors = require('cors')({origin: true});
const db = admin.firestore();
Then the function itself:
exports.getUserConfigFiles = functions.https.onCall((data, context) => {
if (!context.auth) {
return {"status": "error", "code": 499, "message": "The function must be called while authenticated"};
}
const uid = context.auth.uid;
const email = context.auth.token.email;
var outcome = {"status": "OK", "code": 200, "requestor": uid, "email": email, "configfiles":[]};
// search on either the user's userid or their email
outcome.searcharray = [uid];
if (email) {
outcome.searcharray.push(email);
}
return db.collection("configfiles").where("memberAttached", "array-contains-any", outcome.searcharray)
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
outcome.configfiles.push({"tokenid": doc.id, "data-all": doc.data()});
})
// next row is returning the object just to see if it's there
outcome.querySnapshot = querySnapshot;
//
return (outcome);
})
.catch((error) => {
return ({"status": "error", "code": 402, "message": error,"requestor": uid});
});
});
Everything works, the result returns a querySnapshot. Except, there is no data, .configfiles should have a hundred or so rows. If I run the db.collection("configfiles").where("memberAttached... portion just in the web page, data is returned.
I have searched and tried many approaches but I'm obviously missing something fundamental. Can anyone help?

I'd suspect the outcome.querySnapshot = querySnapshot line is causing problems, as querySnapshot is not a JSON object and thus can't be returned. I recommend removing that line, and trying again.
If that doesn't solve the problem, can you add some logging, and see if the code ever reaches inside the then?

Related

Can't make Firestore to get only docs from logged user id

I am an UX Designer and I'm pretty new to working with Firebase.
I've been trying to develop a system on webflow integrated with Firebase using JavaScript and the Firebase SDK to a personal project and got stuck with this problem.
I have managed to create the authentication system, the signup system and everything is working as it should.
However, when I try to fetch data from Firestore userdata collection, I am not being able to get the current user id and pass it to the WHERE string on my query.
If I run the query without the WHERE, it works perfectly, bringing me all the documents in the userdata collection, but when I try to do it only for a specific user, it fails.
I already tried a lot of things which I think wasn't the right method to do this, the JavaScript below was my last attempt. I think I'm just too new on this and can't understand how to pass the id variable into the query.
Here is the link to the project: https://poupei.webflow.io/. Just click "Criar conta" to create an account, you can use a fake email and 6 digit password and then login.
// Import the functions you need from the SDKs you need
import { initializeApp } from "https://www.gstatic.com/firebasejs/9.4.0/firebase-app.js";
import { getAuth, onAuthStateChanged, signOut } from "https://www.gstatic.com/firebasejs/9.4.0/firebase-auth.js";
import { getFirestore, collection, getDocs, query, where, doc } from "https://www.gstatic.com/firebasejs/9.4.0/firebase-firestore.js"
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries
// Your web app's Firebase configuration
const app = initializeApp({
apiKey: "AIzaSyAZUIyxf4Lsw6D9JOzVuNslsGJ8gXkPBVY",
authDomain: "poupei-app.firebaseapp.com",
projectId: "poupei-app",
storageBucket: "poupei-app.appspot.com",
messagingSenderId: "837432279066",
appId: "1:837432279066:web:119bc86e42fb87ac17d1a3"
});
// Initialize Firebase
const auth = getAuth()
const db = getFirestore();
onAuthStateChanged(auth, (user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
const userID = user.id;
console.log("Logged In");
console.log(userID);
// ...
} else {
// User is signed out
window.location.replace("https://poupei.webflow.io/");
}
});
const q = query(collection(db, "userdata"), where("id", "==", userID));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
const docs = doc.data();
document.getElementById('nome').innerHTML = docs.nome;
document.getElementById('sobrenome').innerHTML = docs.sobrenome;
document.getElementById('email').innerHTML = docs.email;
document.getElementById('saldo').innerHTML = docs.saldo;
});
document.getElementById('logoutBtn').addEventListener('click', function(){
signOut(auth).then(() => {
// Sign-out successful.
window.location.replace("https://poupei.webflow.io/");
}).catch((error) => {
// An error happened.
});
});
</script>
´´´
#Allennick has the cause of the problem correct in their answer, but the solution won't work.
Signing in to Firebase (as well as loading data from Firestore and most other modern cloud APIs) is an asynchronous operation. While the user is being signed in (or the data is being loaded) your main code continues to run. Then when the user is signed in, your callback code is executed.
It's easiest to see this flow by running in a debugger, or adding some logging:
console.log("Attaching auth state listener");
onAuthStateChanged(auth, (user) => {
if (user) {
console.log("Got user state");
}
});
console.log("Starting database query");
const q = query(collection(db, "userdata"), where("id", "==", userID));
const querySnapshot = await getDocs(q);
When you run this code it logs:
Attaching auth state listener
Starting database query
Got user state
This is probably not the order you expected, but it perfectly explains why you're not getting the user data from the database: the query executes before the user is ever loaded.
The solution to this problem is always the same: any code that needs to react to the current user state, needs to be inside the onAuthStateChanged callback, be called from there, or otherwise synchronized.
The simplest fix is to move your database code into the callback, like this:
onAuthStateChanged(auth, async (user) => {
if (user) {
const userID = user.id;
// 👇 Now that the user us know, we can load their data
const q = query(collection(db, "userdata"), where("id", "==", userID));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
const docs = doc.data();
document.getElementById('nome').innerHTML = docs.nome;
document.getElementById('sobrenome').innerHTML = docs.sobrenome;
document.getElementById('email').innerHTML = docs.email;
document.getElementById('saldo').innerHTML = docs.saldo;
});
document.getElementById('logoutBtn').addEventListener('click', function(){
signOut(auth).then(() => {
// Sign-out successful.
window.location.replace("https://poupei.webflow.io/");
}).catch((error) => {
// An error happened.
});
} else {
// User is signed out
window.location.replace("https://poupei.webflow.io/");
}
});
Also see:
firebase.auth().currentUser is null at page load
Is there any way to get Firebase Auth User UID?
firebase.initializeApp callback/promise?
I think that the query doesn't know what userID is because you are declaring that variable inside authStateChange. Try to move the declaration of userID to global scope + add a console.log() before executing the query to see if the userID is set correctly.
Or just put the code that performs the query inside the onAuthStateChanged code so that you can use the userID.
(Posted answer on behalf of the question author to move it to the answer space).
Updating with the code that worked for me with the help of Frank. Thanks Frank!
onAuthStateChanged(auth, async (user) => {
if (user) {
const userID = user.email;
console.log(userID);
const q = query(collection(db, "userdata"), where("email", "==", userID));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
const docs = doc.data();
document.getElementById('nome').innerHTML = docs.nome;
document.getElementById('sobrenome').innerHTML = docs.sobrenome;
document.getElementById('email').innerHTML = docs.email;
document.getElementById('saldo').innerHTML = docs.saldo;
document.getElementById('meta').innerHTML = docs.objetivo;
})
} else {
console.log("nada");
}
});

firebase.firestore() shows bizarre data and not my actual documents from the database/

I am querying firebase firestore by...
let database = firebase.firestore();
let places = database.collection("place");
console.log("places", places);
now the logged data is bizarre and not the actual documents..
here is a picture of the log...can you please advice regarding tackling this ?
If you want to retrieve all items in your collections called "place" you can do something like this:
let database = firebase.firestore();
let places = database.collection("place");
const querySnapshot = places.get()
// You can make an empty array to eventually push the items into
const collectionArray = []
querySnapshot.forEach((doc) => {
const data = doc.data()
collectionArray.push(data)
}).catch(function(error) {
console.log("Error getting documents: ", error);
})
console.log('collectionArray:',collectionArray)
}
Your code hasn't actually executed any query yet. All it's done is build a Query object.
If you want to execute the query, call get() on it, and handle the results as shown in the documentation.
let database = firebase.firestore();
let query = database.collection("place");
query.get()
.then(querySnapshot => {
querySnapshot.forEach(documentSnapshot => {
console.log("document", documentSnapshot.data());
})
})

Firebase Cloud Firestore Read-Write Data

i am trying to signup and save user info into firestore. Save operation is fine but i want to search that info but nothing happening. Here is my code
Signup
firestore
.collection("users")
.doc(user.userId)
.collection("profile")
.add({ ...user })
.then(() => {
auth.onAuthStateChanged((u) => {
if (u) {
u.updateProfile({
displayName: user.displayName,
});
}
});
});
Fetch All users data
firestore
.collection("users")
.get()
.then(function (querySnapshot) {
querySnapshot.forEach(function (doc) {
//doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
});
You should be able to achieve by using a similar code as the below one. It very similar to yours, but with some differences, where we separate the referencing to the database and let the querySnapshot iterates by itself, so the data can be returned. I usually use this format to return data from collections.
var db = admin.firestore()
var usersReference = db.collection("users");
usersReference.get().then((querySnapshot) => {
querySnapshot.forEach((userDoc) => {
console.log(userDoc.id)
var userDocData = userDoc.data()
console.dir(userDocData)
})
})
This should work, so, in case it doesn't return anything, probably your saving opearation is not working properly. This will return both the user's id and the whole data from it.

Set on firebase and then set firebase claims

So i working with firebase auth and database in order to set new user to data base, if set successful i want to set claims for that user.
So it means i have a promise within a promise:
function setUser(user){
// no need for the database code before this, but userRef is set properly
return userRef.set(user)
.then(succ => {
return firebase.firebase.auth().setCustomUserClaims(user.key, {admin: true})
.then(() => {
console.log("setting claims")
return true;
});
})
.catch(err => {
return err
})
}
calling function:
app.post("/register_user",jsonParser,async (req, res) => {
var user = req.body.user;
let result = await fireBase.setUser(user);
res.send(result);
})
What happens is that i get the set on the database but claims are not set nor i can i see the log. I know its a js question and not firebase one. I tried many different ways (with await) but non worked.
firebase.firebase does not seem correct. You need to be using the admin object which can be initialised using const admin = require('firebase-admin'); This is not part of the firebase db sdk, but the admin one. You can also use the userRef.uid as that gives you the id of the document of the user, if that is what you want, else use your user.key
return admin.auth().setCustomUserClaims(userRef.uid, {
admin: true
}).then(() => {
//on success
});

Firestore query not working

Shown above is my firestore collection.
I am attempting to get data from this collection using a Google Cloud Function that I have deployed:
const admin = require('firebase-admin')
const functions = require('firebase-functions')
module.exports= function(request, response){
let results = []
admin.firestore().collection('news_stories')
.get()
.then(docs => docs.map(doc => results.push(doc.data())))
.catch(e => resoponse.status(400).send(e))
response.status(200).send(results)
}
When I run the above function I get an:
Error: could not handle the request
I also tried running the function this way to see if it would work.
module.exports= function(request, response){
let ref = admin.firestore().collection('news_stories')
.get()
.then(docs => response.status(200).send(docs))
.catch(e => resoponse.status(400).send(e))
}
This function returned a this JSON object:
There is no information regarding data or any of the docs.
I uploaded the collection to the firestore DB using this function:
const functions = require('firebase-functions')
const admin = require('firebase-admin')
module.exports = function(request,response){
if(!request.body.data){
response.status(422).send({error: 'missing data'})
}
let data = request.body.data
data.map(item => {
admin.firestore().collection('news_stories').add({item})
})
response.status(200).send('success!')
}
Not sure what I am doing wrong. Why is the function not returning any of the documents?
Data is retrieved from Firestore asynchronously. By the time your send you response back to the caller, the results haven't been retrieved from Firestore yet.
It's easiest to see this by replacing the bulk of the code with three log statements:
console.log("Before starting get");
admin.firestore().collection('news_stories')
.get()
.then(() => {
console.log("In then()");
});
console.log("After starting get");
It's best if you run the above in a regular node.js command, instead of in the Cloud Functions environment, since the latter may actually kill the command before the data is loaded.
The output of the above is:
Before starting get
After starting get
In then()
That is probably not the order that you expected. But because the data is loaded from Firestore asynchronously, the code after the callback function is allowed to continue straight away. Then when the data comes back from Firestore, your callback is invoked and can use the data as it needs to.
The solution is to move all the code that requires the data into the then() handler:
const admin = require('firebase-admin')
const functions = require('firebase-functions')
module.exports= function(request, response){
admin.firestore().collection('news_stories')
.get()
.then(docs => {
let results = []
docs.map(doc => results.push(doc.data()))
response.status(200).send(results)
})
.catch(e => resoponse.status(400).send(e))
}
So after some trouble shooting I found the source of the problem . For some reason if you use .map on the return object the server will respond with a 500 status...
change the .map to forEach and the function works
this will work ...
admin.firestore().collection('news_stories')
.get()
.then(docs => {
let data = []
docs.forEach(doc => data.push(doc.data()))
response.status(200).send(data)
})
yet this wont ...
admin.firestore().collection('news_stories')
.get()
.then(docs => {
let data = []
docs.map(doc => data.push(doc.data()))
response.status(200).send(data)
})

Categories

Resources