How do I update nested array values in mongoose? - node.js

I am fairly new to nodejs/express and I'm practicing full stack development with devchallenges.io, i'm doing the shoppingify challenge. I'm trying to update the quantity of an item I am targeting inside of the items array. I understand my attempt below was terrible, I'm really struggling to understand the logic to be able to do so.
// #route PUT api/list/item/quantity/:id
// #desc Increase or decrease quantity
// #access Private
router.put('/item/quantity/:id', auth, async (req, res) => {
const { action } = req.body;
try {
let list = await List.findOne({ user: req.user.id });
const item = list.items.find(
(item) => item._id.toString() === req.params.id
);
list = list.updateOne(
{ 'items._id': req.params.id },
{ $set: { 'items.quantity': item.quantity + 1 } }
);
await list.save();
return res.json(list);
} catch (error) {
console.error(error.message);
res.status(500).send('Server Error');
}
});
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const ListSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
},
name: {
type: String,
default: 'Shopping List',
},
items: [
{
name: {
type: String,
default: '',
},
note: {
type: String,
default: '',
},
image: {
type: String,
default: '',
},
category: {
type: String,
default: '',
},
quantity: {
type: Number,
default: 1,
},
},
],
date: {
type: Date,
default: Date.now,
},
});
module.exports = List = mongoose.model('list', ListSchema);

Look this is my update-vendor route here I'm updating nested street and city name.
router.put("/update-vendors", async (req, res, next) => {
const vendor = await Vendor.updateOne(
{
"address.street": "Street2",
},
{
$set: {
"address.$.street": req.body.street,
"address.$.city": req.body.city,
},
}
);
res.status(200).json(vendor);
});
You can update particular things with the help of $set and other $push method

Related

How to Calculate and return a total cart items in Nodejs and Express

I've been going back and forth on this code for sometime now and I'm trying to have a totalQty value in the cart object that returns total number of items in the cart and I want to use that value in the views of course right next to the cart icon in the navigation. Here is my code for the user model and routes:
User model:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema({
role: {
type: String,
default: 'BASIC'
},
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
address: {
type: String
},
apartment: {
type: String
},
country: {
type: String
},
state: {
type: String
},
city: {
type: String
},
zip: {
type: String
},
phone: {
type: String
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
resetToken: String,
resetTokenExpiration: Date,
cart: {
items: [
{
productId: {
type: Schema.Types.ObjectId,
ref: 'Product',
required: true
},
quantity: { type: Number, required: true }
},
],
totalQty: {
type: Number,
default: 0
}
}
}, { timestamps: true });
userSchema.methods.addToCart = function (product) {
const cartProductIndex = this.cart.items.findIndex(cp => {
return cp.productId.toString() === product._id.toString();
});
let newQuantity = 1;
// let newTotalQty = 1;
const updatedCartItems = [...this.cart.items];
if (cartProductIndex >= 0) {
newQuantity = this.cart.items[cartProductIndex].quantity + 1;
updatedCartItems[cartProductIndex].quantity = newQuantity;
newTotalQty = this.cart.totalQty + 1;
updatedTotalQty = newTotalQty;
} else {
updatedCartItems.push({
productId: product._id,
quantity: newQuantity
});
}
const updatedCart = {
items: updatedCartItems,
totalQty: updatedTotalQty
};
this.cart = updatedCart;
return this.save();
};
userSchema.methods.removeFromCart = function (productId) {
const updatedCartItems = this.cart.items.filter(item => {
return item.productId.toString() !== productId.toString();
});
this.cart.items = updatedCartItems;
return this.save();
};
userSchema.methods.clearCart = function () {
this.cart = { items: [] };
return this.save();
};
module.exports = mongoose.model('User', userSchema);
User routes:
exports.getCart = (req, res, next) => {
// populate req user
req.user
.populate('cart.items.productId')
.execPopulate()
.then(user => {
const products = user.cart.items;
// render cart view
res.render('shop/cart', {
path: '/cart',
pageTitle: 'Cart - Hashing365.com',
products: products
});
})
.catch(err => {
const error = new Error(err);
error.httpStatusCode = 500;
return next(error);
});
};
exports.postCart = (req, res, next) => {
// extract prod ID
const prodId = req.body.productId;
// run DB find with prod ID
Product.findById(prodId)
.then(product => {
// return true && add to cart
return req.user.addToCart(product);
})
.then(result => {
// re-render same page
res.redirect('back');
})
.catch(err => {
const error = new Error(err);
error.httpStatusCode = 500;
return next(error);
});
};
Would really appreciate if someone could help me with a way to do that. Thanks!
You can look into Array reducer function. It should look like this
cart.totalQty = cart.items.reduce((sum, item)=>{
return sum + item.quantity;
},0);

"UnhandledPromiseRejectionWarning: ValidationError" while placing order

Im trying to make an ecommerce app and the frontend is all done but when i place an order i get this message.
I rechecked every file but i cant find where its coming from. I followed the process from a udemy course but it just doesnt work. i got no response from the instructor and his code seems to work fine.
Here is the github master repo for the course.
I am stuck here for 10 days. HELP!
https://github.com/bluebits-academy/mern-stack-ecommerce
This is my Order.js
const mongoose = require('mongoose');
const orderSchema = mongoose.Schema({
orderItems: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'OrderItem',
required:true
}],
shippingAddress1: {
type: String,
required: true,
},
shippingAddress2: {
type: String,
},
city: {
type: String,
required: true,
},
zip: {
type: String,
required: true,
},
country: {
type: String,
required: true,
},
phone: {
type: String,
required: true,
},
status: {
type: String,
required: true,
default: 'Pending',
},
totalPrice: {
type: Number,
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
dateOrdered: {
type: Date,
default: Date.now,
},
})
orderSchema.virtual('id').get(function () {
return this._id.toHexString();
});
orderSchema.set('toJSON', {
virtuals: true,
});
exports.Order = mongoose.model('Order', orderSchema);
/**
Order Example:
{
"orderItems" : [
{
"quantity": 3,
"product" : "5fcfc406ae79b0a6a90d2585"
},
{
"quantity": 2,
"product" : "5fd293c7d3abe7295b1403c4"
}
],
"shippingAddress1" : "Flowers Street , 45",
"shippingAddress2" : "1-B",
"city": "Prague",
"zip": "00000",
"country": "Czech Republic",
"phone": "+420702241333",
"user": "5fd51bc7e39ba856244a3b44"
}
**/
This is my api for order. orders.js
const {Order} = require('../models/order');
const express = require('express');
const { OrderItem } = require('../models/order-item');
const router = express.Router();
router.get(`/`, async (req, res) =>{
const orderList = await Order.find().populate('user', 'name').sort({'dateOrdered': -1});
if(!orderList) {
res.status(500).json({success: false})
}
res.send(orderList);
})
router.get(`/:id`, async (req, res) =>{
const order = await Order.findById(req.params.id)
.populate('user', 'name')
.populate({
path: 'orderItems', populate: {
path : 'product', populate: 'category'}
});
if(!order) {
res.status(500).json({success: false})
}
res.send(order);
})
router.post('/', async (req,res)=>{
const orderItemsIds = Promise.all(req.body.orderItems.map(async (orderItem) =>{
let newOrderItem = new OrderItem({
quantity: orderItem.quantity,
product: orderItem.product
})
newOrderItem = await newOrderItem.save();
return newOrderItem._id;
}))
const orderItemsIdsResolved = await orderItemsIds;
const totalPrices = await Promise.all(orderItemsIdsResolved.map(async (orderItemId)=>{
const orderItem = await OrderItem.findById(orderItemId).populate('product', 'price');
const totalPrice = orderItem.product.price * orderItem.quantity;
return totalPrice
}))
const totalPrice = totalPrices.reduce((a,b) => a +b , 0);
let order = new Order({
orderItems: orderItemsIdsResolved,
shippingAddress1: req.body.shippingAddress1,
shippingAddress2: req.body.shippingAddress2,
city: req.body.city,
zip: req.body.zip,
country: req.body.country,
phone: req.body.phone,
status: req.body.status,
totalPrice: totalPrice,
user: req.body.user,
})
order = await order.save();
if(!order)
return res.status(400).send('the order cannot be created!')
res.send(order);
})
router.put('/:id',async (req, res)=> {
const order = await Order.findByIdAndUpdate(
req.params.id,
{
status: req.body.status
},
{ new: true}
)
if(!order)
return res.status(400).send('the order cannot be update!')
res.send(order);
})
router.delete('/:id', (req, res)=>{
Order.findByIdAndRemove(req.params.id).then(async order =>{
if(order) {
await order.orderItems.map(async orderItem => {
await OrderItem.findByIdAndRemove(orderItem)
})
return res.status(200).json({success: true, message: 'the order is deleted!'})
} else {
return res.status(404).json({success: false , message: "order not found!"})
}
}).catch(err=>{
return res.status(500).json({success: false, error: err})
})
})
router.get('/get/totalsales', async (req, res)=> {
const totalSales= await Order.aggregate([
{ $group: { _id: null , totalsales : { $sum : '$totalPrice'}}}
])
if(!totalSales) {
return res.status(400).send('The order sales cannot be generated')
}
res.send({totalsales: totalSales.pop().totalsales})
})
router.get(`/get/count`, async (req, res) =>{
const orderCount = await Order.countDocuments((count) => count)
if(!orderCount) {
res.status(500).json({success: false})
}
res.send({
orderCount: orderCount
});
})
router.get(`/get/userorders/:userid`, async (req, res) =>{
const userOrderList = await Order.find({user: req.params.userid}).populate({
path: 'orderItems', populate: {
path : 'product', populate: 'category'}
}).sort({'dateOrdered': -1});
if(!userOrderList) {
res.status(500).json({success: false})
}
res.send(userOrderList);
})
module.exports =router;
And this is my order-item.js
const mongoose = require('mongoose');
const orderItemSchema = mongoose.Schema({
quantity: {
type: Number,
required: true
},
product: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product'
}
})
exports.OrderItem = mongoose.model('OrderItem', orderItemSchema);
Do comment if you need any more codes.
The error :
ValidationError : OrderItem validation failed : product : Cast to ObjectId failed for value "{..}" at path "product"
means that: the "product" property in OrderItem expect an ObjectId, you can see it in the schema :
const orderItemSchema = mongoose.Schema({
quantity: {
type: Number,
required: true
},
product: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product'
}
})
but you're passing an object into it here :
let newOrderItem = new OrderItem({
quantity: orderItem.quantity,
product: orderItem.product //<------ orderItem.product is an object
})
newOrderItem = await newOrderItem.save();
I think it should be :
let newOrderItem = new OrderItem({
quantity: orderItem.quantity,
product: orderItem.product.id // we only need the id of product
})
newOrderItem = await newOrderItem.save();
You may need to log orderItem.product before creating a new OrderItem to ensure the data inside it.

In Mongoose findOneAndUpdate, how can I make my post request work?

Hi all so I am trying to make a post request that increments a value if it already exists and if not it should create a new item.
router.post('/', auth, async (req, res) => {
try {
const { name, price, image } = req.body;
var query = { name },
update = { $inc: { count: 1 } },
options = { upsert: true, new: true,};
await CartItem.findOneAndUpdate(query, update, options, function (
err,
data
) {
if (err) {
const newItem = new CartItem({
user: req.user.id,
name: name,
price: price,
image: image,
});
const item = newItem.save();
res.json(item);
} else {
res.json(data);
}
});
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const CartItemSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'user',
},
name: {
type: String,
required: true,
},
price: {
type: Number,
required: true,
},
count: {
type: Number,
},
image: {
type: String,
required: true,
},
});
module.exports = CartItem = mongoose.model('cartItem', CartItemSchema);
So there are two problems here that I cannot wrap my head around(Pretty new with MongoDb, did do my research).
I can get the count to increment, but it increments with 2 or even more instead of 1. (I know other users also experienced this)
If the item is already in the cart(name matches) I want a new item to be added which does happen, but it only adds the name, count and Id. I want it to add the user, name, price and image.
Would appreciate some assistance.
you should create your document with a default value equals to 0.
define count at your schema like the following:
count: {
type: Number,
default: 0
}
then use { $inc: { <field1>: <amount1>, <field2>: <amount2>, ... } }.
link to docs: https://docs.mongodb.com/manual/reference/operator/update/inc/

Duplicate item returned from collection

Blog Schema:
{
body: { type: String, required: true },
title: { type: String, required: true },
published: { type: String, default: false },
date: { type: Date, default: Date.now },
user: { type: Schema.Types.ObjectId, ref: 'BlogUser' },
comments: [{ type: Schema.Types.ObjectId, ref: 'Comments' }],
likes:[{user:{ type: Schema.Types.ObjectId, ref: 'BlogUser' }}]
}
Like Route for adding a like:
exports.likeBlog = async (req, res) => {
const blog_id = req.params.blog_id;
const user_id = req.body.user_id;
await Blog.findByIdAndUpdate(
blog_id,
{
$push: {
likes: {
user: user_id,
},
},
},
{ new: true },
(err, newBlog) => {
if (err) res.status(422).json(err);
console.log(newBlog);
res.json(newBlog);
}
);
};
Blog Route for reciveing a blog:
exports.getBlogByID = async (req, res) => {
const blog_id = req.params.blog_id;
try {
const blog = await Blog.findById(blog_id)
.populate("comments")
.populate("user");
console.log(blog);
res.json(blog);
} catch (error) {
res.status(401).json(error);
}
};
When I add a like by calling Like route from client, I get a blog with correct amount of likes i.e only 1. But when I request blog from Blog Route it returns me with two objects inside "likes" array, with both same as each other(same id too). Why am I getting such result? Mind you that I call 'Blog Route' after calling 'Like Route'.
It worked fine after I changed "like route" to this:
exports.likeBlog = async (req, res) => {
const blog_id = req.params.blog_id;
const user_id = req.body.user_id;
const blog = await Blog.findById(blog_id);
blog.likes.unshift({ user: user_id });
await blog.save();
Blog.findById(blog_id)
.then((result) => {
res.json(result);
})
.catch((error) => {
res.status(501).json({ error });
});
};
I still don't know what's the difference between the two though.

Creating a document and adding to set in same route

I have the following route:
app.post('/accounts', (req, res) => {
obj = new ObjectID()
var account = new Account({
name: req.body.name,
_owner: req.body._owner
}
)
return account.save()
.then((doc) => {
Account.update(
{
"_id": account._id
},
{
$addToSet: {
subscriptions: obj
}
}
)
})
.then((doc) => {
res.send(doc)
}
)
});
I am trying to create a document and then update a field in it (array) with a created objectID. When I call this route the new document is created however the new objectID is not being added to the subscription set.
Here is my model:
var Account = mongoose.model('Account', {
name: {
type: String,
required: true,
minlength: 1,
trim: true
},
_owner: {
type: mongoose.Schema.Types.ObjectId,
required: true
},
subscriptions: [{
type: mongoose.Schema.Types.ObjectId,
required: true
}]
module.exports = {
Account
};
if you have found the id and subscriptions is array then
app.post('/accounts', (req, res) => {
obj = new ObjectID()
var account = new Account({
name: req.body.name,
_owner: req.body._owner
});
return account.save()
.then((doc) => {
return Account.update( //return was missing which was causing the issue because of promise chain
{
"_id": account._id
},
{
$addToSet: {
subscriptions: obj
}
}
)
}).then((doc) => {
res.send(doc)
}
)
});

Resources