Select a document within the db.collection operation - node.js

I'm new here on the site, I have a question regarding a function in nodejs that I am unable to resolve. The solution is very simple but it does not work for me.
I build a social network in React, and for that I use nodeJs.
I have one function that I try to fix, but it does not work.
In firebase I have a collection called match, and in it I have details of users, and for each user, appear the other participants who are his friends.
For example according to the picture:
I have a collection of Match, in it I have my users, for example backend4 is a user's name, and in it, I have details of all the users it is suitable for.
This is the function I wrote down, but there is a problem with it, that it goes through all the users, the three users that can be seen in the picture.
exports.getMatchHandleArray = (req, res) => {
let engineers = [];
db.collection("match").get().then(querySnapshot => {
querySnapshot.forEach(doc => {
const engineerDetails = doc.data();
if (engineerDetails.handle === req.user.handle) {
engineerDetails.handle;
engineers.unshift(engineerDetails);
}
else {
engineerDetails.handle;
engineers.push(engineerDetails);
}
});
console.log(engineers.length);
res.json(engineers);
});
};
I want it to go through only one user I choose, for that I did something like this:
exports.getMatchHandleArray = (req, res) => {
let engineers = [];
db.collection(`/match/${req.user.handle}`).get().then(querySnapshot => {
querySnapshot.forEach(doc => {
const engineerDetails = doc.data();
if (engineerDetails.handle === req.user.handle) {
engineerDetails.handle;
engineers.unshift(engineerDetails);
}
else {
engineerDetails.handle;
engineers.push(engineerDetails);
}
});
console.log(engineers.length);
res.json(engineers);
});
};
I get a 500 error message on this, I understand why, because I can not make a selection within db.collection.
I think something like this should be used:
db.doc (/ match / $ {req.user.handle}) but I can not do that

exports.getMatchHandleArray = (req, res) => {
let userData = {};
db.doc(`/match/${req.user.handle}`)
.get()
.then((doc) => {
if (doc.exists) {
userData.match = doc.data();
}
return res.json(userData.match);
})
.catch((err) => {
console.error(err);
return res.status(500).json({ error: err.code });
});
};
i find the solution that's works for me

Related

Why does Async firebase fetching is not working? (NODE JS)

Building a NodeJS REST API.
Trying to send load data from FireBase collection, then sending it to the user (as API response).
Looks like the problem is that it's not waits for the firebase fetch to resolve, but send back a response without the collection data. (tried to use ASYNC-AWAIT but its not working)
exports.getChatMessages = async (req, res, next) => {
const chatId = req.params.chatId
const getChatData = () => {
db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot((snapshot) => {
snapshot.docs.forEach(msg => {
console.log(msg.data().messageContent)
return {
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
}
})
})
}
try {
const chatData = await getChatData()
console.log(chatData)
res.status(200).json({
message: 'Chat Has Found',
chatData: chatData
})
} catch (err) {
if (!err.statusCode) {
err.statusCode(500)
}
next(err)
}
}
As you can see, I've used 2 console.logs to realize what the problem, Terminal logs looks like:
[] (from console.logs(chatData))
All messages (from console.log(msg.data().messageContent))
Is there any way to block the code unti the firebase data realy fetched?
If I correctly understand, you want to send back an array of all the documents present in the messages subcollection. The following should do the trick.
exports.getChatMessages = async (req, res, next) => {
const chatId = req.params.chatId;
const collectionRef = db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc');
try {
const chatsQuerySnapshot = await collectionRef.get();
const chatData = [];
chatsQuerySnapshot.forEach((msg) => {
console.log(msg.data().messageContent);
chatData.push({
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
});
});
console.log(chatData);
res.status(200).json({
message: 'Chat Has Found',
chatData: chatData,
});
} catch (err) {
if (!err.statusCode) {
err.statusCode(500);
}
next(err);
}
};
The asynchronous get() method returns a QuerySnapshot on which you can call forEach() for enumerating all of the documents in the QuerySnapshot.
You can only await a Promise. Currently, getChatData() does not return a Promise, so awaiting it is pointless. You are trying to await a fixed value, so it resolves immediately and jumps to the next line. console.log(chatData) happens. Then, later, your (snapshot) => callback happens, but too late.
const getChatData = () => new Promise(resolve => { // Return a Promise, so it can be awaited
db.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot(resolve) // Equivalent to .onSnapshot((snapshot) => resolve(snapshot))
})
const snapshot = await getChatData();
console.log(snapshot)
// Put your transform logic out of the function that calls the DB. A function should only do one thing if possible : call or transform, not both.
const chatData = snapshot.map(msg => ({
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
}));
res.status(200).json({
message: 'Chat Has Found',
chatData
})
Right now, getChatData is this (short version):
const getChatData = () => {
db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot((snapshot) => {}) // some things inside
}
What that means is that the getChatData function calls some db query, and then returns void (nothing). I bet you'd want to return the db call (hopefully it's a Promise), so that your await does some work for you. Something along the lines of:
const getChatData = async () =>
db
.collection('chats')
// ...
Which is the same as const getChatData = async() => { return db... }
Update: Now that I've reviewed the docs once again, I see that you use onSnapshot, which is meant for updates and can fire multiple times. The first call actually makes a request, but then continues to listen on those updates. Since that seems like a regular request-response, and you want it to happen only once - use .get() docs instead of .onSnapshot(). Otherwise those listeners would stay there and cause troubles. .get() returns a Promise, so the sample fix that I've mentioned above would work perfectly and you don't need to change other pieces of the code.

How to get the discord id of users who have reacted to a specific message discord.js

I would like to grab the id of users who have reacted to a specific message and push them to the variable players, I've console.log() multiple things searching for a solution, used google, and asked friends, which was not successful. I hope that I can find someone with enough experience to help me find an answer or another question with an answer to this small problem I have.
let players = [message.author.id]
message.react('👌')
const filter = (reaction, user) => reaction.emoji.name === '👌'
message.awaitReactions(filter, { time: 20000 })
.then(collected => {
console.log(collected.user.id)
players.push(collected.user.id)
})
.catch(console.error);
awaitReactions returns a Collection of MessageReactions. Therefore collected.user will be undefined.
You'll have to get the first MessageReaction from the Collection and access its users property, filtering the bot's ID out.
// Creating a filter.
const Filter = (reaction, user) => reaction.emoji.name === "👌";
// Reacting to the message.
await message.react("👌");
// Awaiting reactions.
message.awaitReactions(Filter, { time: 6000 }).then((collected) => {
const reaction = collected.first();
// Making sure that at least one user reacted to the message
if (reaction) {
const players = reaction.users.cache.filter((user) => user.id !== client.user.id);
console.log(`Users that reacted: ${players.map((user) => user.username).join(", ")}`);
} else {
console.log("No one reacted to this message.");
}
});
If you are getting an error such as The 'await' operator can only be used within an async method. you'll have to make your method async.
Example:
client.on("message", async (message) => {
// Code
});
Check this documentation: https://discordjs.guide/popular-topics/reactions.html#reacting-in-order
const userReactions = message.reactions.cache.filter(reaction => reaction.users.cache.has(userId));
try {
for (const reaction of userReactions.values()) {
await reaction.users.remove(userId);
}
} catch (error) {
console.error('Failed to remove reactions.');
}
You can use this example but instead of removing the userId you can do something along the lines of:
const userReactions = message.reactions.cache.filter(reaction => reaction.users.cache.has(userId));
try {
players.push(userId)
} catch (error) {
console.error('Failed to push reaction');
}
Try this:
const filter = (reaction, user) => reaction.emoji.name === '👌'
message.awaitReactions(filter, { time: 20000 })
.then(collected => {
collected.first().users.cache.forEach(u => players.push(u.id))
})
.catch(console.error);
I couldn’t test it. Please tell me any errors you may have.

findById(req.params.id) returns null as response?

My issue is different than others, when I passed /:id then I return JSON yeah its ok, but issue is that when I give wrong objectId it return null value with statusCode 200 instead of error that this id is wrong. according to my perception it call .catch block instead of .then block because id is not available on database.
const get_id_docs = async (req, res) => {
await models
.findById(req.params.id)
.then(result => {
res.send(result)
})
.catch(err => {
res.sendStatus(404).send("Link Not Found")
})
};
There are two cases, one an invalid id, and the other a valid id but doesn't exists in db.
If you want to differentiate an invalid id, you can validate it before querying, and return 404.
Also you mixed async await and Promise, one of them must be used in this case.
const mongoose = require("mongoose");
const get_id_docs = async (req, res) => {
const isValidId = mongoose.Types.ObjectId.isValid(req.params.id);
if (!isValidId) {
res.status(404).send("Link Not Found - invalid id");
}
try {
const result = await models.findById(req.params.id);
if (result) {
res.send(result);
}
res.status(404).send("Link Not Found - does not exists");
} catch (err) {
res.status(500).send(err.message);
}
};
And if you prefer then catch
const mongoose = require("mongoose");
const get_id_docs = (req, res) => {
const isValidId = mongoose.Types.ObjectId.isValid(req.params.id);
if (!isValidId) {
res.status(404).send("Link Not Found - invalid id");
}
models.findById(req.params.id).then(result => {
if (result) {
res.send(result);
}
res.status(404).send("Link Not Found - does not exists");
})
.catch (err) {
res.status(500).send(err.message);
}
};
You are composing various general-purpose libraries that fill a variety of use cases. In particular, database abstraction frameworks typically try to percent a collection like facade over the underlying data stores. In JavaScript, methods like Array.prototype.find return undefined rather than throwing errors. And I think the authors of mongoose are trying to write analogously behaving APIs.
In addition to providing intuitive default Behavior, by not throwing errors, this enable a wider range of use cases, such as checking for existence, to be handled without boilerplate.
Given that, you want something like the following
const get_id_docs = async (req, res) => {
const result = await models.findById(req.params.id);
if (result) {
res.send(result);
}
res.sendStatus(404).send("Link Not Found");
};
Note that the above has other advantages including that it does not propagate other kinds of errors as 404s arbitrarily.

How to send multiple objects from node backend to .hbs

I'm currently trying to send 2 objects to the front .hbs front end. However I cant seem to work out how to do this because I'm using promises.
Currently, my thinking is i perform the sql query, the country and organisation name is extracted, and then each sent to a geocoding api, returned and then squashed together in the same promises. But i'm not sure how to extract these for the render function.
Node
//route for homepage
app.get('/', (req, res) => {
let sql = "SELECT org_name, country_name from places;
let query = conn.query(sql, (err, results) => {
if (err) throw err;
const geoPromise = param => new Promise((resolve, reject) => {
geo.geocode('mapbox.places', param, function(err, geoData) {
if (err) return reject(err);
if (geoData) {
resolve(geoData.features[0])
} else {
reject('No result found');
}
});
});
const promises = results.map(result =>
Promise.all([
geoPromise(result.country_name),
geoPromise(result.org_name)
]));
Promise.all(promises).then((geoLoc, geoBus) => {
res.render('layouts/layout', {
results: JSON.stringify(geoLoc),
businesses: JSON.stringify(geoBus)
});
});
});
});
Front end call
results1 = {{{results}}}
console.log(results1.length)
business1 = {{{businesses}}}
console.log(business1.length)
Wrap your geo.geocode into a Promise
const geoPromise = param => new Promise((resolve, reject) => {
geo.geocode('mapbox.places', param, function(err, geoData) {
if (err) return reject(err);
if (geoData) {
resolve(geoData.features[0])
} else {
reject('No result found');
}
});
});
Combine both calls to geo.geocode
const promises = results.map(result =>
Promise.all([
geoPromise(result.country_name),
geoPromise(result.org_name)
]));
Call them
Promise.all(promises).then(([geoLoc, geoBus]) => {
res.render('layouts/layout', {
results: JSON.stringify(geoLoc),
businesses: JSON.stringify(geoBus)
});
});
As MadWard's answer mentions, deconstructing the argument of the callback of Promise.all is necessary since everything will be in the first argument. Make sure you check out his post for more details
Something important to recall: you will never have more than one argument in a then() callback.
Now you may ask: in the case of Promise.all(), what is this value?
Well, it is an array with all the values from the promises it awaits, in the order in which they are called.
If you do:
Promise.all([
resolveVariable1, resolveVariable2, resolveVariable3
]).then((values) => {
})
values will be [variable1, variable2, variable3], the three variables that the promises resolve to.
Your case is, however, a bit more complicated. What is gonna be returned at the end is a 2-D array containing every entry. It is an array of length results.length, and each of its element has a length of 2. The first element is the result, and the second one is the business.
Here is your snippet:
Promise.all(promises)
.then((values) => {
let results = values.map(elmt => elmt[0]);
let businesses = values.map(elmt => elmt[1]);
res.render('layouts/layout', {
results: JSON.stringify(results),
businesses: JSON.stringify(businesses)
});
})

Node js callback issue

In my NodeJS application, I am building an API that first fetches all tarrifs name from tarrifs collection then based on all those tarrifs I want to return the counts of these tariffs allocated to users I tried the below-given code
router.get('/getTarrifDetails', (req,res,next) => {
result=[];
tname=[];
counts=[];
Tarrif.find().distinct('tarrif_type', (err,docs) => {
docs.forEach((ele) => {
tname.push(ele);
User.countDocuments({tarrif_type:ele}, (uerr,usr) => {
counts.push(usr);
});
result.push(ele);
});
result.push(counts);
});
});
When I console.log(result) it only shows one array of tarrif_type and other array is empty
You need to understand how the event loop works. I was here once, and I made the same mistakes once.
Try to sync your callbacks since you want to sequentially, like so:
router.get('/getTarrifDetails', (req, res, next) => {
let result = [], count = 0;
Tarrif.find().distinct('tarrif_type', (err, docs) => {
async.forEach(docs, async ele => {
try {
let userCount = await User.countDocuments({ tarrif_type: ele });
result.push(userCount);
} catch (err) {
//your err goes here.
}
})
});
});
I am not sure this will work 100%, but try it out and debug a little bit.

Resources