Why save() in mongoose is not a function? - node.js

I'm developing an app using Node.js, Mongoose, MongoDb, express.
I have 2 schemas one for student and one for snippets. I'm using the population model population model. I can create a user, and create a snippet and link it to the user. But I can't link and save the snippets in the user collection.
How to link and save the user so that it can have a reference to his snippets?
user and snippet schema
var userSchema = Schema({
name: { type: String, required: true, unique: true },
password: { type: String, required: true },
snippet: [{ type: Schema.Types.ObjectId, ref: 'Snippet' }]
})
var snippetSchema = Schema({
user: {type: Schema.Types.ObjectId, ref: 'User'},
title: String,
body: String,
createdAt: {
type: Date,
require: true,
default: Date.now
}
})
This is how I save the snippets I add it inside a user .save() function so that it saves the snippet ref but it gives me user.save() is not a function error.
var name = request.session.name.name
User.find({ name: name }).then(function (user) {
if (user) {
console.log('====================')
console.log(user)
user.save().then(function () { // problem is here?
var newSnippet = new Snippet({
user: user._id,
title: title,
body: snippet
})
newSnippet.save().then(function () {
// Successful
console.log('success')
response.redirect('/')
})
})
}
}).catch(function (error) {
console.log(error.message)
response.redirect('/')
})
But, I actually get the object printed after searching for it!
[ { _id: 5a2e60cf290a976333b19114,
name: 's',
password: '$2a$10$vD3EaQly4Sj5W3d42GcWeODuFhmHCSjfAJ1YTRMiYAcDBuMnPLfp6',
__v: 0,
snippets: [] } ]

You need to use User.findOne to get a valid user object, here you get an array. Also, don't forget to always return something in you promises (or throw an error).
Here is a quick rewrite of your function. With a few improvements such as arrow functions, const and a flat promise chain (never using any .then inside another .then) and avoiding code repetition
const name = request.session.name.name
User.findOne({ name })
.then(user => {
if (user) return user.save()
// What to do if not found? Throw an error?
throw new Error('User not found')
})
.then(() => {
const newSnippet = new Snippet({
user: user._id,
title: title,
body: snippet,
})
return newSnippet.save()
})
.catch((error) => console.log(error.message))
.then(() => response.redirect('/'))

Related

User cannot place order twice

I'm facing a weird issue while placing order in my ecommerce. when i place order the 1st time it registers and saves the order in my database but when the same user tries to place order the 2nd time i get a weird error.
my api call -
useEffect(()=>{
const makeRequest = async ()=>{
try{
const res = await userRequest.post("/checkout/pay", {
tokenId:stripeToken.id,
amount: cart.total,
})
try {
userRequest.post('/orders', {
userId: userr._id,
products: cart.products.map((item) => ({
productName: item.title,
productId: item._id,
quantity: item._quantity,
})),
amount: cart.total,
address: res.data.billing_details.address,
})
history('/success');
} catch(err) {
console.log(err)
}
}catch(err){
console.log(err)
}
};
stripeToken && makeRequest();
}, [stripeToken, cart.total, history])
order model -
const mongoose = require('mongoose');
const orderSchema = new mongoose.Schema(
{
userId: {type: String, required: true},
products: [
{
productName: {type: String},
productId: {type: String},
quantity: {type: Number, default: 1},
},
],
amount: {type:Number, required:true},
address: { type: Object, required:true },
status: {type: String, default: 'pending'},
}, {timestamps: true}
);
module.exports = mongoose.model('Order', orderSchema);
order route -
router.post("/", verifyToken, async (req, res) => {
const newOrder = new Order(req.body);
try {
const savedOrder = await newOrder.save();
res.status(200).json(savedOrder);
} catch (err) {
res.status(500).json(err);
}
});
error message when placing order 2nd time__
I think it's happening because backend creates the same document again when the same user places the same order a second time.
So what you can do is create a function in the backend which checks if the user has already ordered the same product then you can just increase the quantity of the product in the document for the same user.
it looks like the problem is that you try to add the same _id twice, in the second time that you want to insert to cart you need to update the order and not make a new one(newOrder.save();)
so before you save new order first check if there is a new order if not make newOrder.save() else you need to )update the cart
if you using mongoose(findOneAndUpdate)

MongoDB/Mongoose some of the model properties undefined in app.js

I am working on an Authentication/Session using Express, NodeJS, and MongoDB.
The Mongoose Schema looks like this:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const userSchema = new Schema({
username: { type: String, required: true, },
email: { type: String, required: true, unique: true, },
password: { type: String, required: true, },
SignUpDate: { type: { type: Date, default: Date.now } },
LastLogin: { type: { type: Date, default: Date.now } },
loggedin: { type: Boolean, required: false, },
attempts: { type: Number },
});
module.exports = mongoose.model("User", userSchema);
The Signup form only takes username, email, password, but I would like to save sign update, last login, failed login attempts, etc.
In the controller.js file, I have the routes, this is the problematic one.
exports.register_post = async (req, res) => {
const { username, email, password } = req.body;
let user = await User.findOne({ email });
if (user) {
req.session.error = "User already exists";
return res.redirect("/register");
}
const hasdPsw = await bcrypt.hash(password, 12);
user = new User({
username,
email,
password: hasdPsw,
SignUpDate,
loggedin: true
});
await user.save();
console.log(user)
res.redirect("/login");
};
And in App.JS I have this
app.post("/register", appController.register_post);
If I only use username, email, and password in the Schema, it all works, saves to the database.
But as above, I get
"UnhandledPromiseRejectionWarning: ReferenceError: SignUpDate is not
defined"
if I submit the signup button on the /register route. Another question, if I want to get a timestamp with Mongoose, do I have to call Date.now() and where?
Or do I have to define and add/push the properties that are not user provided via Signup Post ( SignUpDate,LastLogin:,loggedin:,attempts ) to the Schema after the users sign up? I am new to using Mongoose, going through the docs and cant seem to find how to ad a timestamp.
A little update, if I comment out the SignUpDate,LastLogin variables in the post function, I get "Object, Object" in MongoDBcompass and the object is collapsible, it saved the values in the database but crashed the app. The change that was necessary was simply
SignUpDate: { type: { type: Date, default: Date.now } },
LastLogin: { type: { type: Date, default: Date.now } },
to
SignUpDate: {
type: Date, default: Date.now(),
},
LastLogin: {
type: Date, default: Date.now(),
}
This is how it looks in the database, and it gets saved and the app doesn't crash. but as soon I uncomment "SignUpDate" in the route function, I get the same undefined error again.
I can live with this, but would rather not. Besides, how do I convert "type: Date, default: Date.now()" to a nice out put like ""Sun May 10 2015 19:50:08 GMT-0600 (MDT)"? If I change it in the Schema, it don't work, if I change it in the route function, it will not let me chain the functions and I don't know where to declare the var for the nice formatted output.
Remove "SignUpDate":
const user = new User({
username,
email,
password: hasdPsw,
loggedin: true
});
If you specified a default value, you don't need to specify it when you create new object.
If you want to accumulate the number of unsuccessful attempts, you need to get the users from the base, increase the counter by one and update it in the base. Smth like this:
let userAttempts = await User.findOne({ username });
await User.update({ username }, { $set: { attempts: userAttempts.attempts + 1 } });

Followers / Following structure in MongoDB

My website has a 'follow' feature, where users can follow each other.
Ideally, a user1 would be able to follow a user2, and have their _id present inside user2.followers, aswell as user2 should be present in user1.following.
My first thought was to simply do something like
/*
UserSchema = new Schema({
username: String,
followers: [
{
type: Schema.Types.ObjectId,
ref: 'user'
}
],
following: [
{
type: Schema.Types.ObjectId,
ref: 'user'
}
]
})
*/
// POST /api/users/follow
let user = await User.findById(userId)
let follow = await User.findById(followId)
if (!user || !follow) {
return errorHandle()
}
user.following.push(follow._id);
follow.followers.push(user._id);
user.save();
follow.save();
/*
User.findById(userId)
.populate('following', 'username')
.then(user => console.log(user.following))
*/
But these would be difficult to scale and maintain (as well as other problems).
So I want to hear from you, the stack community, what a proper way to deal with this system be, as I am new to MongoDB and Databases as a whole.
Any help is appreciated
hey I was getting the same problem I figured it out how it can be done. I have two way of writing the code choose as per your choice.
UserSchema
var UserSchema = new Schema({
name:{
type:String,
required:true
},
email:{
type:String,
required:true
},
password:{
type:String,
required:true
},
following: [
{
user:{
type: Schema.ObjectId,
ref: 'User'
},
}
],
followers: [
{
user:{
type: Schema.ObjectId,
ref: 'User'
},
}
],
date:{
type:Date,
default: Date.now
},
});
First Method
User.js
You can use .then and .catch method
router.post("/user/:user_id/follow-user", (req,res) => {
// finding user by id (to whom user is going to follow)
User.findById(req.params.user_id)
.then(user => {
//check if follow reqest by user2 is already exist in user1 followers
if(user.followers.filter(follower => follower.user.toString()=== req.user._id).length > 0 ){
return res.status(400).json({ alreadyLike : "User already like the post"})
}
// the requested user will push and save to followers of other user to whom request has made
user.followers.push(req.user._id);
var followedUser = user._id;
user.save()
// we will find current user by email you can find it by _id also
User.findOne({ email: req.user.email })
.then(user => {
// now push the user to following of its own and save the user
user.following.push(followedUser);
user.save().then(user => res.json(user))
})
.catch(err => console.log("error cant follow again you jave already followed the user"))
})
})
second method
Normal method by call backs
router.post("/user/:user_id/follow-user", (req,res) => {
// finding user by id (to whom user is going to follow)
User.findById(req.params.user_id, function(err, user) {
// the requested user will push and save to followers of other user to whom request has made
user.followers.push(req.user._id);
var followedUser = user._id;
user.save(function(err){
if(err){
console.log(err)
}
else
{
// Secondly, find the user account for the logged in user
User.findOne({ email: req.user.email }, function(err, user) {
user.following.push(followedUser);
user.save(function(err){
if(err){
console.log(err)
}
else{
//send success response
res.json(user)
}
});
});
}
});
});
});

Mongoose: Using the same schema in two separate arrays

I have two schemas, defined as following:
var userSchema = new Schema({
email: String,
name: String,
role: String,
password: String
})
var raceSchema = new Schema({
date: Date,
name: String,
location: String,
time: String,
register: String,
participants: [{ type: Schema.Types.ObjectId, ref: 'User'}],
registered_participants: [{ type: Schema.Types.ObjectId, ref: 'User'}],
})
As you can see, I reference the first schema twice in the second schema. If I add a reference to a user in one of the lists, everything is fine. But when I add a reference to the same user to the other list I get the following error: Cast to [undefined] failed for value
What causes this error? Is it related to the fact that the same schema is used twice in the second schema?
Edit:
I get the error when I call the following Express endpoint:
app.post('/race/:id/registered', passport.authenticate('jwt', { session: false}), (req, res) =>
Race.findOne({ _id: req.params.id }, function (err, race) {
if (err) return res.json({'Error': err})
if (!race) return res.json({'Error': 'Race not found'})
race.registered_participants.push(req.user)
race.save(function (err, updatedRace) {
if (err) return res.json({'Error': err})
res.send(updatedRace)
})
})
)
Edit 2: The model definitions:
var User = mongoose.model('User', userSchema);
var Race = mongoose.model('Race', raceSchema);
Try using findByIdAndUpdate in your POST method instead:
app.post('/race/:id/registered', passport.authenticate('jwt', { session: false}), (req, res) =>
Race.findByIdAndUpdate(req.params.id,
{ $push: { registered_participants: req.user } },
function (err, race) {
if (err) return res.json({'Error': err})
res.send(race)
})
)

What is a better way to prevent document from deleting if a reference is present in another document in Mongodb via Mongoose?

I am creating a webapp using the following stack:
Node
Express
MongoDB
Mongoose
I have structured the app into a MVC structure.
There are Customer, OrderReceived and OrderSent schemas. OrderReceived and OrderSent schema references Customer schema. Abridge schema structures are following:
Customer
const mongoose = require('mongoose');
const customerSchema = mongoose.Schema({
companyName: String,
firstName: { type: String, required: true},
lastName: { type: String, required: true}
});
module.exports = mongoose.model('Customer', customerSchema);
OrderReceived
const mongoose = require('mongoose');
const orderReceivedSchema = mongoose.Schema({
receivedDate: { type: Date, required: true},
customer: {type: mongoose.Schema.Types.ObjectId, ref: 'Customer', required: true}
});
module.exports = mongoose.model('OrderReceived', orderReceivedSchema);
OrderSent
const mongoose = require('mongoose');
const orderSentSchema = mongoose.Schema({
sentDate: { type: Date, required: true},
customer: {type: mongoose.Schema.Types.ObjectId, ref: 'Customer', required: true}
});
module.exports = mongoose.model('OrderSent', orderSentSchema);
When a Customer document is asked for delete, I want to check if it the document is referenced by either OrderReceived or OrderSent documents. And if there is a presence I want to prevent the deletion of the Customer document.
The solution I came up with is to do the check in the controller of Customer, as following:
CustomerController#destroy this handles the delete request:
destroy(req, res){
OrderReceived.count({customer: req.params.id}, (error, orderLength)=>{
if (error) res.send(error);
if (orderLength<1){
OrderSent.count({'customer.customer': req.params.id}, (error, orderLength)=>{
if (error) res.send(error);
if (orderLength<1){
Customer.remove({_id: req.params.id}, error => {
if (error) res.send(error);
res.json({message: 'Customer successfully deleted.'});
});
} else {
res.status(409).json({message: 'There are orders sent using the Customer. Datum could not be deleted'});
}
});
} else {
res.status(409).json({message: 'There are orders received using the Customer. Datum could not be deleted.'});
}
});
}
Is there a better way to do this? I have other models that also depends upon the Customer document and this code is only going to get messier.
Please help.
When you are creating OrderReceived or OrderSent save reference of it in Customer too.
So on this way before you delete it, you can simply check if they are empty or not.
Your Customer schema would be like:
const customerSchema = mongoose.Schema({
companyName: String,
firstName: { type: String, required: true},
lastName: { type: String, required: true},
ordersSent: [{type: mongoose.Schema.Types.ObjectId, ref: 'OrderSent'}],
ordersReceived: [{type: mongoose.Schema.Types.ObjectId, ref: 'OrderReceived'}],
});
and your delete function should contain something like:
Customer.findById(req.params.id)
.then(customer => {
if(customer.ordersSent.length== 0&& customer.ordersReceived.length== 0)
return true
//if there was more than 0
return false
}).then(result => {
if(result)
return Customer.findByIdAndRemove(req.params.id)
res.status(409).json({message: 'There are orders received or sent using the Customer. Customer could not be deleted.'})
}).then(customerDataJustInCase =>{
res.status(200).json({message: 'Customer deleted.'})
}).catch(err => {
//your error handler
})
or you can use it via try-catch.
You can use Promise.all method to perform all DB queries at once, like below:
Promise.all([
OrderReceived.count({customer: req.params.id}),
OrderSent.count({'customer.customer': req.params.id})
])
.then(([orderReceivedCount, orderSendCount]) => {
if (orderReceivedCount < 1 && orderSendCount<1) {
...delete doc
}
}).catch(error => ...handleError)

Resources