Mongoose: save() is not a function when using find() and atributing value to variable - javascript

This is the basic structure of the Schema I am working with using mongoose:
const User = {
uid: {
type: String
},
routes: {
type: Array
}
}
In my application there is a POST to /route, in which uid and a new route are provided as "body parameters". In order to add to the routes array, I wrote a code similar to this (the only diference is that I check if the route already exists):
var user = await User.find({uid: uid}) // user is found, as expected
user[0].routes.push(route //parameter)
user.save()
When a POST request is made, though, it throws an error:
TypeError: user.save is not a function
What am I doing wrong?

user in your code is an array of documents
so you'll have mongo documents inside that array
you can't do array.save, you've to do document.save
await user[0].save()
var user = await User.find({uid: uid}) // user is found, as expected
if (user && user.length) {
user[0].routes.push(route //parameter)
await user[0].save(); // save the 1st element of the object
}
if your query returns only 1 record better use https://mongoosejs.com/docs/api.html#model_Model.findOne
var user = await User.findOne({uid: uid}) // user is found, as expected
if (user) {
user.routes.push(route //parameter)
await user.save(); // save the 1st element of the object
}

if you need to find only one specific user you should use findOne function instead
User.findOne({uid: uid})
.then(
(user) => {
user[0].routes.push(route //parameter);
user.save();
},
(err) => {
console.error(err);
}
)

I think bulkSave() can be what you're looking for:
var user = await User.find({uid: uid}
enter code user[0].routes.push(route //parameter)
await User.bulkSave(user)

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

is there a way to automatically update everything in a mongoose document?

For example I want to update a mongoose document in a put request, I have to do this:
app.put('/update', async(req,res) => {
try{
const product = await Product.findById(req.body.id)
product.name = req.body.name
product.price = req.body.price
procut.discount = req.body.discount
// etc...
await product.save()
res.json(product)
}catch(e){
res.json({message: "Error updating the product"})
}
})
I'm asking if there is another faster and developer friendly way of updating products instead of typing each of the document properties and equal them to the req.body.[property]?
You can try the following for object merging
Object.assign(product, req.body)
note: i haven't tried with mongoose collection
You can use updateMany or findOneAndUpdate model methods, but it is more advisable to use .save()
https://mongoosejs.com/docs/api.html#model_Model.updateMany
https://mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate
If you want to .save() to look cleaner, you can do like this:
async updateEntity(payload) {
const keysToUpdate = Object.keys(payload)
if (keysToUpdate.length === 0) {
throw new Error('Update payload must not be empty!')
}
const entity = await entityModel.findOne({ _id: redirect })
keysToUpdate.forEach((key) => {
entity[key] = payload[key]
})
await entity.save()}

GET an object from mongodb using mongoose

The problem is probably simple, but my 2 AM brain can't understand what's going on anymore. I'm trying to create a profile page that shows basic public info. The way I'm trying to make it work is pulling out the users username from mongodb when registered the account by his specific _id. If needed, verification I use is JWT.
app.post('/api/user-profile', async (req,res) => {
const { token } = req.body
if(!token) {
return res.json({ status: 'error', error: 'not logged in' })
}
try {
const user = jwt.verify(token, JWT_SECRET)
const userid = user.id
const result = User.findOne({ userid })
console.log(result)
// return res.json({ status: 'ok', name: result })
} catch(error) {
// return res.json({ status: 'error', error: 'something went wrong' })
console.log(error)
}
})
I'm not sure what function should I use, findOne() or findById(). I tried to look at the documentation at mongoose, but the explanation is a bit too hard for me to understand.
P.S User = the user registration model. If needed I can paste in the code.
use findById instead of findOne if userid is _id and use await before the query, so do like this:
const result = await User.findById(userid)
if you want to use findOne :
const result = await User.findOne({"_id" : userid})
if you want a plain object javascript use .toObject after query like this:
const result = await User.findById(userid).toObject()
console.log(result)
I don't have a lot of experience with mongoose but worked with Mongo quite a lot. findOne is a direct correspondent of Mongo's findOne function which receives a query in the format {"key": "expectedValue"}. If you want to use it to get data by id the query is {"_id": user.id}.
Because fetching data by id is a common case, the lib added the method findByID which receives an ID, and then formats the query and makes an internal call to findOne.
For anyone interested, the answer is just like Mohammad Yaser Ahmadi said. Everything works fine, and by getting the username I did:
const user = jwt.verify(token, JWT_SECRET)
const userid = user.id
const result = await User.findById(userid)
const usersName = result.username
console.log(usersName)

Asynchronous verification within the .map function

I am developing the backend of an application using Node JS, Sequelize and Postgres database.
When the course is registered, the user must inform which organizations, companies and teachers will be linked to it.
The organization IDs are passed through an array to the backend, I am trying to do a check to make sure that the passed IDs exist.
What I've done so far is this:
const { organizations } = req.body;
const organizationsArray = organizations.map(async (organization) => {
const organizationExists = await Organization.findByPk(organization);
if (!organizationExists) {
return res
.status(400)
.json({ error: `Organization ${organization} does not exists!` });
}
return {
course_id: id,
organization_id: organization,
};
});
await CoursesOrganizations.bulkCreate(organizationsArray);
This link has the complete controller code, I believe it will facilitate understanding.
When !OrganizationExists is true, I am getting the return that the organization does not exist. The problem is when the organization exists, I am getting the following message error.
The Array.map() is returning an array of promises that you can resolve to an array using Promise.all(). Inside the map you should use throw new Error() to break out of the map - this error will be raised by Promise.all() and you can then catch it and return an error to the client (or swallow it, etc).
This is a corrected version of your pattern, resolving the Promise results.
const { organizations } = req.body;
try {
// use Promise.all to resolve the promises returned by the async callback function
const organizationsArray = await Promise.all(
// this will return an array of promises
organizations.map(async (organization) => {
const organizationExists = await Organization.findByPk(organization, {
attributes: ['id'], // we only need the ID
raw: true, // don't need Instances
});
if (!organizationExists) {
// don't send response inside the map, throw an Error to break out
throw new Error(`Organization ${organization} does not exists!`);
}
// it does exist so return/resolve the value for the promise
return {
course_id: id,
organization_id: organization,
};
})
);
// if we get here there were no errors, create the records
await CoursesOrganizations.bulkCreate(organizationsArray);
// return a success to the client
return res.json({ success: true });
} catch (err) {
// there was an error, return it to the client
return res.status(400).json({ error: err.message });
}
This is a refactored version that will be a bit faster by fetching all the Organizations in one query and then doing the checks/creating the Course inserts.
const { Op } = Sequelize;
const { organizations } = req.body;
try {
// get all Organization matches for the IDs
const organizationsArray = await Organization.findAll({
attributes: ['id'], // we only need the ID
where: {
id: {
[Op.in]: organizations, // WHERE id IN (organizations)
}
},
raw: true, // no need to create Instances
});
// create an array of the IDs we found
const foundIds = organizationsArray.map((org) => org.id);
// check to see if any of the IDs are missing from the results
if (foundIds.length !== organizations.length) {
// Use Array.reduce() to figure out which IDs are missing from the results
const missingIds = organizations.reduce((missingIds, orgId) => {
if (!foundIds.includes(orgId)){
missingIds.push(orgId);
}
return missingIds;
}, []); // initialized to empty array
throw new Error(`Unable to find Organization for: ${missingIds.join(', ')}`);
}
// now create an array of courses to create using the foundIds
const courses = foundIds.map((orgId) => {
return {
course_id: id,
organization_id: orgId,
};
});
// if we get here there were no errors, create the records
await CoursesOrganizations.bulkCreate(courses);
// return a success to the client
return res.json({ success: true });
} catch (err) {
// there was an error, return it to the client
return res.status(400).json({ error: err.message });
}
If you have an array of Ids and you want to check if they exist you should you use the (in) operator, this makes it so that you are hitting the DB only once and getting all the records in one hit (instead of getting them one by one in a loop), after you get these records you can check their lengths to determine if they all exist or not.
const { Op } = require("sequelize");
let foundOrgs = await Organization.findAll({
where: {
id: {
[Op.in]: organizationsArray,
}
}
});

Categories

Resources