How do I access another user's data by a specific field? - javascript

I'm trying to make a user send their num to another user.
I created a random keycode for every user to send each other a num.
I tried accessing the data by querying them.
const sendNum = async(e) => {
const userCol = collection(db, "users")
e.preventDefault();
const targetQuery = query(userCol, where("keycode", "==", target))
const targetSnapshot = await getDocs(targetQuery)
targetSnapshot.forEach((doc) => {
console.log(doc.data().num);
})
console.log(targetSnapshot);
But it returns an object rather than the another user's num field
NuĀ {_firestore: xc, _userDataWriter: ah, _snapshot: Oo, metadata: Su, query: Ic}
Here's what the data looks like:

Posting the solution suggested by Doug Stevenson as a Community Wiki for visibility.
From the description it's not possible to tell what value the target holds.
In this case, hard coding the value worked to access the user field.

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;
});
}

Issues connecting data stream to users responses in Google Voice App

I am currently developing a voice agent to be used in a smart speaker where users will ask about some items that are being stored in a data stream. The ultimate goal is that users ask about items' names in the stream and google actions through voice will tell them the details about those items as presented in another column in the stream.
To do this, I linked a spreadsheet to Axios to stream the content of the spreadsheet as data to be read in a webhook in google actions. The link to the data stream is HERE.
Honestly, I am new to developing apps for google actions and new to javascript overall so I might be doing silly mistakes.
In the graphical interface for google actions, I am setting a type for the items I want the user to ask about.
Then, I set an intent to recognize the item as a data type and be able to send this to the webhook.
The cloud function in the webhook is as follows:
const { conversation } = require('#assistant/conversation');
const functions = require('firebase-functions');
require('firebase-functions/lib/logger/compat'); // console.log compact
const axios = require('axios');
const app = conversation({debug: true});
app.handle('getItem', async conv => {
const data = await getItem();
const itemParam = app.types.Item;
// conv.add("This test to see if we are accessing the webhook for ${itemParam}");
data.map(item => {
if (item.Name === itemParam)
agent.add('These are the datails for ${itemParam}. It is located in zone
${item.Zone}, at level ${item.Level}');
});
});
async function getItem() {
const res = await axios.get('https://sheetdb.io/api/v1/n3ol4hwmfsmqd');
console.log(res.data);
return res.data; // To use in your Action's response
}
exports.ActionsOnGoogleFulfillment = functions.https.onRequest(app);
What the webhook is doing is getting the stream with the getItem function and then mapping the data to find the Name in the stream to match the item parameter (ItemParam) as identified by the user.
However, one of the main problems I have is that when trying to access the item from the user, I am using app.types.Item, but this does not work as when testing I get an error saying: "error": "Cannot read property 'Item' of undefined". I think what is happening is that I am not using the correct way to call the Item in the conversation app.
Also, I am not sure exactly how the linking to the database will work. In other works, I am not sure if
data.map(item => {
if (item.Name === itemParam)
agent.add('These are the datails for ${itemParam}. It is located in zone
${item.Zone}, at level ${item.Level}');
will work.
I have tried multiple things to solve but I am really struggling so any help with this would be really appreciated. Also, I know that I rushed to explain things, so please let me know if you need me to explain better or clarify anything.
Thank you
There are three points I am seeing that won't work.
First, app.types.Item is not the way to get this parameter. You should instead use conv.intent.params['Item'].resolved to get the user's spoken name.
Second, you are trying to use agent.add to include text, but there is no agent in your environment. You should instead be using conv.add.
Third, the text you are sending is not properly escaped between backticks ``. It is the backtick that allows you to use template literals.
Altogether your code can be rewritten as:
const { conversation } = require('#assistant/conversation');
const functions = require('firebase-functions');
require('firebase-functions/lib/logger/compat'); // console.log compact
const axios = require('axios');
const app = conversation({debug: true});
app.handle('getItem', async conv => {
const data = await getItem();
const itemParam = conv.intent.params['Item'].resolved;
data.map(item => {
if (item.Name === itemParam)
conv.add(`These are the datails for ${itemParam}. It is located in zone
${item.Zone}, at level ${item.Level}`);
});
});
async function getItem() {
const res = await axios.get('https://sheetdb.io/api/v1/n3ol4hwmfsmqd');
console.log(res.data);
return res.data; // To use in your Action's response
}
exports.ActionsOnGoogleFulfillment = functions.https.onRequest(app);

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.

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() })
});
})

Get key of added record in IndexedDB

I have this code in IndexedDB:
var request = objectStore.add({ entryType: entryType, entryDate: t});
Now I want to know the key of this record that was just added in. How do I do that?
I found this article, and this
code:
var data = {"bookName" : "Name", "price" : 100, "rating":"good"};
var request = objectStore.add(data);
request.onsuccess = function(event){
document.write("Saved with id ", event.result)
var key = event.result;
};
This does not work for me - key shows up as undefined. I think I am missing something basic here!
Go through this code
var data = {"bookName" : "Name", "price" : 100, "rating":"good"};
var request = objectStore.add(data);
request.onsuccess = function(event){
document.write("Saved with id ", event.result)
var key = event.target.result;
};
Hope this code will work to retrieve key of last inserted Record
The spec is written for user agent, not for developer. So it is confusing. Key generator is provided by the user agent.
Any event object that is received by onsuccess handler always have event.target.result. It is the key you are looking for. The key is auto generated if you don't provide it, assuming you set autoIncrement to true.
It is documented in Step 8: as follow:
The result of this algorithm is key.
The trick here is knowing how to search using phrases iteratively, until you land on what you need. I've never heard of IndexedDB before, but seem to have found what you want.
I put "IndexedDB" into a search engine and found this. That yielded the phrase "key generator", so I searched for that as well which led me to this and this.
The StackOverflow link discusses using UUIDs, which of course can be generated in JavaScript, and the last link appears to have examples to do what you want out of the box.
If you're using the idb Promise wrapper for IndexedDB then the new key is just the return value from the add() call:
import { openDB } from 'idb';
const db = await openDB(...);
const tx = db.transaction('mystore', 'readwrite');
const newId = await tx.store.add({ hello: 'world' });
await tx.done;
console.log(`Autogenerated unique key for new object is ${newId}`);
Remember of course, this will only work if you include autoIncrement: true in the options passed to createObjectStore().

Categories