Need help searching for a value in firestore - javascript

I'm new to firestore and I'm making a register page with vue.
Before a new user is made, it has to check if the given username already exists or not and if not, make a new user.
I can add a new user to the database, but I don't know how to check if the username already exists or not. I tried a lot of things and this is the closest I've gotten:
db.collection("Users")
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
if (this.username === doc.data().username) {
usernameExist = true;
}
});
});
Anyone got any ideas?

Link to documentation: https://firebase.google.com/docs/firestore/query-data/queries#simple_queries
You can where this query, which is beneficial to you in multiple ways:
1: Fewer docs pulled back = fewer reads = lower cost to you.
2: Less work on the client side = better performance.
So how do we where it? Easy.
db.collection("Users")
.where("username", "==", this.username)
.get()
.then(querySnapshot => {
//Change suggested by Frank van Puffelen (https://stackoverflow.com/users/209103/frank-van-puffelen)
//querySnapshot.forEach(doc => {
// if (this.username === doc.data().username) {
// usernameExist = true;
// }
//});
usernameExists = !querySnapshot.empty
});

Related

Firebase Firestore transactions on cloud functions ( getAll method on the callback function first param ) How to use it?

I've been struggling for a while to find out how exactly to use the getAll function on the first param of the callback function on runTransation function.
The doc firebase doc only shows how to use the get function to retrieve a single doc, but I want to retrieve multiple docs based on multiple where statements from a collection.
the question is how to use getAll function bellow?
export const match = functions.firestore
.document('waitingList/{userId}').onCreate(async (snapshot, context) => {
app.firestore().runTransaction(async transaction => {
transaction.getAll( //?); // ???
});
});
UPDATE 1 | 23/4/2022
I figured out a way (but I'm not fully satisfied with it because of duplication in reading from firestore).
The solution is as follows
app.firestore().runTransaction(async transaction => {
const docRefs: FirebaseFirestore.DocumentReference<any>[] = [];
(await firestore
.collection('collectionName')
.limit(100)
.get())
.forEach((doc) => {
docRefs.push(doc.ref);
}); //This is reading the Database for 100 docs
const users = await transaction.getAll(...docRefs); // This also is reading the database for the same 100 docs (But in a transaction context)
});
if someone knows how to read the docs only once in the transaction please do provide the solution.
Something like that:
transaction.getAll(...docRefs).then((docs) => {
docs.forEach((doc) => {/*...*/}
}
As #Abobker already stated, I agree that for now this will be the best way to retrieve multiple docs based on multiple where statements from a collection:
app.firestore().runTransaction(async transaction => {
const docRefs: FirebaseFirestore.DocumentReference<any>[] = [];
(await firestore
.collection('collectionName')
.limit(100)
.get())
.forEach((doc) => {
docRefs.push(doc.ref);
}); //This is reading the Database for 100 docs
const users = await transaction.getAll(...docRefs); // This also is reading the database for the same 100 docs (But in a transaction context)
});
Although is not the most efficient solution, It does address the problem.

Firebase Firestore Delete multiple documents by timestamp and more fields

I see a similar question here . However, I am very new to coding and I am trying to delete all the documents that are older than 1 month and not premium from the “users” collection. When deleting a document, the “user_online” filed need to be 30 days or more old and “user_premium” need to be “no”.
I am using the node js with adminsdk.
I would be very grateful if anyone can help me with a node js code to achieve above.
using some other post I came up with following.
var userdelete_query = db.collection('users').where('user_online', '<=', new Date(Date.now() - 2592000000) && 'user_premium', '==', 'no');
userdelete_query.get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
doc.ref.delete();
console.log(`deleted: ${doc.id}`);
});
});
above code shows no errors, but nothing happens. I think the following part is the problem
&& 'user_premium', '==', no); -
To combine two where clauses, you need to chain them, as explained in the doc.
var userdelete_query = db.collection('users')
.where('user_online', '<=', new Date(Date.now() - 2592000000))
.where('user_premium', '==', 'no');
userdelete_query.get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
doc.ref.delete();
});
});
Note that you will need to create a composite index for this query to work.
Note that with the above code you don't know when all the docs are deleted. If you want to monitor the execution of all the parallel calls to the delete() method, you can use Promise.all() as follows:
const userdelete_query = db.collection('users')
.where('user_online', '<=', new Date(Date.now() - 2592000000))
.where('user_premium', '==', 'no');
userdelete_query.get()
.then(function(querySnapshot) {
const promises = [];
querySnapshot.forEach(function(doc) {
promises.push(doc.ref.delete());
});
return Promise.all(promises);
})
.then(function() {
console.log("ALL DOCS ARE DELETED");
})
A third possible approach would be to use a batched write (or better, a batched deletion).

Firebase Firestore - Async/Await Not Waiting To Get Data Before Moving On?

I'm new to the "async/await" aspect of JS and I'm trying to learn how it works.
The error I'm getting is Line 10 of the following code. I have created a firestore database and am trying to listen for and get a certain document from the Collection 'rooms'. I am trying to get the data from the doc 'joiner' and use that data to update the innerHTML of other elements.
// References and Variables
const db = firebase.firestore();
const roomRef = await db.collection('rooms');
const remoteNameDOM = document.getElementById('remoteName');
const chatNameDOM = document.getElementById('title');
let remoteUser;
// Snapshot Listener
roomRef.onSnapshot(snapshot => {
snapshot.docChanges().forEach(async change => {
if (roomId != null){
if (role == "creator"){
const usersInfo = await roomRef.doc(roomId).collection('userInfo');
usersInfo.doc('joiner').get().then(async (doc) => {
remoteUser = await doc.data().joinerName;
remoteNameDOM.innerHTML = `${remoteUser} (Other)`;
chatNameDOM.innerHTML = `Chatting with ${remoteUser}`;
})
}
}
})
})
})
However, I am getting the error:
Uncaught (in promise) TypeError: Cannot read property 'joinerName' of undefined
Similarly if I change the lines 10-12 to:
remoteUser = await doc.data();
remoteNameDOM.innerHTML = `${remoteUser.joinerName} (Other)`;
chatNameDOM.innerHTML = `Chatting with ${remoteUser.joinerName}`;
I get the same error.
My current understanding is that await will wait for the line/function to finish before moving forward, and so remoteUser shouldn't be null before trying to call it. I will mention that sometimes the code works fine, and the DOM elements are updated and there are no console errors.
My questions: Am I thinking about async/await calls incorrectly? Is this not how I should be getting documents from Firestore? And most importantly, why does it seem to work only sometimes?
Edit: Here are screenshots of the Firestore database as requested by #Dharmaraj. I appreciate the advice.
You are mixing the use of async/await and then(), which is not recommended. I propose below a solution based on Promise.all() which helps understanding the different arrays that are involved in the code. You can adapt it with async/await and a for-of loop as #Dharmaraj proposed.
roomRef.onSnapshot((snapshot) => {
// snapshot.docChanges() Returns an array of the documents changes since the last snapshot.
// you may check the type of the change. I guess you maybe don’t want to treat deletions
const promises = [];
snapshot.docChanges().forEach(docChange => {
// No need to use a roomId, you get the doc via docChange.doc
// see https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentChange
if (role == "creator") { // It is not clear from where you get the value of role...
const joinerRef = docChange.doc.collection('userInfo').doc('joiner');
promises.push(joinerRef.get());
}
});
Promise.all(promises)
.then(docSnapshotArray => {
// docSnapshotArray is an Array of all the docSnapshots
// corresponding to all the joiner docs corresponding to all
// the rooms that changed when the listener was triggered
docSnapshotArray.forEach(docSnapshot => {
remoteUser = docSnapshot.data().joinerName;
remoteNameDOM.innerHTML = `${remoteUser} (Other)`;
chatNameDOM.innerHTML = `Chatting with ${remoteUser}`;
})
});
});
However, what is not clear to me is how you differentiate the different elements of the "first" snapshot (i.e. roomRef.onSnapshot((snapshot) => {...}))). If several rooms change, the snapshot.docChanges() Array will contain several changes and, at the end, you will overwrite the remoteNameDOM and chatNameDOM elements in the last loop.
Or you know upfront that this "first" snapshot will ALWAYS contain a single doc (because of the architecture of your app) and then you could simplify the code by just treating the first and unique element as follows:
roomRef.onSnapshot((snapshot) => {
const roomDoc = snapshot.docChanges()[0];
// ...
});
There are few mistakes in this:
db.collection() does not return a promise and hence await is not necessary there
forEach ignores promises so you can't actually use await inside of forEach. for-of is preferred in that case.
Please try the following code:
const db = firebase.firestore();
const roomRef = db.collection('rooms');
const remoteNameDOM = document.getElementById('remoteName');
const chatNameDOM = document.getElementById('title');
let remoteUser;
// Snapshot Listener
roomRef.onSnapshot(async (snapshot) => {
for (const change of snapshot.docChanges()) {
if (roomId != null){
if (role == "creator"){
const usersInfo = roomRef.doc(roomId).collection('userInfo').doc("joiner");
usersInfo.doc('joiner').get().then(async (doc) => {
remoteUser = doc.data().joinerName;
remoteNameDOM.innerHTML = `${remoteUser} (Other)`;
chatNameDOM.innerHTML = `Chatting with ${remoteUser}`;
})
}
}
}
})

Is there a way to get reactions on an old message in DiscordJS

I'm using DiscordJs V12.3.1 and I'm trying to return a list of users that have reacted to an old message. (One that was created before the bot was started).
In my current implementation, I get the fetch the message by ID, map through all reactions, then fetch the reaction, and finally map through the users in the reaction.
let cacheChannel = msg.guild.channels.cache.get(channelID);
if(cacheChannel){
cacheChannel.messages.fetch(messageID).then(reactionMessage => {
reactionMessage.reactions.cache.map(async function(reaction){
reaction.fetch().then(r => {
r.users.cache.map(item => {
if(!item.bot) console.log(item.id);
})
})
});
});
}
The above code works for any reactions that were made since the bot started, however fails to find and users that reacted to the message before the bot was started.
Is there a better way to go about this?
Thanks in advance.
EDIT: Updated the above code based on Lioness100's comment
EDIT2: Managed to get it working with resolve(), see answer below
Right, managed to get it working with the use of .resolve() rather than fetching or mapping through the cache.
var getReactedUsers = async(msg, channelID, messageID, emoji) => {
let cacheChannel = msg.guild.channels.cache.get(channelID);
if(cacheChannel){
cacheChannel.messages.fetch(messageID).then(reactionMessage => {
reactionMessage.reactions.resolve(emoji).users.fetch().then(userList => {
return userList.map((user) => user.id)
});
});
}
}
Hopefully that is helpful if anyone has the same issue. (Note: You have to pass the emoji, but you can retrieve a list of reacted emojis by using the method posted in the question or in Lioness' comment)
And thanks to Lioness100 for your responses.
Since MessageManager.fetch() returns the message's reactions, but not the users who reacted, you'll have to pair it with ReactionUserManager.fetch(). Also, why are you creating another variable for the message when you've already fetched it?
var getReactedUsers = (message) => {
// fetch the users
message.reactions.cache.users.fetch().then((users) =>
// I'm not quite sure what you were trying to accomplish with the original lines
reaction.cache.map((item) => item.users.cache.array())
);
};
let cacheChannel = client.channels.cache.get(channelID);
if (cacheChannel) getReacterUsers(await cacheChannel.messages.fetch(messageID));
Edit: In your updated code, you're misusing Array.prototype.map() a lot.
let cacheChannel = msg.guild.channels.cache.get(channelID);
if (cacheChannel) {
cacheChannel.messages.fetch(messageID).then((reactionMessage) => {
console.log(
reactionMessage.reactions.cache
.each(async (reaction) => await reaction.users.fetch())
.map((reaction) => reaction.users.cache.filter((user) => !user.bot))
.flat()
);
});
}

Firestore can't get empty docs

I have collection of documents where the id of the doc is the users id.
Lets call these user documents.
Each "user document" contains a subcollection of chat messages. But not all "user documents" contains any fields (data, other than the subcollection).
I wan't to return all the doc in the collection that don't have any fields, but have a subcollection, but I seems this is not possible?
var allUserDocs = {},
count = 0,
users = firestore.collection("users");
users.get().then(snapshot => {
snapshot.forEach(doc => {
count++;
allUserDocs[count] = doc.data();
});
allUserDocs.count = count;
res.status(200).send(allUserDocs);
})
this code only returns the docs that contains fields, not the docs that only have a subcollection? Is there a way to return all?
How can i get a list of all document ids in the collection? both empty and non-empty ones? or how can I add a field to all the docs without fields if i cant access them?
There is a listDocuments method that retrieves all documents, missing or not, that have a subcollection. Here's the page in the docs that explains it.
Something like this might be what you are looking for:
let collectionRef = firestore.collection('col');
return collectionRef.listDocuments().then(documentRefs => {
return firestore.getAll(...documentRefs);
}).then(documentSnapshots => {
for (let documentSnapshot of documentSnapshots) {
if (documentSnapshot.exists) {
console.log(`Found document with data: ${documentSnapshot.id}`);
} else {
console.log(`Found missing document: ${documentSnapshot.id}`);
}
}
});
You would not care whether the docRef exists or not.
Nevertheless, it does not sound like a good solution to have empty documents. What is the logic you were pursuing with an architecture where users can be empty, but messages underneath them still matter? Maybe if you still need to access them you can add a boolean variable to determine if the user is active or not, instead of leaving a blank document.
Use doc.id
users.get().then(snapshot => {
snapshot.forEach(doc => {
count++;
allUserDocs[doc.id] = doc.data();
});
allUserDocs.count = count;
res.status(200).send(allUserDocs);
})

Categories