Firestore Nested Documents with random ID inside a collection document - javascript

I am creating an application using Angular and firebase. Where I want to create a collection called PETS inside that collection the document ID will be currently logged in users UID. And inside that Document I want to create documents with random IDs based on my Form values.
-|pets
-|userId
-|RandomId which contains all the form values
-|RandomId which contains all the form values
Right Now I am using this method. Please tell me what are the changes that I need to make here. This method creating a collection with same ID as current users. But it's not creating separate documents inside the parent Document with users ID. Also I want to loop over those documents with random Ids and will show a list of PETS in fronted.
addPet(ownerName, PetName, type, breed, size){
let user = this.afAuth.auth.currentUser;
if (user) {
return new Promise((resolve, reject) => {
this.afs.collection("pets").doc(user.uid).set({
OwnerName: ownerName,
Petname: PetName,
PetType: type,
PetBreed: breed,
PetSize: size
})
.then((response) => {
resolve(response)
})
.catch((error) => {
reject(error)
});
})
} else {
alert('user not logged in')
}
}

Currently you wish to store a user's pets at /pets/userId. To add each pet at this location, you would need to use a subcollection as documents can't hold other documents. The following code adds a pet that will be stored at /pets/userId/pets/somePetId.
addPet(ownerName, petName, type, breed, size){
let user = this.afAuth.auth.currentUser;
if (!user) {
return Promise.reject({code: 'unauthenticated', message: 'user not logged in'})
}
return this.afs.collection("pets").doc(user.uid).collection("pets").add({
OwnerName: ownerName,
OwnerID: user.uid,
PetName: petName,
PetType: type,
PetBreed: breed,
PetSize: size
});
}
However you have two other ways you could model this data.
Global Pets Collection
Instead of saving pets under a userId, instead each pet has their own ID and you link that pet to their owner's ID.
addPet(ownerName, petName, type, breed, size){
let user = this.afAuth.auth.currentUser;
if (!user) {
return Promise.reject({code: 'unauthenticated', message: 'user not logged in'})
}
return this.afs.collection("pets").add({
OwnerName: ownerName,
OwnerID: user.uid,
PetName: petName,
PetType: type,
PetBreed: breed,
PetSize: size
});
}
To get the an array of pets for a given user, you would use the following:
function getPetsForCurrentUser() {
let user = this.afAuth.auth.currentUser;
if (!user) {
return Promise.reject({code: 'unauthenticated', message: 'user not logged in'})
}
return this.afs.collection("pets").where("OwnerID", '==', user.uid).get()
.then(childrenAsArrayOfObjects)
}
Pets Subcollection of User
Because you are using Cloud Firestore and not the Realtime Database, you can save each pet as a subcollection of the owner's user data. Unlike the Realtime Database, when you fetch the data of /users/userId, you only get the data of that specific document and not all the data nested under that path. The code below assigns each pet their own ID and saves it to a subcollection of it's owner:
addPet(ownerName, petName, type, breed, size){
let user = this.afAuth.auth.currentUser;
if (!user) {
return Promise.reject({code: 'unauthenticated', message: 'user not logged in'})
}
return this.afs.collection("users").doc(user.uid).collection("pets").add({
OwnerName: ownerName,
OwnerID: user.uid,
PetName: petName,
PetType: type,
PetBreed: breed,
PetSize: size
});
}
To get the an array of pets for a given user, you would use the following:
function getPetsForCurrentUser() {
let user = this.afAuth.auth.currentUser;
if (!user) {
return Promise.reject({code: 'unauthenticated', message: 'user not logged in'})
}
return this.afs.collection("users").doc(user.uid).collection("pets").get()
.then(childrenAsArrayOfObjects)
}
With this data structure, if you wanted to query all pets, regardless of their owner, you would make use of a collection group query.
let ragdollCatsQuery = db.collectionGroup('pets').where('PetType', '==', 'Cat').where('PetBreed', '==', 'Ragdoll');
ragdollCatsQuery.get()
.then(querySnapshot => {
console.log("Found " + querySnapshot.size + " cats with the breed: Ragdoll");
})
Other Notes
Please use a consistent casing style to prevent errors and typos. e.g. Change Petname to PetName to match the database key OwnerName's TitleCase. Similarly, the convention is to use camelCase for variable names, so the variable PetName should be petName.
The above functions both use the following transformation function:
function childrenAsArrayOfObjects(querySnapshot, idFieldName) {
let idFieldName = (idFieldName && idFieldName + "") || "id";
let children = [];
querySnapshot.forEach(childDoc => {
children.push({...childDoc.data(), [idFieldName]: childDoc.id})
}
return children;
}
If you wanted to use a different field name for the document ID when using this transformation, you would use:
.then(querySnapshot => childrenAsArrayOfObjects(querySnapshot, "PetID"))
You can find other transforms here.

Related

How to create a nested collection when creating a user in Firebase / Firestore where users can save bookmarked items

I want to be able to have a nested collection in firebase/firestore where I can save an authenticated users favorites. I was trying to create the collection when the user is created so I can just read/write to it later but I can't figure out how to create the collection. I have something like this:
//This function creates a new user. If the user already exists, no new document will be created
export const createUserDocumentFromAuth = async (
userAuth,
additionalInfo = {}
) => {
if (!userAuth) return;
const userDocRef = doc(db, 'users', userAuth.uid); //database instance, collection, identifier
const bookmarkRef = doc(db, 'users', userAuth.id, 'bookmarks'); //This triggers error
const userSnapshot = await getDoc(userDocRef);
if (!userSnapshot.exists()) {
//If user snapshot doesn't exist - create userDocRef
const { displayName, email } = userAuth;
const createdAt = new Date();
try {
await setDoc(userDocRef, {
displayName,
email,
createdAt,
...additionalInfo,
});
setDoc(bookmarkRef, { //Try to create a bookmarks collection here
favorites: []
})
} catch (error) {
console.log('Error creating user', error.message);
}
}
//if user data exists
return userDocRef;
};
I can create the user just fine but not another collection at the same time. I also tried just creating the collection when a signed-in user clicks on the bookmark button like this but I get a type error in both cases Uncaught (in promise) TypeError: n is undefined every time.
export const addBookmarkForUser = async (userAuth, showId) => {
const bookmarkRef = doc(db, 'users', userAuth.id, 'bookmarks');
try {
await setDoc(bookmarkRef, {
favorites: showId
});
}catch(error){
console.log('error creating bookmark', error.message)
}
};
I'm pretty new to Firebase / Firestore and all I want is to be able to save an item id in an array for an individual user when they click a button. If saving in an array is not ideal or there is any better way to do this, I am open to any suggestions at this point.
I was trying to create the collection when the user is created so I
can just read/write to it later but I can't figure out how to create
the collection.
A (sub)collection is only created when you create the first document in it. There is no way to materialize an empty collection without a document.
And it is normal that you get an error when using the doc() method as follows
const bookmarkRef = doc(db, 'users', userAuth.id, 'bookmarks');
because this method is used to create a DocumentReference and therefore you need to pass a path with an even number of path segments. In you case you pass 3 segments.
You could very well define the CollectionReference for the bookmarks subcollection as follows, using the collection() method and passing the 3 segments
const bookmarkRef = collection(db, 'users', userAuth.id, 'bookmarks');
but, until you add a document in it, it will not exist in the database.
Conclusion: You will automatically create the user's bookmarks subcollection the first time you create a bookmark for the user.
For example:
const bookmarksCollectionRef = collection(db, 'users', userAuth.id, 'bookmarks');
await bookmarksCollectionRef.add({ ... })

How to get document after user has logged in using google auth in firebase?

I want to fetch user information stored in firestore after the user has logged in using google sign in. I am checking whether the user exists or not. If the user does not exist then I am creating a new document in firestore.
const q = query(collection(db, "users"),where("uid", "==", user.uid));
getDocs(q).then((doc) => {
if (doc.docs.length === 0) {
addDoc(collection(db, "users"), {
uid: user.uid,
name: user.displayName,
authProvider: "google",
email: user.email,
lists: [],
}).then((response) => {
console.log(response);
});
}else{
console.log(doc.docs);
}
user is the user Object I am getting after google sign in.
The doc.docs is not giving the document data which I need, namely lists. How do fetch that?

How to create a new collection on document creation if one doesn't exist (Firebase 9 & NextJS)

Basically I am wanting to create a new collection on a document creation if that said collection does not exist.
Here is the structure I have
-Company (Collection)
--Company Name (Doc)
---CompanyInfo (Doc)
---Users (Collection)
----Users (Docs)
This is working if I create the collection manually in firebase, but I want these to somehow create automatically when a company signs up
Here is my full code: https://pastebin.com/JeCAM3Fx
const handleCreate = (e) => {
e.preventDefault();
createUserWithEmailAndPassword(auth, email, password)
.then((userInfo) => {
setDoc(doc(db, `company/${company}/users`, userInfo.uid), {
firstName,
lastName,
email,
phone,
address,
country,
state,
company,
position,
});
console.log("You are registerd!");
})
.catch((error) => {
console.log(error.message);
});
};
I get the error cannot find indexOf() when trying to signup
I am using NextJS and Firebase 9

How can I remove bookmarked posts of user (1) from user (2) tab after user (1) deletes his account?

After creating a node.js, express, mongoDb REST api for a social media web app with almost all basic social media actions (login, signup, add a post, delete a post, delete account, follow users ...),
I'm currently facing a problem, where after implementing bookmarking a post feature, I'm unable to come up with a solution to remove a bookmarked post from another user's bookmarked posts page, after the first user deletes his account. I'll provide my code below:
(P. S. Bookmarks is an array inside User model. I'd also like to mention the steps that I initially intended for the task:
Get current user by ID
Then get all posts created by this user, which returns an array, so I mapped it to get each Posts id
After that I fetched all users accross the app, and initially intended to compare the posts that live inside bookmarks array inside each user to the posts that the current user have created. Then I'd pull these same posts out of the bookmarks array from each of these users.
--> I think the logic that I've analyzed is maintainable, but it's just not working with me. This is the Code below:
export const deleteUser = async (req, res) => {
try {
let user = await User.findById(req.params.userId)
const userPosts = await Post.find({ creatorId: user._id })
const allUsers = await User.find()
const myPostsIds = userPosts.map((post) => post._id.toString())
//This is the section I've implemented for my task, but obviously
something isn't right
await Promise.all(
myPostsIds.forEach((id) =>
allUsers.map((user) => {
user.bookmarks.includes(id) &&
user.updateOne({ $pull: { bookmarks: id } })
})
)
)
await Post.deleteMany({ creatorId: user._id })
await user.remove()
res.status(200).json({
message: "Account has been deleted successfully!",
})
} catch (err) {
errorHandler(res, err)
}
}
As mentioned in my comments, the value you pass to Promise.all is no array of Promise/array of async functions.
The 2nd error is inside the (currently) forEach function at the .map() you are not returning anything in the map-call.
So this should do it:
// first convert all ids to a promise
await Promise.all(myPostsIds.map(id => new Promise(resolve => {
// during this, await every test and update
return Promise.all(allUsers.map(user => new Promise(resolve => {
// if it includes the id, cast the update and then resolve
if (user.bookmarks.includes(id)) {
// if found, resolve the promise for this user after the change
user.updateOne({ $pull: { bookmarks: id } }).then(resolve)
} else {
// resolve directly if not found.
resolve()
}
// when all users are done for this id, resolve the Promise for the given id
}))).then(resolve)
})))
An easier to read and shorter method would be:
for (const id of myPostIds) {
for (const user of allUsers) {
if (user.bookmarks && user.bookmarks.includes(id)) {
await user.updateOne({ $pull: { bookmarks: id } });
}
}
}

find if product exists and if so, check if user exists in product array of objects

When a user clicks a button, their userId and the product ID gets send to my query below. I want to check if that product exists, and if it does then check if a particular userId exists in an array of objects within the same document. I have tried the below but when I test it with other products where the userId is not present in the array, it is telling me that the user exists. So, it seems to be checking all products instead of just the one I am passing the product ID of.
Product.findById(productId).then(product => {
if (!product) {
console.log("no product found");
}
return Product.find({ "requests.userId": userId })
.then(result => {
if (result === undefined || result.length == 0) {
res.status(200).json({ message: "You can add it!" });
} else {
res.status(200).json({ message: "You cannot add this again!" });
}
})
.catch(err => {
console.log(err);
});
});
});
What you need to do is find a product that satisfies 2 conditions simultaneously:
Has a specific id
Has a specific string in the requests array filed
What your query is doing is testing the 2 conditions individually. First, you find the product and then you test if there's any product out there that satisfies condition 2.
To apply both conditions to the same product, use a single query:
Product.find({ _id: productId, 'requests.userId': userId })
.then(product => {
if (product) {
const [result] = product; // take the first matched item
// ... do stuff with result
}
})
Alternatively you could do all of this in memory:
Product.findById(productId).then(product => {
if (!product) {
console.log("no product found");
// ... potentially send an error here
return;
}
// find a match in memory
const [result] = product.requests.filter(uid => uid === userId);
if (result) {
// ...
}
});
First, Ln 4, you are trying to check user ID in complete Product object from Mongo schema.
If your userId is stored within every individual product,
Then change Ln 4, Product to product.

Categories

Resources