Join Search Query on firebase, how? - javascript

I am trying to conduct simple search query as below.
SELECT post.title as "Post Title", user.email as "Author Email"
FROM User
JOIN Post ON User.id = Post.userID
WHERE Post.title LIKE '%retriever%';
I know I can't do join search on nested collection
so I ended up creating two root collections: users, posts.
Below is my pathetic try on such query
const db = firebase.firestore()
const ref = db.collection('Post')
const postTitle = await ref
.orderBy('title')
.startAt('Springfield')
.get();
const userEmail = await db.collection('User')
.where('id', '==', 'postTitle.userId')
.document('email');
How do I achieve the above result in this particular case?

Alright, first let me confirm I understand the SQL.
You're wanting to return two variables (title, email) from the User table
SELECT post.title as "Post Title", user.email as "Author Email"
FROM User
Then you want to join the Post table where the 'User.id' is equal to the 'Post.userID' so essentially finding the users' posts.
JOIN Post ON User.id = Post.userID
WHERE Post.title LIKE '%retriever%';
If my assessment is accurate, and without knowing your data model, I can give a rough guess on what that would look like.
//Reference to Firestore
const db = firebase.firestore()
//Reference to the Post "table" Collection
const ref = db.collection('Post')
//This is assuming I know the USER ID that I need - if I don't have it, we can retrieve it by their email 'admin.auth().getUserByEmail(email)' or somewhere else.
//This example should still work with whatever the 'id' key is representing in the Post model
const postsByUserID = await ref.where('id', '==', userID).get();
if (postsByUserID.empty) {
console.log('No matching documents.');
return;
}
//This will loop through every Post that we got that matched the user's ID
postsByUserID.forEach(doc => {
console.log(doc.id, '=>', doc.data());
});
Keep in mind this is assuming that 'id' from your Post model is equal to a user's UID.
If we assume we do not know the user's ID, and want to get every user, and then the posts that have the 'id' equal to that user's id:
//Reference to the Post "table" Collection
const ref = db.collection('Users')
const allUsers = await ref.get();
allUsers.forEach(doc => {
console.log(doc.id, '=>', doc.data());
const userID = doc.data().id;
const postsByUserID = await ref.where('id', '==', userID).get();
if (postsByUserID.empty) {
console.log('No matching documents.');
return;
}
//This will loop through every Post that we got that matched the user's ID
postsByUserID.forEach(doc => {
console.log(doc.id, '=>', doc.data());
});
});

Related

Get the var value out of IF statement

I'm trying to do a comparison between user session email and an email in an array in firestore. That is, I want to search for the login email within the database and if any email is found, bring some information to the screen, such as name and surname.
I even managed to get inside the array and make this comparison, but I can't make the "var UserName" leave the { } of the IF
Can someone help me?
my code is:
const [data, setData] = useState([]);
const getUsers = () => {
firestore()
.collection("users")
.get()
.then((querySnapshot) => {
let userFirestore = [];
querySnapshot.forEach((doc) => {
const usuario = {
id: doc.id,
nome: doc.data().nome,
sobrenome: doc.data().sobrenome,
email: doc.data().email,
profissao: doc.data().profissao,
}
userFirestore.push(usuario);
});
userFirestore.forEach(function (item, indice, array) {
if (item.email === user.email){ //user.email brings the email of the logged in user
var nomeUsuario = item.nome
console.log(nomeUsuario) //UserName brings the result I expected
}
});
})
.catch((error) => console.log(error));
}
You can use a query to get a document with current user's email that instead of fetching the whole collection as shown below:
firestore()
.collection("users")
.where("email", "==", user.email)
.get().then((querySnapshot) => {
if (querySnapshot.empty) {
console.log("User not found")
return;
}
const user = querySnapshot.docs[0].data();
console.log(user)
})
Even better, if you use user's UID as the Firestore document ID (highly recommended), then you can get that single document by ID as shown below:
firestore()
.collection("users")
.doc(user.uid) // < user.uid must be defined
.get()
.then((snapshot) => {
console.log(snapshot.data())
})
When you fetch the whole collection, you are charged N read where N is total number of documents in the collection and it'll be slower as you are downloading plenty of data.

How to list all documents in a sub-collection and then fetch their documents by their ID?

I'm trying to show data from a document inside a collection between "students" and their "courses"
I'm using html, javascript and this structure but as of now I can only retrieve the ID of the document but I'd like to also access the data inside this document
async function fetchCourses() {
const studentId = firebase.auth().currentUser.uid
const courseIds = await db.collection(`students/${studentId}/attending`).get();
const courseDocs = await Promise.all(
courseIds.docs.map(doc => db.doc(`courses/${doc.id},${studentId}`).get())
);
return courseDocs.filter(doc => doc.exists).map(doc => ({ id: doc.id, ...doc.data() }),
console.log(courseDocs));
}
My console.log displays all this:
Any help is gladly appreciate
Edit
This is the data structure inside my Firebase:
The students are stored in a collection indexed by their uid and each student document contains their name and more data, but most importantly a sub-collection named attending which contains empty documents indexed by courseId, refering to a document in the courses collection
The courses are stored in another collection indexed by courseId and like in students each course document contains a title and more data, and a sub-collection named attendees which contains empty documents indexed by uid, refering to a student.
import { doc, getDoc } from "firebase/firestore";
const docRef = doc(db, "cities", "SF");
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
console.log("Document data:", docSnap.data());
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
above is an example from firestore docs. firestore has really specific syntax.
you can only access data of request with doc.data() syntax. All other attempts will bring you nothing or some irrelevant object(at least in my experience)

How can I get documents from Firestore for current user?

I'm trying do display data from Firestore database in my component.
This is my function:
const getData = async () => {
const data = [];
const querySnapshot = await getDocs(
collection(databaseRef, "mK7DFNJgRAPmtvgrZh7X6AOj8cR2")
);
console.log(querySnapshot);
querySnapshot.forEach((doc) => {
console.log(doc.id, " => ", doc.data().Title);
data.push({
About: doc.data().About,
Title: doc.data().Title,
Who: doc.data().Who,
});
});
setData(data);
};
Collection ID = Current logged in User.
I want to display every document.
Everything works fine but insteed of passing hard-coded string here:
collection(databaseRef, "mK7DFNJgRAPmtvgrZh7X6AOj8cR2")
I would like to pass variable where I store my UID.
Is there any way to do that?
Assuming your user is logged in, you should be able to access their UID via firebase.auth().currentUser.uid. This question's answers may be useful for more information on getting the current user's ID.
With that, you should be able to do:
const querySnapshot = await getDocs(
collection(databaseRef, firebase.auth().currentUser.uid)
);
to get the current user's documents.
https://firebase.google.com/docs/reference/js/v8/firebase.auth.Auth#currentuser

access subcollection in firebase firestore v9

I'm trying to access the firebase firestore documents inside subcollection (messages):
user > user.uid > messages > docRef.id > date: Date.now()
text: userText
userEmail: user.email
userName: display.name
I used the following:
const snapRef2 = collection(db, "users/" + user.uid + "/messages")
onSnapshot(snapRef2, (snapshot) => {
snapshot.forEach((doc) => {
console.log(doc.data());
})
})
But this method works only when user.uid is a string like: const snapRef2 = collection(db, "users/randomstring/messages")
How do I access the documents inside messages?
const snapRef2 = collection(db, `users/${user.uid}/messages`)
Make sure to use back ticks instead of quotation marks
The code in your answer works if you want to retrieve the messages for a specific user, identified by user.uid.
If you want to get the messages for all users, you can use a collection group query. Such a query reads from all collections with a specific name, like messages:
const snapRef2 = collectionGroup(db, "messages")
...
The rest of your code can stay the same.

Firebase Cloud Function. Create documents in sub-collection of specific collections

here's the basic premise of what im trying to accomplish here. if a user ask a question about a product i want to send a notification to other users who currently own that product. basically saying "hey, so and so has a question about this product. maybe you can help since you own it already"
each userProfile collection has a subcollection called 'notify' where notifications are stored for various things. what i need to do is sort through the userProducts and find every user who owns the product and then create a notification post in only the notify sub-collections for those specific users who own that product.
here is the basic code. the first bit works in that it does return an array of userIDs who own that product. where im struggling now is getting it to create a new doc in the Notify sub-collection for just those specific users. is this possible to do?
exports.Questions = functions.firestore
.document("/userPost/{id}")
.onCreate(async (snap, context) => {
const data = snap.data();
if (data.question == true) {
const userProducts = await db
.collection("userProducts")
.where("product", "==", data.tag)
.get();
const userData = userProducts.docs.map((doc) => doc.data().userId);
await db
.collection("userProfile")
.where("userId", "in", userData)
.get()
.then((querySnapshot) => {
return querySnapshot.docs.ref.collection("notify").add({
message: "a user has asked about a product you own",
});
});
});
Your current solution is on the right track, but there are improvements that can be made.
Use a guard pattern for the data.question == true check.
You don't need to get userProfile/<uid> as you aren't using its contents.
When changing multiple documents at once, you should consider batching them together for simpler error handling.
ref.add(data) is shorthand for ref.doc().set(data) which you can use in the batched write to create new documents.
exports.Questions = functions.firestore
.document("/userPost/{id}")
.onCreate(async (snap, context) => {
const data = snap.data();
if (!data.question) {
console.log("New post not a question. Ignored.")
return;
}
const userProducts = await db
.collection("userProducts")
.where("product", "==", data.tag)
.get();
const userIds = userProducts.docs.map(doc => doc.get("userId")); // more efficient than doc.data().userId
// WARNING: Limited to 500 writes at once.
// If handling more than 500 entries, split into groups.
const batch = db.batch();
const notificationContent = {
message: "a user has asked about a product you own",
};
userIds.forEach(uid => {
// creates a ref to a new document under "userProfile/<uid>/notify"
const notifyDocRef = db.collection(`userProfile/${uid}/notify`).doc();
batch.set(notifyDocRef, notificationContent);
});
await batch.commit(); // write changes to Firestore
});
Note: There is no special handling here for when no one has bought a product before. Consider pinging the product's owner too.

Categories