.where() firestore query not working properly? - node.js

In the below code, when I remove the .where() from my query the query works and I get my comments returned. However, when I use the where query to try and find a comment bound to a post (scream) id, i get a blank array. Am I using .where() incorrectly? Any help would be appreciated.
exports.getScream = (req, res) => {
let screamData = {};
db.doc(`/screams/${req.params.screamId}`)
.get()
.then((doc) => {
screamData = doc.data();
screamData.screamId = doc.id;
return db
.collection('comments')
.where('screamId', '==', screamData.screamId) //query that doesnt seem to be working
.orderBy('createdAt', 'desc')
.get();
})
.then((data) => {
screamData.comments = [];
data.forEach((doc) => {
screamData.comments.push(doc.data());
});
return res.json(screamData);
})
.catch((err) => {
console.error(err);
res.status(500).json({ error: err.code });
});
}
Firestore document model
{
screamId: “id of post commenting on”,
body: “content of comment”,
userHandle: “username of comment”
}

Related

How would I paginate Firestore data with cloud functions?

I'm trying to paginate data so I can get an infinite scroll for posts on my app. I have a cloud function called getPosts where there are multiple get functions a reads. Everything works fine with that. But when I try thinking of pagination using cloud functions, I run into a problem sending the last snapshot as a query parameter. This snapshot will be super long and an unexpected length, way more than 3000 characters. Here is my getPosts function:
exports.getPosts = (req, res) => {
const postId = req.query.postId;
if(postId != null){
db
.collection('posts')
.doc(postId)
.get()
.then((doc) => {
if(!doc.exists){
throw 'postNotFound';
}
else{
const voteCount = sumValues(doc.data().votingOptions);
let liked;
let votingOptionsDictionary;
return db.collection('votes')
.where('postId', '==', doc.id)
.where('userHandle', '==', req.user.userHandle)
.get()
.then((voteDoc) => {
return db.collection('likes')
.where('postId', '==', doc.id)
.where('userHandle', '==', req.user.userHandle)
.get()
.then((likeDoc) => {
liked = likeDoc.empty ? false : true;
return res.json([{
postId: doc.id,
userHandle: doc.data().userHandle,
postQuestion: doc.data().postQuestion,
userImageUrl: doc.data().userImageUrl,
imageUrl: doc.data().imageUrl,
postDescription: doc.data().postDescription,
createdAt: doc.data().createdAt
}]);
});
});
}
})
.catch((err) => {
if(err == "postNotFound"){
return res.json({'Error': `Post ID ${postId} does not exists`});
}
else{
console.error(err);
return res.json({error: err});
}
});
}
else{
db
.collection('posts')
.orderBy('createdAt', 'desc')
.limit(10)
.get()
.then(async (data) => {
const promises = await data.docs.map((doc) => {
const voteCount = sumValues(doc.data().votingOptions);
let liked;
let votingOptionsDictionary;
return db.collection('votes')
.where('postId', '==', doc.id)
.where('userHandle', '==', req.user.userHandle)
.get()
.then((voteDoc) => {
return db.collection('likes')
.where('postId', '==', doc.id)
.where('userHandle', '==', req.user.userHandle)
.get()
.then((likeDoc) => {
liked = likeDoc.empty ? false : true;
return {
postId: doc.id,
userHandle: doc.data().userHandle,
postQuestion: doc.data().postQuestion,
userImageUrl: doc.data().userImageUrl,
imageUrl: doc.data().imageUrl,
postDescription: doc.data().postDescription,
createdAt: doc.data().createdAt
};
});
});
})
Promise.all(promises)
.then((posts) => {
res.json(posts);
})
})
.catch((err) => {
console.error(err);
res.status(500).json({ error: err.code});
});
}
}
I was thinking of saving the snapshot object on the client side, then sending that as an optional query parameter to the getPosts cloud function to get the data I want, but I'm almost positive I can't send that object as a query paramter...
If you can't use an actual DocumentSnapshot object as the anchor document for your pagination, you can simply use the field values in the document that are relevant for sort ordering. This is described in the documentation. So, if you have a single ordering on createdAt, can can just pass the relevant field value to startAt() or startAfter().
If you don't have a order defined at all, then the sort order is based on the document ID, and you can simply use a where clause to get all documents greater than or less than the ID you specify. For example: where(FieldPath.documentId(), ">", id).

Firestore Update Document field and get immidiate response issue getting old data

I am trying to doing update firestore field value. It is updating, but I want immediately to use updated document. When I am getting those data that time, it will giving me old data from Firestore first time i hit the api. If I hit the same api twice, then I am getting updated data.
So, i don't understand what is the actual problem
updateProductDetail: async(req, res)=>{
console.log("reqeuest", req.body.creator);
try {
var productFilterArray = [];
var counter = 0;
let collectionRef = db.collection("product_details_temp");
let query = collectionRef.where('creator', '==', req.body.creator).get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
return;
} else {
snapshot.forEach(doc => {
db.collection("product_details_temp").doc(doc.id).update({ "product": req.body.product });
});
collectionRef.where('creator', '==', req.body.creator).get()
.then(snapshot => {
let a =[];
snapshot.forEach(doc => {
// a = doc.data();
a.push(doc.data());
});
res.send(functions.responseGenerator(200, "successfull", a));
})
}
})
} catch (error) {
res.send(
functions.responseGenerator(error.code, error.message, error.data)
);
}
Please help me
It sounds like you have two operations:
A write operation
A query/read operation that must see the result of the write operation
Code that reads data from or writes data to Firestore runs asynchronously. And to prevent blocking the app, your normal code flow is allowed to continue while the read/write operations runs in the background. That means that the query/read currently runs in your code, the write operation hasn't completed yet, and hence you don't see the result of that write operation in your query.
The solution is to wait until the write operation has completed before starting the query. You already do this with the query, where you use the .then(snapshot => { construct to wait for the results. You'll need to do the same with the update(...) calls, with something like:
let collectionRef = db.collection("product_details_temp");
let query = collectionRef.where('creator', '==', req.body.creator).get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
return;
} else {
let promises = [];
snapshot.forEach(doc => {
promises.push(db.collection("product_details_temp").doc(doc.id).update({ "product": req.body.product }));
});
Promise.all(promises).then(() => {
collectionRef.where('creator', '==', req.body.creator).get()
.then(snapshot => {
let a =[];
snapshot.forEach(doc => {
a.push(doc.data());
});
res.send(functions.responseGenerator(200, "successfull", a));
})
})
}
The main changes here:
This code captures the result of the update calls in a promises array. This gives us a list of promises, which resolve when the write operation completes.
It then uses Promise.all() to wait for all write operations to complete before starting the query.
I also have the same issue, I tried the code in this way :
updateProductDetail: async (req, res)=>{
try {
db.collection("product_details_temp")
.where('creator', '==', req.body.creator).get()
.then(snapshot => {
snapshot.forEach(doc => {
db.collection("product_details_temp").doc(doc.id).update({
"product": req.body.product });
});
controller.getAPI(req,res);
});
} catch (error) {
res.send(
functions.responseGenerator(error.code, error.message, error.data)
);
}
},
getAPI: async (req, res) =>{
try{
db.collection("product_details_temp").where('creator', '==',
req.body.creator).get()
.then(snapshot => {
var A = [];
snapshot.forEach(doc => {
A.push(doc.data());
});
res.send(functions.responseGenerator(200, "successfull", A));
}) ;
} catch (error) {
res.send(
functions.responseGenerator(error.code, error.message, error.data)
);
}
}
};
I got the solution for this issue by using the setTimeout() function:
setTimeout(() => {
collectionRef.where('creator', '==', req.body.creator).get()
.then(snapshot => {
let a = [];
snapshot.forEach(doc => {
a.push(doc.data());
});
res.send(functions.responseGenerator(200, "successfull", a));
})
}, 2000);
Use this with code like:
updateProductDetail: async(req, res)=>{
try {
let collectionRef = db.collection("product_details_temp");
let query = collectionRef.where('creator', '==', req.body.creator).get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
return;
} else {
snapshot.forEach(doc => {
db.collection("product_details_temp").doc(doc.id).update({
"product": req.body.product });
});
}
setTimeout(() => {
collectionRef.where('creator', '==', req.body.creator).get()
.then(snapshot => {
let a = [];
snapshot.forEach(doc => {
a.push(doc.data());
});
res.send(functions.responseGenerator(200, "successfull", a));
})
}, 2000);
})
} catch (error) {
res.send(
functions.responseGenerator(error.code, error.message, error.data)
);
}

Nested queries, promises on nodejs

Im trying to build a rest api, fetching a nested mysql queries.
When i fetch the first query, this return a array, then with this array i need to fetch data with another query for each value through a array.map
when the script running, always log a empty array, i think must be cause of promises. any help please?
//this the mysql queries
const getTournaments = 'SELECT ID FROM wp_posts WHERE post_type = "tournament"'
const getTournamentGame = 'SELECT meta_value FROM wp_postmeta WHERE meta_key = "tournament_game" AND post_id = ?'
async function fetchType(id){
return new Promise ((res, rej) => {
try{
pool.query(getTournamentGame, [id], (err, rows) => {
if (err) {
return rej(err)
}else {
return res(rows[0].meta_value)
}
})
} catch(err){
console.log(err)
}
})
}
async function mapeado(array) {
return new Promise (async (resolve,rej) => {
try{
var arr = []
array.map((item) => {
fetchType(item.ID).then((res) => {
var tourData = {
id: item.ID,
type: res
}
return tourData
}).then((data) => {
arr.push(data)
})
})
return resolve(arr)
} catch(err) {
console.log(err)
}
})
}
//making rest api
app.get('/tournaments', async (req, res) => {
pool.query(getTournaments, (err, rows) => {
mapeado(rows).then(console.log)
})
})

Firebase Firestore transaction is returning a querySnapshot instead of a documentSnapshot

i'm replicating this code from the firebase documentation in google cloud functions:
var cityRef = db.collection('cities').doc('SF');
var transaction = db.runTransaction(t => {
return t.get(cityRef)
.then(doc => {
// Add one person to the city population
var newPopulation = doc.data().population + 1;
t.update(cityRef, { population: newPopulation });
});
}).then(result => {
console.log('Transaction success!');
}).catch(err => {
console.log('Transaction failure:', err);
});
But i get: property 'data' does not exist on type 'QuerySnapshot' when it should be a documentSnapshot.
I found the answer after i opened an issue on github and get some insight https://github.com/firebase/firebase-admin-node/issues/344
async function setCounts(storeRef: admin.firestore.DocumentReference) { // Type here
var transaction = admin.firestore().runTransaction(t => {
return t.get(storeRef)
.then((doc: admin.firestore.DocumentSnapshot) => { // Type here
x = doc.data(); // Now i can get data() from the doc
});
}).then(result => {
console.log('Transaction success!');
}).catch(error => {
console.log('Transaction failure:', error);
});
}
I finally made it work by explicitly declaring the types for DocumentReference and DocumentSnapshot, i don't know why, but while deploying the linter inferred doc as QuerySnapshot even though it was not.

POST request for many to many in express

I have 2 tables: user and material which have a m:m relationship. The intersection entity is journalMaterials. I am trying to send a POST request to insert into journalMaterials. Also, this table has 2 attributes: recycledQuantity and recycleDate. I tried something, but if i insert with a materialId which doesn't exist it doesn't give me "not found".
app.post('/users/:uid/materials/:mid', (req, res, next) => {
User.findById(req.params.uid)
.then((user) => {
if (user){
let journalMaterial = req.body
journalMaterial.userId = user.id
Material.findById(req.params.mid)
.then((material) => {
if (material){
journalMaterial.materialId = material.id
return JournalMaterial.create(journalMaterial)
}
else{
res.status(404).send('not found')
}
})}
else{
res.status(404).send('not found')
}
})
.then(() => {
if (!res.headers){
res.status(201).json('created')
}
})
.catch((err) => next(err))
})
I've solved it. Here is the correct code.
app.post('/users/:uid/materials/:mid', (req, res, next) => {
const { uid, mid } = req.params;
Promise.all([
User.findById(uid),
Material.findById(mid)
])
.then(([user, material]) => {
if (user && material) {
let journalMaterial = req.body
journalMaterial.userId = user.id
journalMaterial.materialId = material.id
res.status(201).json('created')
return JournalMaterial.create(journalMaterial)
}
res.status(404).send('not found')
})
.catch(err => next(err));
})
Slightly re-wrote this to make it a bit more readable. Removed your nested promise calls... (let's not dive into promise hell when they try to get rid of callback hell..)
app.post('/users/:uid/materials/:mid', (req, res, next) => {
const { journalMaterial } = req.body;
const { uid, mid } = req.params;
Promise.all([
User.findById(uid),
Material.findById(mid)
])
.then(([user, material]) => {
if (user && material) {
journalMaterial.userId = user.id;
journalMaterial.materialId = material.id;
return JournalMaterial.create(journalMaterial);
}
res.status(404).send('not found');
})
.then(() => {
if (!res.headers) {
res.status(201).json('created');
}
})
.catch(err => next(err));
});
Your check against if(user) currently passes. It seems that if that's what is happening, you're always getting an object back. Lots of databases generally don't simply return a null or false value, but rather an object with a bunch of meta data. In that object is generally the data you requested (ie, user.data.id, but it may be that user.data is NULL). Can you verify what the exact contents of Users is? It's evaluating to truthy, thus it must have something in it.

Resources