How to query database with firebase cloud functions - javascript

I am trying to query my firestore database using cloud functions.
I want to trigger an email notification every time a new reading in my database is under the value of 10.
Here is the relevant database structure for reference: database structure.
The "readings" field is an array and each "reading" is a map which holds the fields "date" and "value".
Currently I am at the point where I can send an email notification every time a new user is created however I want this to work for the database. I am unsure how to query for the "readings" array and then for each individual reading.
Here is my code so far which sends an email when a new user is created
exports.sendNotification = functions.auth.user().onCreate((user) => {
const mailOptions = {
from: '"Spammy Corp." <noreply#firebase.com>',
to:"fakeEmail#btopenworld.com",
text: "TEST"
};
return mailTransport.sendMail(mailOptions)
.then(() => console.log("It worked"))
.catch((error) =>
console.error('There was an error while sending the email:', error));
});

See: https://firebase.google.com/docs/firestore/extend-with-functions
For example, to fire on all new readings added to that first child:
exports.sendEmail = functions.firestore
.document('sensor/UGt.../readings')
.onCreate((snap, context) => {
const newValue = snap.data();
const value = newValue.value;
if (value < 10) {
// send email
}
});
In further comments you mentioned listening for new readings in all sensor elements, not just your first one. This is unfortunately not possible in an efficient / simple way (source). Instead you will have to listen to all onUpdate events on /sensor/, check if the update is adding a reading, then check the value & send your email.
It may be easier to call the cloud function directly from wherever adds the reading, depending on how many times the /sensor/ path is going to be updated for other reasons (since every time this happens, it's a waste of resources).

Related

How to (using React JS web) and Firestore, can you find out when a chatRoom (on the Firestore Database) receives new messages?

I am trying to build an app using FireStore and React JS (Web)
My Firestore database basically has:
A collection of ChatRooms ChatRooms
Every chat-room has many messages which is a subcollection, for example:
this.db.collection("ChatRooms").doc(phone-number-here).collection("messages")
Also, every chat-room has some client info like first-name, last-name etc, and one that's very important:
lastVisited which is a timestamp (or firestamp whatever)
I figured I would put a React Hook that updates every second the lastVisited field, which means to try to record as accurately as possible on Firestore the last time I left a chat-room.
Based on that, I want to retrieve all the messages for every customer (chat-room) that came in after the last visit,
=> lastVisited field. :)
And show a notification.
I have tried from .onSnapshot listener on the messages subcollection, and a combination of Firestore Transactions but I haven't been lucky. My app is buggy and it keeps showing two, then one, then nothing, back to two, etc, and I am suffering much.
Here's my code!
Please I appreciate ANY help!!!
unread_messages = currentUser => {
const chatRoomsQuery = this.db.collection("ChatRooms");
// const messagesQuery = this.db.collection("ChatRooms");
return chatRoomsQuery.get().then(snapshot => {
return snapshot.forEach(chatRoom => {
const mess = chatRoomsQuery
.doc(chatRoom.id)
.collection("messages")
.where("from", "==", chatRoom.id)
.orderBy("firestamp", "desc")
.limit(5);
// the limit of the messages could change to 10 on production
return mess.onSnapshot(snapshot => {
console.log("snapshot SIZE: ", snapshot.size);
return snapshot.forEach(message => {
// console.log(message.data());
const chatRef = this.db
.collection("ChatRooms")
.doc(message.data().from);
// run transaction
return this.db
.runTransaction(transaction => {
return transaction.get(chatRef).then(doc => {
// console.log("currentUser: ", currentUser);
// console.log("doc: ", doc.data());
if (!doc.exists) return;
if (
currentUser !== null &&
message.data().from === currentUser.phone
) {
// the update it
transaction.update(chatRef, {
unread_messages: []
});
}
// else
else if (
new Date(message.data().timestamp).getTime() >
new Date(doc.data().lastVisited).getTime()
) {
console.log("THIS IS/ARE THE ONES:", message.data());
// newMessages.push(message.data().customer_response);
// the update it
transaction.update(chatRef, {
unread_messages: Array.from(
new Set([
...doc.data().unread_messages,
message.data().customer_response
])
)
});
}
});
})
.then(function() {
console.log("Transaction successfully committed!");
})
.catch(function(error) {
console.log("Transaction failed: ", error);
});
});
});
});
});
};
Searching about it, it seems that the best option for you to achieve that comparison, would be to convert your timestamps in milliseconds, using the method toMillis(). This way, you should be able to compare the results better and easier - more information on the method can be found in the official documentation here - of the timestamps of last message and last access.
I believe this would be your best option as it's mentioned in this Community post here, that this would be the only solution for comparing timestamps on Firestore - there is a method called isEqual(), but it doesn't make sense for your use case.
I would recommend you to give it a try using this to compare the timestamps for your application. Besides that, there is another question from the Community - accessible here: How to compare firebase timestamps? - where the user has a similar use cases and purpose as yours, that I believe might help you with some ideas and thoughts as well.
Let me know if the information helped you!

Trying to check for a certain field inside a firebase firestore document but struggling with the logic behind it

So I am using this currently to check and see if a doc exists for a user
let user = firebase.auth().currentUser;
let userInfo = db.collection("stripe_customers").doc(user.uid);
and then if it does, it runs a script I want
userInfo.get().then(function (doc) {
if (doc.exists) {
However, instead of checking for a specific doc, I need to try and check for a field value inside some documents.
For example I have a collection called "stripe_customers" > that has a document per user via their UID > then inside the document is the collection "charges" > then inside "charges" is a document under a random string of numbers and letters "89nzVNrfQCOVqogDaGvo" for example that is generated by stripe for their charge after they purchase (there may be multiple of these if they have an older charge which is why I need to find the most recent one) > then inside the most recent charge document, I need to check for the field "status" and that is has the value "succeeded". That way I can check to see who has a succeeded payment and if they do it will run the script I want. I am just so confused on how to achieve this. I know how to do basic queries but this is somewhat complex. I need to be able to make sure the current UID has that field with that value so I can see if the current UID paid or not and if they did the script runs, which sets a custom claim.
Here is a visual of my db storage flow for what im trying to do so its easier to understand https://imgur.com/a/NE1x6sU
So, you want to get the most recent document in a charges (sub)collection based on a created field which contains a timestamp value, and check the value of the status field of this doc.
The following should do the trick:
const user = firebase.auth().currentUser;
const subCollRef = db.collection("stripe_customers").doc(user.uid).collection("charges");
const query = subCollRef.orderBy('created', 'desc').limit(1);
query.get()
.then(snapshot => {
if (snapshot.size > 0 && snapshot.docs[0].data().status === "succeeded") {
//payment was succeeded, you can "run your script"
});
})
.catch(err => {
console.log('Error getting documents', err);
});

How to return single variable from Firebase function forEach loop?

I created an iOS app that uses Firebase, and I wrote a Firebase cloud function that sends a notification to all app users when there is a new item (post) in the app. I don't want users to get too many notifications, so the Firebase function also saves the notification time to the database. When the function is triggered, it retrieves the time of the last notification and only sends a new notification if a minimum amount of time has passed (currently 24 hours).
My issue is retrieving a variable from inside the forEach loop, specifically the value of the last notification time (variable 'epoch2' in the code below). I cannot see the value outside of the forEach loop. I spent a couple hours trying to solve this, including trying to push the value to an array that had been declared outside of the loop. But even that did not work, the value was not visible once I left the loop.
The only solution that I could get to work was to place the rest of my code (a second forEach loop that loops through all users and sends the notifications) inside the first forEach loop. My code is below. This technically works and accomplishes what I want, but it feels clunky and seems like not the "right" way to do it.
Is there a better/simpler way to retrieve a single value from the first forEach loop that can then remain visible in the rest of the function? I'd appreciate any suggestions.
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
// function to send notification when there is a new post
exports.sendNotification = functions.database.ref('/posts/{postId}').onCreate((snapshot, context) => {
// current time
var epoch1 = Math.round((new Date()).getTime() / 1000);
// retrieve time of last notification from database
admin.database().ref("notifications").orderByKey().limitToLast(1).on('value', function(snap1) {
snap1.forEach(function(childNodes1) {
var epoch2 = childNodes1.val().date;
var timeSinceLastNotification = epoch1 - epoch2;
// only send new notification if it has been at least 24 hours since last one
if (timeSinceLastNotification >= 86400) {
var payload = {
notification: { title: 'New Post', body: 'A new post is available' }
};
// loop through all users
admin.database().ref("users").on('value', function(snap2) {
snap2.forEach(function(childNodes2) {
var fcmToken = childNodes2.val().fcmToken;
// only send a notification if they have an fcmToken in the database
if (fcmToken) {
// SEND NOTIFICATION
}
})
})
// record notification in the database
var newNotification = admin.database().ref('notifications').push();
newNotification.set({
'date': epoch1
});
}
})
})
})

Atomic update using transaction in firebase android

I want to create a simple match making application using android and firebase.
The working is as follows
UserA comes online and a node is created on firebase.
UserB comes online and 2nd node is created on firebase. and so on.
UserA and UserB objects are
Now I want to update UserA with userB fields and userB with UserA fields.
Ensuring that no other user should interrupt in between this update. like
I tried using transaction and found that only single database reference can be changed using transaction. Is there any way by which I can update both user at the same time using transaction and ensuring that both references are either updated or failed at same time.(no in between. like one updated and other is not)
A cloud function can also be used but not know how to use multi path update inside a transaction for real time database.
EDIT-1
I have moved to firebase fire store and so far tried below code with firebase functions. Not sure it will server the purpose when user base increases.
function matchedUser(xSnapshot, mSnapshot) {
return db.runTransaction(t => {
console.log(mSnapshot.mUserId)
console.log(xSnapshot.mUserId)
console.log("Other Snap ID" + otherSnapshot.mUserId)
console.log("My Snap ID" + mySnapshot.mUserId)
const xUserRef = db.collection("Users").doc(otherSnapshot.mUserId)
return t.get(xUserRef).then(doc => {
t.update(xUserRef, otherSnapshot)
const mUserRef = db.collection("Users").doc(mySnapshot.mUserId)
t.update(mUserRef, mySnapshot)
}).catch(error => {
console.log(error)
})
})
.then(result => {
console.info('Transaction success!')
return "Success"
})
.catch(err => {
console.error('Transaction failure:', err)
return "Failed"
})
}

delete incoming write event after calculations in firebase functions

I have an app that uses firebase, the whole stack pretty much, functions, database, storage, auth, messaging, the whole 9. I want to keep the client end very lightweight. So if a user comments on a post and "tags" another user, let's say using the typical "#username" style tagging, I moved all of the heavy lifting to the firebase functions. That way the client doesn't have to figure out the user ID based on the username, and do everything else. It is setup using triggers, so when the above scenario happens I write to a "table" called "create_notifications" with some data like
{
type: "comment",
post_id: postID,
from: user.getUid(),
comment_id: newCommentKey,
to: taggedUser
}
Where the taggedUser is the username, the postID is the active post, the newCommentKey is retrieved from .push() on the comments db reference, and the user.getUid() is from the firebase auth class.
Now in my firebase functions I have a "onWrite" trigger for that specific table that gets all of the relevant information and sends out a notification to the poster of the post with all the relevant details. All of that is complete, what I am trying to figure out is... how do I delete the incoming event, that way I don't need any sort of cron jobs to clear out this table. I can just grab the event, do my needed calculations and data gathering, send the message, then delete the incoming event so it never even really exists in the database except for the small amount of time it took to gather the data.
A simplified sample of the firebase functions trigger is...
exports.createNotification = functions.database.ref("/create_notifications/{notification_id}").onWrite(event => {
const from = event.data.val().from;
const toName = event.data.val().to;
const notificationType = event.data.val().type;
const post_id = event.data.val().post_id;
var comment_id, commentReference;
if(notificationType == "comment") {
comment_id = event.data.val().comment_id;
}
const toUser = admin.database().ref(`users`).orderByChild("username").equalTo(toName).once('value');
const fromUser = admin.database().ref(`/users/${from}`).once('value');
const referencePost = admin.database().ref(`posts/${post_id}`).once('value');
return Promise.all([toUser, fromUser, referencePost]).then(results => {
const toUserRef = results[0];
const fromUserRef = results[1];
const postRef = results[2];
var newNotification = {
type: notificationType,
post_id: post_id,
from: from,
sent: false,
create_on: Date.now()
}
if(notificationType == "comment") {
newNotification.comment_id = comment_id;
}
return admin.database().ref(`/user_notifications/${toUserRef.key}`).push().set(newNotification).then(() => {
//NEED TO DELETE THE INCOMING "event" HERE TO KEEP DB CLEAN
});
})
}
So in that function in the final "return" of it, after it writes the finalized data to the "/user_notifications" table, I need to delete the event that started the whole thing. Does anyone know how to do that? Thank you.
First off, use .onCreate instead of .onWrite. You only need to read each child when they are first written, so this will avoid undesirable side effects. See the documentation here for more information on the available triggers.
event.data.ref() holds the reference where the event occurred. You can call remove() on the reference to delete it:
return event.data.ref().remove()
The simplest way to achieve this is through calling the remove() function offered by the admin sdk,
you could get the reference to the notification_id through the event, i.e event.params.notification_id then remove it when need be with admin.database().ref('pass in the path').remove(); and you are good to go.
For newer versions of Firebase, use:
return change.after.ref.remove()

Categories

Resources