Read random user firestore - node.js

Whenever a user uses the Firebase Auth to register on my app, I create a document in a users collection of Firestore that stores metadata such as pseudo, userType, gender ...
To do that, the document id is exactly the same as the uid provided automatically by Firebase Auth (from the user UserRecord object)
Now, my app needs to fetch a user randomly from the users collection in Firestore.
I read Firestore: How to get random documents in a collection but this post suggest that I create the ID myself. The app is already built using the FirebaseAuth generated ID so what is the solution ?

A common practice in firestore is to create a "--Stats--" document within a collection (--Stats-- being the document id). This document can house information about the collection (number of documents, last updated, collection privileges etc.).
In your case, you could use cloud functions/triggers to keep tract of the total number of users in the users collection and add the id of a new user to a "userIds" array. You could keep both of these fields in the users collection's --Stats-- document. This way, when you wanted to get a random user, you could randomly generate a number betweeen 0 and the document count, then use it as an index of the userIds array. I might look something like this:
var usersCollectionRef= db.collection("users");
usersCollectionRef.doc("--Stats--").get().then((doc) => {
let numberOfUsers = doc.data().numberOfUsers;
let userIdArray = doc.data().userIds;
let randomNumber = Math.floor(Math.random() * (numberOfUsers + 1));
return usersCollectionRef.doc(userIdArray[randomNumber]).get();
}).then((doc) => {
...do something with the user's document
})

Related

How to Update an Object inside an Array inside a MongoDB Collection using Mongoose

This Is An Expense Tracker Application
I'm Trying to Make An Update Route where The User Can Update the Value of An Expense
For Example, I want to update the First Expense Name from Bike to Bike Rental & Amount from 250 to 500
And similarly, make a generic endpoint where the user can update any expense whenever necessary
Here A Snapshot of My Express Get Route & The Patch route which I want to fill
The ObjectID of the Entire Collection which is 607bbb07e1ebb63a3033af15 will be sent to the backend from the Frontend.
I am using Express, Mongoose.
You can run update on the model you have created like this:
const filter = {_id :"(*User Id Here*)", "Expenses._id": "(*Expense Id Here*)"}; //check the type of _id if this doesn't work
const query = {$set: {"Expenses.$.Name" : "value"}};
User.update(filter, query);
Or,
User.update({_id :"ObjectId(pass id)", "Expense._id" : ObjectId'pass id'}, {'$set': {'Expenses.$.Name': 'name update'});
Similarly, You can do this for any key in the object provided that the key exists in it. Now this will check the

Is there a function in mongoose where I can check if a certain id exists in one collection and insert in other collection if it does?

I have 2 models - Driver and User. Both of them rate each other, so while creating the API, how can I check whether a certain userId exists in db, and if it exists, I want to add to my driver's rating array such that a new object is added like this.
{userId:xyz/*Already Checked in the Db that it exists*/,rating:4}
It sounds like you need to use two requests.
const user = await Users.findOne({_id: userId});
if (user){
const driver = await Drivers.findOne({_id: driverId});
driver.ratings.push({userId, rating: 4})
await driver.save();
}

How to start Firestore query from a particular document number without using OFFSET?

I have a Firestore collection named 'users' and has many documents by the name of each user.
I want to retrieve list of 25 users at a time in alphabetical order and this is what I tried:
const allUsersRef = admin.firestore().collection('users').orderBy('name').offset(0).limit(25)
allUsersRef.get().then((top25Users) => {
let usersList = '``` << Users LIST >>\n'
if (!top25Users.empty) {
top25Users.forEach(eachUser => {
usersList = usersList + `\n${eachUser.data().name} \n${eachUser.data().id}`
})
console.log(usersList)
return
} else {
message.channel.send('Looks like we have no users at the moment!')
return
}
}).catch((error) => {
console.log(error)
return
})
This way I can get the top 25 users easily! But what if I want the next 25? This is a Discord Bot and not an Android Application where I can add a button [view more] and then continue the results query.start() as shown in this firebase video
I can use OFFSET but the number of users is large so using offset(500) won't be affordable :'(
Also I need to fetch users in alphabetical order and when new users register, the order changes.
TL,DR: If I had a list of my users in alphabetical order, how do I get users from 126th position to 150th position on the list which is sort of page 5 for my 25/page query! and without using offset because that just uses more resources!
I had this in firebase realtime database first but then I needed some more advanced querying so I have migrated here :)
Database Struture: Just a single collection named USERS and documents named as username in it.
PS:
const startAtRes = await db.collection('cities')
.orderBy('population')
.startAt(1000000)
.get();
Using something like this ^ from Firebase Documentation is not possible because I won't be knowing from where to start from. As the list changes as new users Register!
Firestore does not support efficient offset based pagination. When you use offset(), you're paying for reads of all the documents up to that point. The only availabe efficient pagination requires that you provide an anchor document, or properties of the anchor document, to navigate between pages, as described in the documentation.

In Cloud function how can i join from another collection to get data?

I am using Cloud Function to send a notification to mobile device. I have two collection in Firestore clientDetail and clientPersonalDetail. I have clientID same in both of the collection but the date is stored in clientDetail and name is stored in clientPersonal.
Take a look:
ClientDetail -- startDate
-- clientID
.......
ClientPersonalDetail -- name
-- clientID
.........
Here is My full Code:
exports.sendDailyNotifications = functions.https.onRequest( (request, response) => {
var getApplicants = getApplicantList();
console.log('getApplicants', getApplicants);
cors(request, response, () => {
admin
.firestore()
.collection("clientDetails")
//.where("clientID", "==", "wOqkjYYz3t7qQzHJ1kgu")
.get()
.then(querySnapshot => {
const promises = [];
querySnapshot.forEach(doc => {
let clientObject = {};
clientObject.clientID = doc.data().clientID;
clientObject.monthlyInstallment = doc.data().monthlyInstallment;
promises.push(clientObject);
});
return Promise.all(promises);
}) //below code for notification
.then(results => {
response.send(results);
results.forEach(user => {
//sendNotification(user);
});
return "";
})
.catch(error => {
console.log(error);
response.status(500).send(error);
});
});
}
);
Above function is showing an object like this
{clienId:xxxxxxxxx, startDate:23/1/2019}
But I need ClientID not name to show in notification so I'll have to join to clientPersonal collection in order to get name using clientID.
What should do ?
How can I create another function which solely return name by passing clientID as argument, and waits until it returns the name .
Can Anybody please Help.?
But I need ClientID not name to show in notification so I'll have to join to clientPersonal collection in order to get name using clientID. What should do ?
Unfortunately, there is no JOIN clause in Firestore. Queries in Firestore are shallow. This means that they only get items from the collection that the query is run against. There is no way to get documents from two top-level collection in a single query. Firestore doesn't support queries across different collections in one go. A single query may only use properties of documents in a single collection.
How can I create another function which solely return name by passing clientID as argument, and waits until it returns the name.
So the most simple solution I can think of is to first query the database to get the clientID. Once you have this id, make another database call (inside the callback), so you can get the corresponding name.
Another solution would be to add the name of the user as a new property under ClientDetail so you can query the database only once. This practice is called denormalization and is a common practice when it comes to Firebase. If you are new to NoQSL databases, I recommend you see this video, Denormalization is normal with the Firebase Database for a better understanding. It is for Firebase realtime database but same rules apply to Cloud Firestore.
Also, when you are duplicating data, there is one thing that need to keep in mind. In the same way you are adding data, you need to maintain it. With other words, if you want to update/detele an item, you need to do it in every place that it exists.
The "easier" solution would probably be the duplication of data. This is quite common in NoSQL world.
More precisely you would add in your documents in the ClientDetail collection the value of the client name.
You can use two extra functions in this occasion to have your code clear. One function that will read all the documents form the collection ClientDetail and instead of getting all the fields, will get only the ClientID. Then call the other function, that will be scanning all the documents in collection ClientPersonalDetail and retrieve only the part with the ClientID. Compare if those two match and then do any operations there if they do so.
You can refer to Get started with Cloud Firestore documentation on how to create, add and load documents from Firestore.
Your package,json should look something like this:
{
"name": "sample-http",
"version": "0.0.1",
"dependencies": {
"firebase-admin": "^6.5.1"
}
}
I have did a little bit of coding myself and here is my example code in GitHub. By deploying this Function, will scan all the documents form one Collection and compare the ClientID from the documents in the other collection. When it will find a match it will log a message otherwise it will log a message of not matching IDs. You can use the idea of how this function operates and use it in your code.

Twitter OAuth + Node + A MongoDB Collection: How to store users?

I've successfully retrieved a user's id and screen name from Twitter's oauth service, like so:
{ user_id: '12345678', screen_name: 'spencergardner' }
I am hoping to create a simple way for users to authenticate using Twitter (and soon Facebook, for example), so that they can add words they are interested in learning to their account. How do I now go about setting up "users" in a mongodb collection that will allow each user to have their own bank of words (and other data)?
If I understand you correctly, you are asking how you can store data with different structures in a mongo collection.
Well, you're in luck! Mongo does just that. You can store any different data structures in a mongo collection without having to "declare" the structure a priori. Just create a DBObject (if using the Java driver for example), add fields to it, and just save it. You can then retrieve it, and query the data to see what this specific users has, and anything you want in your application.
I use mongoose with nodejs to create a user model which you would then input the oauth data into and then you would be free to associate whatever data you wanted.
Once you've obtained the Oauth information you could create a new User associating the twitter data with that specific user model. The _id is automatically provided however in this case, you would use the user_id returned from twitter (assuming that is unique).
Here's an example schema:
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var userSchema = new Schema({
_id: String,
screen_name: String,
words: Array
});
module.exports = mongoose.model('User', userSchema);
In future you would be able to query the database for a particular user, and authenticate a user when they return. You would also look to create a new User with something similar to the following:
new User({ _id: req.body.user_id,
password: req.body.screen_name,
words: []
}).save(function(err) {
if (!err) {
res.send("User added");
}
})

Resources