Connecting two promises in a Firebase web app - javascript

I am using a Realtime Database to handle some data in a Firebase web app.
In the code hereafter, I want to insert a record in the DB only if fieldOne and fieldTwo aren't going to be duplicated.
dbReference.orderByChild("fieldOne").equalTo(fieldOneVal).once('value')
.then(function(snapshot) {
if (snapshot.exists()) {
alert('This fieldOne has already been used.')
reject();
}
}).then( // What is the correct way to connect this line and the following ??
dbReference.orderByChild("fieldTwo").equalTo(fieldTwoVal).once('value')
.then(function(snapshot) {
if (snapshot.exists()) {
alert('This NAME has already been used.')
reject();
}
// All is now OK.
.... do the final things here .....
}).catch(function(error) {
// An error happened.
alert('This record cannot be inserted.')
});
At this point, I am able to tweak the code to make things work the way I wish. But my issue is that I am not doing things the proper way (I know that due to some messages I can see in the console). The comment in my code shows where I need to know the correct way to connect the two parts.
What is the .... the following ??
For information the DB looks like this:
MyList
+ -M93j....443cxYYDSN
fieldOne: "asdc..."
fieldTwo: "Gkk...."
+ -M94j.........OZS6FL
fieldOne: "afc..."
fieldTwo: "SDFSk...."

The following Promises chaining combined with errors throwing should do the trick.
dbReference
.orderByChild('fieldOne')
.equalTo(fieldOneVal)
.once('value')
.then(function (snapshot) {
if (snapshot.exists()) {
throw new Error('fieldOneExists');
}
return dbReference
.orderByChild('fieldTwo')
.equalTo(fieldTwoVal)
.once('value');
})
.then(function (snapshot) {
if (snapshot.exists()) {
throw new Error('fieldTwoExists');
}
// All is now OK.
//.... do the final things here .....
})
.catch(function (error) {
if (
error.message === 'fieldOneExists' ||
error.message === 'fieldTwoExists'
) {
console.log('This record cannot be inserted');
} else {
console.log('other error');
}
});
However, it would probably be better to use a Transaction for checking the existence of the two values for the fieldOne and fieldTwo fields.
The problem is that the Realtime Database transactions don't work with queries: you need to exactly know the location of the data to be modified (or to be checked for existence/non-existence). So you would need to adapt your data model if it appears that you really need a transaction (which depends on your exact global requirements).
For example you could create database nodes with the concatenation of the values of fieldOne and fieldTwo values and check the existence of such a node in a Transaction. But, again, the feasibility of this approach depends on your exact global requirements, which we don't know.

Try the hasChild() method.
if(snapshot.hasChild('name') {
// ... do stuff !
}
Or,
create a new node in your firebase real-time db named as usernames and add each username in this list. In future before inserting a new username check if it's present in this list

Related

How to write data in Firebase Realtime Database as [custom key : value] in React?

What I'm trying to achieve is having random IDs in a dedicated node in Firebase Realtime Database, while being able to check if an ID already exists in the node before writing it to prevent duplicity.
I'm working with web React (JavaScript).
The database currently looks like this:
What I'm aiming for is to have another node with IDs in the following format:
Note: Generated IDs in 'users' node are different from those in 'ids', they're not connected.
ID keys in [key : value] pairs, such as 'abcd', shouldn't be generated by Firebase (as push() method does).
Instead, IDs are generated separately before writing data.
So far, I've found a few resources with similar questions, but they were either related to a different platform with another syntax or outdated.
My current code to read IDs from the 'ids' node appears to be working, if the node with IDs is manually created in database console, but it appears that I still need help with the code to write data in the desired format.
This is the code for reading data:
function checkIdDuplicity(id)
{
const database = getDatabase();
get(child(ref(database), 'ids/'))
.then((snapshot) => {
if (snapshot.exists()) {
// Snapshot exists, find possible duplicate
if (snapshot.hasChild(id)) {
// Such ID is already in database
console.log("ID already exists");
return true;
}
else {
// ID is unique
return false;
}
}
else {
// No IDs in database, node shall be created
return false;
}
})
.catch((error) => {
console.log(error);
});
}
Would anyone have any ideas? 🙂
To add a node with custom key, you need to use set() instead of push().
const nodeRef = child(ref(database), "ids/" + id); // id = custom ID you want to specify
await set(nodeRef, { ...data })
Also, you are downloading entire ids/ node to check existence of a given ID. Instead you can just download that particular ID:
// specify the custom ID to check for in ref
get(child(ref(database), 'ids/' + id)) .then((snapshot) => {
if (snapshot.exists()) {
console.log("ID already exists in database")
} else {
console.log("ID does not exist in database")
}
})

Is it possible if I can get the last key (latest message) added from the realtime database?

I would like to get the last key (the latest message) from my realtime database but not sure how this can be achieved.
I see from this link i need to get Last child of my firebase databse that I can use orderByKey().limitToLast(1) to get this but it looks like I need to specify the complete ref in order to achieve this. Is that correct? Or is it possible if I can orderByKey().limitToLast(1) on the val()? Or is there another way I can achieve this?
Here is my messages structure in the database:
I have a timestamp child under each key as shown above which I thought I could query in order to extract the latest key but I really don't know how to do this. Can someone please help? Below is my code so far:
database().ref(`messages/`).once(`value`, snapshot => {
if(snapshot.exists()) {
snapshot.forEach(function (childSnapshot) {
if(childSnapshot.key.includes(auth().currentUser.uid)) {
console.log("show me the key: "+childSnapshot.key)
//not working
console.log("show last message: "+ JSON.stringify(childSnapshot.val().orderbyKey().limitToLast(1)))
}
})
}
})
console.log(JSON.stringify(messages)) => [{"-MfqYBzbusp1Cljgxpan":{"unreadMessage":true,"user":{"name":"Mike","avatar":"xxxxxx","_id":"tFhmw5oQoPhk8nF2sx5rE5BFqw93"},"timestamp":1627634061437,"senderId":"tFhmw5oQoPhk8nF2sx5rE5BFqw93","notification":{"body":"Hey","title":"Project","imageUrl":"./assets/xxxxx.png"},"text":"Hey"}}]
console.log(JSON.stringify(unreadMsgs)) => []
Firebase Realtime Database queries work on a flat list of nodes. So if you have a specific path /messages/nodeid already, you can find the latest message under that, but you can't find the latest message across all of /messages.
Reading all messages from all chatrooms, just to find the latest message for each chatroom this user is in is really wasteful though. As you add more users to the app, you're driving up the bandwidth cost for them, and for yourself too.
I recommend keeping a separate node where you track the chat rooms for each user, as explained in my answer on Best way to manage Chat channels in Firebase. With such a node you can then easily determine just the chat rooms for the current user, and then load the latest message for each of them with something like:
database().ref(`user_chatrooms/${auth().currentUser.uid}`).once(`value`, indexSnapshot => {
indexSnapshot.forEach((indexSnapshotChild) => {
let chatroomId = indexSnapshotChild.key;
let query = database().ref(`messages/${chatroomId}`).orderByChild("timestamp").limitToLast(1)
query.once(`value`, (msgSnapshot) => {
console.log(`Last message in ${chatroomId} was ${msgSnapshot.val().text}`);
})
}
})
The orderByKey and limitToLast methods exists on a DatabaseReference and not on the value you fetch from the snapshot fetched earlier. It seems the parent key for all messages is of format userId1userId2. If you know this combination then you run your query this way.
const uidsKey = "uid1" + "uid2"
const query = database().ref(`messages/${uidsKey}`).orderByChild("timestamp").limitToLast(1)
query.once("value").then((snapshot) => {
console.log(snapshot.val())
})
But it seems you are trying to get UIDs of others users who have chats with user1 and trying to real all nodes first. I won't recommend doing that as that might have issues with security rules and so on. Instead if you keep list of those UIDs somewhere else, it'll be better. But if you want to keep what you have right now, try this:
const userUID = auth().currentUser.uid
database().ref("messages/").once("value").then(async (msgSnapshot) => {
const keys = Object.keys(msgSnapshot.val() || {})
const userChatKeys = keys.filter(k => k.includes(userUID))
//const otherUserIDs = userChatKeys.map(k => k.replace(userUID, ""))
//userChatKeys will be chat IDs where current user is included
//now follow the same steps mentioned in first code snippet
const queries = userChatKeys.map(chat => database().ref(`messages/${chat}`).orderByChild("timestamp").limitToLast(1).once("value"))
const lastMessagesSnap = await Promise.all(queries)
const messages = lastMessagesSnap.map(m => Object.values(m.val())[0]))
console.log(`messages: ${messages}`)
const unreadMsgs = messages.filter((msg) => msg.unreadMessage === true)
console.log(unreadMsgs.length)
})
This will logs last message from each of user's chat.

Confusing error message using updateOne() function on a mongodb collection

To summarize, I have a mongodb database with a 'groupcollection' in it. One of the attributes is called 'deleted' and it has values either true or false. I want to update the value of 'deleted' for a specific document using 'groupname' as the query attribute. However, when I try the code below I receive the error "TypeError: collection.updateOne is not a function"
router.post('/deletegroup', function(req, res) {
var db = req.db;
var collection = db.get('groupcollection');
var filter = {"groupname" : req.body.groupname};
var updates = { $set: {"deleted" : true} };
collection.updateOne(filter, updates, function(err) {
if (err) {
// If it failed, return error
res.send("There was a problem deleting the group from the database.");
}
else {
// And forward to success page
res.redirect("grouplist");
}
});
});
I've read the documentation on updateOne() for Node.js from mongoDB and I can't seem to figure out the reason for the error. Also, I am still very new to javascript/nodejs/mongo so I would greatly appreciate more informative answers!
The solution I came up with was using unique IDs for each group and instead of using updateOne() just using update() and having the unique ID as the query to make sure that I don't modify groups with the same name

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client. Attempting to write Mongo ObjectID to cookies

I will preface this by saying I understand it is not best practice to put a userID in cookies, but this is an extremely simple application that is just supposed to display to the same user the number of times they have visited the site, so I'm just rolling with this implementation to keep things simple.
I'm using express, node, and mongoDB to handle this. What I'm trying to do is create and save a new instance of my User class, and then in the promise chain (where the Mongo ObjectID is returned), I'd like to write that ID to cookies so I can keep track of how many times they have come to the site.
It seems like I am receiving the Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers because I am trying to set cookies in the then statement which is completed after the headers are already sent off. I don't really understand this though, because I don't call setHeaders anywhere else.I have thought about creating a randomly generated number within the server, but that seems clunky as I can't guarantee it won't generate an existing ID. Does anyone know how I can get around this issue, or am I taking the complete wrong approach here?
User schema:
var UserSchema = new Schema({
timesSeen: { type: Number, default: 0 },
});
The problem code
router.get("/", function (req, res, next) {
// Read cookies
let cookies = parseCookies(req.headers.cookie);
// If userID cookies is not set, this is a new user (or one visiting from another browser)
if (cookies.userID === undefined) {
// Create a new instance of a user and save to mongoDB
var newUser = new User({});
newUser
.save()
.then((newUser) => {
console.log("newUser");
// This is the line that is giving me problems.
res.setHeader("Set-Cookie", [
`userID=${newUser._id}`,
"timesSeen=0",
]);
})
.catch((err) => {
if (err) {
console.log(err);
return;
}
});
} else {
let timesSeen = Number(cookies.timesSeen) + 1;
// let timesSeen = parseInt(cookies.timesSeen)++;
// console.log("times", timesSeen);
res.setHeader("Set-Cookie", [`timesSeen=${timesSeen}`]);
res.render("index", {
title: "Express",
});
}
});

Call getUsers() function from MongoDB driver

I am currently building an api application that checks the status and gets information of various types of dbs(i.e. Mongo, MySQL) using Sailsjs such as users, depending on a user input. Here is a snippet of the code I am working on. The local host is just the test database I am connecting to, but in the future it will be supplied by the user.
var mp = require('mongodb-promise');
var MongoClient = require('mongodb');
mp.MongoClient.connect("mongodb://#localhost:27017/test")
.then(function(db){
db.getUsers().then(function(users){
res.ok(users);
})
})
.fail(function(err) {
console.log(err);
})
I am attempting to use promises for the async issue. The problem I am having is that it doesn't work. It tells me that that Object[object object] has no method 'getUsers'. I have searched and can't seem to find a solution that works.
If I change the function to the below, I get the some data back.
mp.MongoClient.connect("mongodb://#localhost:27017/IMS")
.then(function(db){
db.stats().then(function(stats){
return res.ok(stats);
})
})
.fail(function(err) {
console.log(err);
dbObject.vipUp = false;
})
I am not sure what the issue is or how to solve it.
What you are doing here is using the node native driver methods to connect and inspect the database. There is in fact "no such method" as .getUsers() here in this API or in fact in any other API.
The .getUsers() function is just a "shell helper" that is basically implemented like this:
function (args) {
var cmdObj = {usersInfo: 1};
Object.extend(cmdObj, args);
var res = this.runCommand(cmdObj);
if (!res.ok) {
var authSchemaIncompatibleCode = 69;
if (res.code == authSchemaIncompatibleCode ||
(res.code == null && res.errmsg == "no such cmd: usersInfo")) {
// Working with 2.4 schema user data
return this.system.users.find({}).toArray();
}
throw Error(res.errmsg);
}
return res.users;
}
So what you should be able to see here is that this normally wraps a "command" form, or otherwise falls back for compatibility with MongoDB 2.4 to querying the system.users collection on the current database.
Therefore, instead of calling a method that does not exist, you then need to use the .command() method instead:
mp.MongoClient.connect("mongodb://#localhost:27017/test")
.then(function(db){
db.command({ "usersInfo": 1}).then(function(users){
res.ok(users);
})
})
.fail(function(err) {
console.log(err);
})
Or in the case of connecting to a MongoDB 2.4 instance, then fetch from the .collection():
mp.MongoClient.connect("mongodb://#localhost:27017/test")
.then(function(db){
db.collection('system.users').find().toArray().then(function(users){
res.ok(users);
})
})
.fail(function(err) {
console.log(err);
})
At any rate, you really should be establishing the database connection elsewhere in your application ( or re-using the underlying driver connection from another store ), and then calling methods on the connection already establihed. This is always preferable to creating a connection on the request of the information you want to retrieve.
Also, recent versions of the node native driver support promises right out of the box. So there may be no need to configure in anything else, depending on how you intend to use it.

Categories