I'm using cloud function to check if specific document exists but it did not work. No document found even there was. The code is below:
exports.onUserAppCreated = functions.firestore.document('users/{userId}/first_col/{colId}')
.onCreate((snap, context) => {
const data = snap.data();
const colId = data.colId;
console.log(colId);
var docRef = db.collection('users/{userId}/somecollection');
let query = docRef.where('colId', '==', colId).get().then(doc => {
if (doc.exists) {
console.log("Document data:", doc.data());
let tracksRef = db.collection('users/{userId}/othercolllection');
tracksRef.where('otherId', '==', colId).get()
.then(transSnapshot => {
if (!transSnapshot.exists) {
transSnapshot.ref.set({
otherId: colId,
time:admin.firestore.FieldValue.serverTimestamp()
});
}
return transSnapshot;
}).catch(error => {
console.log(error);
//response.status(500).send(error);
})
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
return;
}
return doc;
}).catch(function(error) {
console.log("Error getting document:", error);
});
Has I done something wrong here?
I understand that you want to get the value of colId from the {colId} wildcard which is in the path 'users/{userId}/first_col/{colId}'. You should use the context object as follows:
exports.onUserAppCreated = functions.firestore.document('users/{userId}/first_col/{colId}')
.onCreate((snap, context) => {
const data = snap.data();
const colId = context.params.colId;
//....
});
Note that snap is the DocumentSnapshot corresponding to the document that triggered the Cloud Function. So snap.data() gives you an object containing the fields of this document, and therefore data.colId is undefined (unless you have saved the document id in a colId field in your document).
Note also that you could get the value of colId through the snap object by doing snap.id, but for the other wildcard, i.e. userId, you will need to use context.params.
In addition, note that you don't take into account the promises returned by the asynchronous methods of the Admin SDK (get(), set()). It is very important that you correctly return those promises, see the corresponding doc.
Related
I'm trying to get the device token of a particular user in firestore which is stored in tokens collection inside either "clients" or "lawyers" collection.
When i remove the second .collection("tokens") from the chain i get the user object back but with the token collection in the chain i just can't seem to get any user (client or lawyer) back, even though the user and it's token exist. what am i doing wrong
exports.onReceiveChatMessage = functions.database
.ref("/messages/{uid}")
.onCreate(async (snapshot, context) => {
const newMessage = snapshot.val();
console.log("NEW_MESSAGE", newMessage);
const senderName = newMessage.sender_name;
const messageContent = newMessage.content;
console.log("SENDER'S_NAME", senderName);
console.log("MESSAGE_BODY", messageContent);
const uid = context.params.uid;
console.log("RECEIVERS_ID", uid);
if (newMessage.sender_id == uid) {
//if sender is receiver, don't send notification
console.log("sender is receiver, dont send notification...");
return;
} else if (newMessage.type === "text") {
console.log(
"LETS LOOK FOR THIS USER, STARTING WITH CLIENTS COLLECTION..."
);
let userDeviceToken;
await firestore
.collection("clients")
.doc(uid)
.collection("tokens")
.get()
.then(async (snapshot) => {
if (!snapshot.exists) {
console.log(
"USER NOT FOUND IN CLIENTS COLLECTION, LETS CHECK LAWYERS..."
);
await firestore
.collection("lawyers")
.doc(uid)
.collection("tokens")
.get()
.then((snapshot) => {
if (!snapshot.exists) {
console.log(
"SORRY!!!, USER NOT FOUND IN LAWYERS COLLECTION EITHER"
);
return;
} else {
snapshot.forEach((doc) => {
console.log("LAWYER_USER_TOKEN=>", doc.data());
userDeviceToken = doc.data().token;
});
}
});
} else {
snapshot.forEach((doc) => {
console.log("CLIENT_USER_TOKEN=>", doc.data());
userDeviceToken = doc.data().token;
});
}
});
// console.log("CLIENT_DEVICE_TOKEN", userDeviceToken);
} else if (newMessage.type === "video_session") {
}
})
This line
if (!snapshot.exists) {
should be:
if (snapshot.empty) {
because you're calling get() on a CollectionReference (which returns a QuerySnapshot), not on a DocumentReference (which returns a DocumentSnapshot).
If you remove the .collection('tokens') from the chain in your example, it does work because a DocumentSnapshot does have the member exists, but a CollectionReference doesn't.
Take a look at their members here:
https://googleapis.dev/nodejs/firestore/latest/CollectionReference.html#get
Then:
https://googleapis.dev/nodejs/firestore/latest/QuerySnapshot.html
As a suggestion, I used to confuse snapshots and got that problem because of working with Javascript instead of Typescript. So I got used to calling the result snap when called on a document, and snaps when called on collections. That reminds me of what kind of response I'm working on. Like this:
// single document, returns a DocumentSnapshot
const snap = await db.collection('xyz').doc('123').get();
if (snap.exists) {
snap.data()...
}
// multiple documents, returns a QuerySnapshot
const snaps = await db.collection('xyz').get();
if (!snaps.empty) { // 'if' actually not needed if iterating over docs
snaps.forEach(...);
// or, if you need to await, you can't use the .forEach loop, use a plain for:
for (const snap of snaps.docs) {
await whatever(snap);
}
}
I have difficulties returning a value using a callable firebase function, while the same code works fine when it is a httprequest.
So what I am doing, is getting some user data, then getting some other data (a list of vessels) and then only return the vessels the user has edit rights for. I am very sure the accessibleVessels object holds some json data: I changed the function in a functions.https.onRequest firebase function and it went fine.
exports.getFormData = functions.https.onCall( (data, context) => {
const uid = context.auth?.uid.toString;
try {
const user = admin.firestore().
doc("users/"+uid)
.get().then((doc: any) => {
return doc.data();
});
const vessels = admin.firestore().
collection("vessels").get()
.then(mapSnapshot((doc: any) => doc.data()));
const accessibleVessels = vessels.filter((vessel: any) => {
return user.hasEditRights.some((right: any) => {
return vessel.name === right;
});
});
return accessibleVessels;
} catch (error) {
console.log(error);
return {"error": true};
}
});
When I run this I get:
Data cannot be encoded in JSON. [Function (anonymous)]
Looking in the documentation I understand that I do need to return json or a promise. I read other answers about this (returning a promise) but don't see how this would work in my example: in the examples I find not much is done with the data, it's just returned. I want to put the data in variables instead of chaining everything, so I can combine both. How should I do this?
The easiest way to fix this is using async/await:
exports.getFormData = functions.https.onCall(async(data, context) => { // 👈
const uid = context.auth?.uid.toString;
try {
const user = await admin.firestore() // 👈
.doc("users/"+uid)
.get().then((doc: any) => {
return doc.data();
});
const vessels = await admin.firestore() // 👈
.collection("vessels").get()
.then(mapSnapshot((doc: any) => doc.data()));
const accessibleVessels = vessels.filter((vessel: any) => {
return user.hasEditRights.some((right: any) => {
return vessel.name === right;
});
});
return accessibleVessels;
} catch (error) {
console.log(error);
return {"error": true};
}
});
I also recommend reading the MDN documentation on async/await, the Firebase documentation on sync, async, and promises and Doug's awesome series on JavaScript Promises in Cloud Functions.
I need to retrieve information from a Firestore Document when another document is created. When I try to do this I get hit with an error about the function not being async. It has been so long since I used javascript I am basically a novice again and have no idea how to fix this.
ok, so I am using Firebase Cloud Functions and the function in question is a Firestore .onCreate() trigger.
When the function is triggered I set a sender variable (which is the document ID from a different collection that I need to retrieve)
then I try to get the document as per the documentation.
The function ends up like this:
exports.pushFriendRequestNotification = functions.firestore.document('friends/{friendID}')
.onCreate((snap, context) => {
// when friend request is created
data = doc.data()//get request data
sender = data["sender"]//get request sender from data
const requestRef = db.collection('User').doc(sender);
const doc = await requestRef.get();//get user data of sender
if (!doc.exists) {
console.log('No such document!');
} else {
console.log('Document data:', doc.data());
}
});
when I run this in the emulator I get this error:
const doc = await requestRef.get();//get user data of sender
^^^^^
SyntaxError: await is only valid in async functions and the top level bodies of modules
I have absolutely no idea where to go from here.
Can anyone help me with this?
Thanks
The await keyword is valid only in an async function.
exports.pushFriendRequestNotification = functions.firestore.document('friends/{friendID}')
.onCreate(async (snap, context) => {
// ^^^^^
})
If you are (or need to) use synchronous function then you would have to use promise chaining.
exports.pushFriendRequestNotification = functions.firestore.document('friends/{friendID}')
.onCreate((snap, context) => {
return requestRef.get().then((snapshot) => {
if (snapshot.exists) { ... }
})
})
Apart from that, the order of variables/statements looks incorrect. With the current code (as in original question), you may end up getting an error: "Cannot access 'doc' before initialization" Try refactoring it like this:
exports.pushFriendRequestNotification = functions.firestore.document('friends/{friendID}')
.onCreate(async (snap, context) => {
// accessing data from newly created doc
const newDocData = snap.data()
// const sender = "" // ??
const requestRef = db.collection('User').doc(sender);
const doc = await requestRef.get();//get user data of sender
if (!doc.exists) {
console.log('No such document!');
} else {
console.log('Document data:', doc.data());
}
})
Where is the sender coming from? I've just commented it above but if the sender is present in new document then you can access it by: const sender = newDocData.sender
If your using await you have to specify that function is asynchronous. Otherwise it will throw error.
exports.pushFriendRequestNotification = functions.firestore.document('friends/{friendID}').onCreate(async (snap, context) => {
// when friend request is created
data = doc.data()//get request data
sender = data["sender"]//get request sender from data
const requestRef = db.collection('User').doc(sender);
const doc = await requestRef.get();//get user data of sender
if (!doc.exists) {
console.log('No such document!');
} else {
console.log('Document data:', doc.data());
}
});
Yet some of your references is unknown to us. Maybe this code is not completed.
The main point is you need to understand when you can access async/await or Promise
All await methods must be inside an async block or be handled in an async manor using .then() promises
in this case, the parent function is on this line .onCreate((snap, context) => {
simply inserting an async at the start of the variables will upgrade the arrow function to an async arrow function
.onCreate(async (snap, context) => {
I tried querying Firestore using .get():
//Cloud function to perform leaderboard calculation
exports.scheduledLeaderboardFunction = functions.pubsub.schedule('00 21 * * *')
.timeZone('America/Los_Angeles')
.onRun(async (context) => {
var globalPostsArray = [];
try {
await admin.firestore()
.collection('globalPosts')
.get()
.then((querySnapshot) => {
if(querySnapshot.exists) {
querySnapshot.forEach((res) => {
const {
//Fields
//Removed
} = res.data();
globalPostsArray.push({
//Fields
//Removed
});
});
}
else {
throw new Error("Data doesn't exist") <-------- This error is thrown
}
return null
})
.then(() => {
if (globalPostsArray.length > 0) {
console.log(globalPostsArray)
}
else {
throw new Error("length not greater than 0")
}
return null;
})
}
catch(error) {
console.log(error);
}
return null;
});
but in the firebase cloud log, I get the following error printed:
Error: Data doesn't exist
Which means querySnapshot doesn't exist when I use .get() (the error is thrown).
globalPosts, the collection I am querying, is NOT empty
if I can make .get() to work, that would work for my issue, since I am not waiting for updates which is what .onSnapshot() is good for.
summary: onSnapshot() worked for fetching the data from Firestore, but I can't use .then() to wait for the data so I can finish up the work. get() isn't working, but I can use .then() to wait for the collection if it does work.
How can I fix my issue?
EDIT: changed the function to this, but its still not working
//Cloud function to perform leaderboard calculation
exports.scheduledLeaderboardFunction = functions.pubsub.schedule('00 21 * * *')
.timeZone('America/Los_Angeles')
.onRun(async (context) => {
try {
await admin.firestore().collection('globalPosts').orderBy("date_created", "desc")
.get()
.then(function(querySnapshot) {
if(querySnapshot) {
querySnapshot.forEach(function(doc) {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
return null
}
else {
throw new Error("Data doesn't exist")
}
})
}
catch(error) {
console.log(error);
}
return null;
});
Proof globalPosts isn't empty:
Same error:
Error: Data doesn't exist
I think you're on the right track with .get(), but don't mix async syntax.
const shortSnapshot = await firebase.firestore()
.collection("stuff")
.where("stuff", "==", "new")
.limit(1)
.get();
if (shortSnapshot.empty) {
console.error("No stuff");
return response.sendStatus(404);
}
console.log(shortSnapshot.docs[0].data());
There is no .then for .onSnapShot - it attaches a Listener Function, and returns synchronously, with no data - it does NOT return a promise.
the anonymous function you created and passed - it begins (querySnapshot) => { is called with a querySnapshot as it's argument when the Listener is triggered. Judging by the other parts of the code, you're attempting a cloud function? As a general rule, listeners are NOT the correct approach for a Cloud Function, as these functions are intended to be short-lived.
The message you are shown is precisely what would be expected if your collection 'globalPosts' were empty - and you show nothing here to indicate that this is an error.
Removing the try, catch fixed my issue!
await admin.firestore().collection('globalPosts').orderBy("date_created", "desc")
.get()
.then(function(querySnapshot) {
if(querySnapshot) {
querySnapshot.forEach(function(doc) {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
return null
}
else {
throw new Error("Data doesn't exist")
}
})
The error of "data doesn't exist" is no longer an issue
I'm new to Cloud Functions and I'm trying to retrieve the attribute 'name' performing a query using its id, but I don't know how to handle the object returned by the query.
Here is the code I'm using:
// Create and Deploy Your First Cloud Functions
// https://firebase.google.com/docs/functions/write-firebase-functions
const functions = require('firebase-functions');
const admin = require('firebase-admin')
admin.initializeApp()
exports.testNotification = functions.firestore
.document('messages/{email}/{groupChat}/{groupId1}/{groupId2}/{message}')
.onCreate((snap, context) => {
console.log('----------------start function--------------------')
const doc = snap.data()
console.log(doc)
const idFrom = doc.idFrom
const idTo = doc.idTo
console.log(idTo)
const contentMessage = doc.content
/*[...] Some awesome code here not correlated to the question */
admin
.firestore()
.collection('settings')
.doc('table')
.collection('room')
.where('id', '==', idTo)
.get()
.then(querySnapshot =>{
console.log('querySnapshot: ' + querySnapshot)
return null
})
.catch(error => {
console.log('Error sending message:', error)
})
return null
});
I went for the trial & error solution, but the only syntax I tried that returns something rather than 'undefined' or exception, is 'querySnapshot', that logs 'querySnapshot: [object Object]'.
Other useful info: I know that the const 'idTo' is correct and the element searched into the db has both attributes 'id' and 'name'.
What am I doing wrong? Do you have an useful and complete documentation to link to?
Thanks
I just needed to use
querySnapshot.forEach(doc => {
console.log(doc.data().name)
})
So the final code is:
admin
.firestore()
.collection('settings')
.doc('table')
.collection('room')
.where('id', '==', idTo)
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
console.log(doc.data().name)
});
return null
})
.catch(error => {
console.log('Error sending message:', error)
})
return null
For more info, check out this documentation.