Get Every Document from inside a Users Collection Firebase - javascript

I have written a Firebase cloud function in which I want to get every users internal collection called 'numbers' and read each document out of that collection to do some comparisons.
Any idea how to do this?
I am pretty new to firebase and for some reason the database navigation commands are just not sticking with me very well.
I have tried a handful of commands with no success
const snapshot = functions.database.collection('users').collection('numbers').get()
let sfRef = db.collection('users');
sfRef.getCollections().then(collections => {
collections.forEach(collection => {
console.log('Found subcollection with id:', collection.id);
});
});
Here is a loose cloud code infastructure
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
export const prize1 = functions.pubsub.schedule('every 5 minutes').onRun((context) => {
const users = functions.database.ref('/users/numbers')
console.log("")
return null;
});
I feel like I have a good idea of how to do it, but the syntax is holding me back.
The collection of users. Go through each document in here, i.e. each user.
In each user go to the collection called numbers.
In the collection called numbers go through each document and find the numbers field to do logic/comparisons with.
Hopefully this can help you understand the way my database is ordered.

You could try it like this:
let usersRef = db.collection('users');
let allUsers = usersRef.get();
.then(userSnapshot => {
userSnapshot.forEach(userDoc => {
userDoc.ref.collection('numbers').get().then(numSnapshot => {
numSnapshot.forEach(numDoc => {
console.log(numDoc.data().numbers);
// here you got your numbers document with the numbers field
});
});
});
})
.catch((error) => {
console.log("Error getting document: ", error);
});
For more information you can look here and here.

You can't use functions for accessing the database. What you've defined as functions is for building triggers that respond to events. If you want to get data from Cloud Firestore, you should be using the Firebase Admin SDK via your admin instead. It might also help if you look through the official samples.
I will also point out that your code samples appear to be split between accessing Cloud Firestore and Realtime Database, which are different database products. Your screenshot shows Firestore, so ignore any APIs for Realtime Database.

Related

Importing User Data from filtered Array (VUE3 + Quasar + Firebase)

I am importing the data from the currently signed in user in order to manage the entire user profile page and all the associated actions.
On one hand I have the auth.currentUser and on the other I have the USERS collection in the db which stores all the additional data related to that particular user.
Now, my question concerns optimization. What would be the ideal way to get this user's data? Currently I am getting the entire users collection and filtering to get the one that matched the uid from the route params, yet I was told that loading the entire users collection and filtering the one I want to display was less than ideal, that I should rather create a function to get a specific user by a property such as name or id. This is what confuses me, is that not essentially what I am doing by filtering the users collection? How else would it be best to get that user's info? By creating this function in the Store and not in the component itself?
Currently it's looking like this:
UserPage.vue
const storeUsers = useUserStore();
const users = storeUsers.users;
const route = useRoute();
const id = route.params.id;
const userData = computed(() => {
return users.find((u) => u.uid == id);
});
Any way to optimize this would be appreciated.
*Adding a screenshot of the Firestore console (data model):
Your code is loading every document from the users collection into your application code, and then choosing there which single document you are actually interested in. Since you pay for every document read from the database, and you (and your users) pay for all bandwidth that is used, this is wasteful - especially as you start adding more users to the collection.
Instead you should use a query to read only the document(s) you are interested in from the database into your application code. Read the documentation for examples for all supported SDK versions.
finally solved it using a query as suggested. I am triggering the getUserInfo action whenever a user signs in and then assigning it to a pinia state called currentUserData:
AUTH STORE
async getUsers() {
onSnapshot(userCollectionRef, (querySnapshot) => {
let users = [];
querySnapshot.forEach((doc) => {
let user = {
did: doc.id,
...doc.data(),
};
this.users.push(user);
});
});
},
getUserInfo(userCredential) {
const q = query(
userCollectionRef,
where("uid", "==", userCredential.user.uid)
);
onSnapshot(q, (snapshot) => {
let currentUserData = [];
snapshot.docs.forEach((doc) => {
currentUserData.push({ ...doc.data(), id: doc.id });
});
this.currentUserData = currentUserData;
});
}

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.

Firebase function not being triggered when data base is written to

My database is structured as follows Collection("Message").Document("message")
But in reality, I want any change in the database's main collection to be monitored—when a document is added. I added the message document because I thought that maybe the function wasn't being called since my documents are the auto-generated ones. However, the problem persists...
Just for background I am an iOS developer, so perhaps I am doing something wrong here:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendPushNotifications = functions.database.ref('/Messages/{message}').onCreate((snapshot,context) => {
console.log(snapshot);
console.log(context);
var topic = "/topics/sentMessages";
var payload = {
data: {
message : 'You recieved a new message!'
}
}
return admin.messaging().sendToTopic(topic,payload).then((response) => {
return response;
})
})
For additional background: The application receives push notifications fine when using the console whether it be directly to the testing device or using topics. This problem is strictly when writing to firebase Firestore...
When you said "Collection("Message").Document("message")" that suggested to me that you're using Firestore as your database. However, your function is targeting changes to Realtime Database, which is a completely different thing. functions.database builds function for Realtime Database. functions.firestore builds functions for Firestore. You will want to read the documentation on Firetore triggers to learn how to write them.

How to notify the front end that it needs to refresh after setting a custom claim with Firebase cloud functions onCreate listener

I'm trying to initialize a user upon registration with a isUSer role using custom claims and the onCreate listener. I've got it to set the correct custom claim but the front end is aware of it only after a full page refresh.
I've been following this article, https://firebase.google.com/docs/auth/admin/custom-claims?authuser=0#logic, to notify the front end that it needs to refresh the token in order to get the latest changes on the custom claims object, but to be honest I don't quite fully understand what's going on in the article.
Would someone be able to help me successfully do this with the firestore database ?
This is my current cloud function:
exports.initializeUserRole = functions.auth.user().onCreate(user => {
return admin.auth().setCustomUserClaims(user.uid, {
isUser: true
}).then(() => {
return null;
});
});
I've tried adapting the real-time database example provided in the article above to the firestore database but I've been unsuccessful.
exports.initializeUserRole = functions.auth.user().onCreate(user => {
return admin.auth().setCustomUserClaims(user.uid, {
isUser: true
}).then(() => {
// get the user with the updated claims
return admin.auth().getUser(user.uid);
}).then(user => {
user.metadata.set({
refreshTime: new Date().getTime()
});
return null;
})
});
I thought I could simply set refreshTime on the user metadata but there's no such property on the metadata object.
In the linked article, does the metadataRef example provided not actually live on the user object but instead somewhere else in the database ?
const metadataRef = admin.database().ref("metadata/" + user.uid);
If anyone could at least point me in the right direction on how to adapt the real-time database example in the article to work with the firestore database that would be of immense help.
If my description doesn't make sense or is missing vital information let me know and I'll amend it.
Thanks.
The example is using data stored in the Realtime Database at a path of the form metadata/[userID]/refreshTime.
To do the same thing in Firestore you will need to create a Collection named metadata and add a Document for each user. The Document ID will be the value of user.uid. Those documents will need a timestamp field named refreshTime.
After that, all you need to do is update that field on the corresponding Document after the custom claim has been set for the user. On the client side, you will subscribe to changes for the user's metadata Document and update in response to that.
Here is an example of how I did it in one of my projects. My equivalent of the metadata collection is named userTokens. I use a transaction to prevent partial database changes in the case that any of the steps fail.
Note: My function uses some modern JavaScript syntax that is being transpiled with Babel before uploading.
exports.initializeUserData = functions.auth.user().onCreate(async user => {
await firestore.collection('userTokens').doc(user.uid).set({ accountStatus: 'pending' })
const tokenRef = firestore.collection('userTokens').doc(user.uid)
const userRef = firestore.collection('users').doc(user.uid)
const permissionsRef = firestore.collection('userPermissions').doc(user.email)
await firestore.runTransaction(async transaction => {
const permissionsDoc = await transaction.get(permissionsRef)
const permissions = permissionsDoc.data();
const customClaims = {
admin: permissions ? permissions.admin : false,
hasAccess: permissions ? permissions.hasAccess : false,
};
transaction.set(userRef, { name: user.displayName, email: user.email, getEmails: customClaims.hasAccess })
await admin.auth().setCustomUserClaims(user.uid, customClaims)
transaction.update(tokenRef, { accountStatus: 'ready', refreshTime: admin.firestore.FieldValue.serverTimestamp() })
});
})

Getting id of the current user from Cloud Functions and Firebase

I am using google cloud functions to register push notifications through firebase. In my app, i have a notifications reference that changes for a current user whenever they get a new follower or like, etc. As of right now I am able to send the notification to the phone whenever that whole reference child changes
For example, if any single post is liked, then it will send a notification. What I need to do is observe the current user to only send the notification that single person.
Here is my JavaScript file
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendPushNotification = functions.database.ref('/notification/{id}').onWrite(event => {
const payload = {
notification: {
title: 'New message arrived',
body: 'come check it',
badge: '1',
sound: 'default',
}
};
return admin.database().ref('fcmToken').once('value').then(allToken => {
if (allToken.val()) {
const token = Object.keys(allToken.val());
return admin.messaging().sendToDevice(token, payload).then(response => {
});
}
});
});
I would like to replace this line:
functions.database.ref('/notification/{id}').onWrite(event => {
With this:
functions.database.ref('/notification/{id}').(The current user ID).onWrite(event => {
How do I get the current users id?
You seem very new to JavaScript (calling it JSON is sort-of a give-away for that). Cloud Functions for Firebase is not the best way to learn JavaScript. I recommend first reading the Firebase documentation for Web developers and/or taking the Firebase codelab for Web developer. They cover many basic JavaScript, Web and Firebase interactions. After those you'll be much better equipped to write code for Cloud Functions too.
Now back to your question: there is no concept of a "current user" in Cloud Functions. Your JavaScript code runs on a server, and all users can trigger the code by writing to the database.
You can figure out what user triggered the function, but that too isn't what you want here. The user who triggered the notification is not the one who needs to receive the message. What you want instead is to read the user who is the target of the notification.
One way to do this is to read it from the database path that triggered the function. If you keep the notifications per user in the database like this:
user_notifications
$uid
notification1: ...
notification2: ...
You can trigger the Cloud Function like this:
exports.sendPushNotification = functions.database.ref('/user_notification/{uid}/{id}').onWrite(event => {
And then in the code of that function, get the UID of the user with:
var uid = event.params.uid;
For Swift 3.0 - 4.0
You can do this:
import Firebase
import FirebaseAuth
class YourClass {
let user = Auth.auth().currentUser
let userID = user.uid
// user userID anywhere
}

Categories