Mongoose - Cannot modify field value - node.js

I am building a simple shop backend for practice purposes. I have three schemas Product, Customer and Order.
What I am trying to achieve is to subtract the ordered quantity from the stock quantity for each product inside an order, when the order is created. Clearly I am doing something wrong cause my productsToUpdateInDbArray contains the correct products (checked it with console log) but I can't find a way to make it work.
stockQty field inside Products collection is not updating.
My controller code is:
'use strict'
// require validator for string validation
const validator = require('validator');
// import Order, Customer, Product Models
const Order = require("../models/order.model");
const Customer = require("../models/customer.model");
const Product = require("../models/product.model");
// DEFINE CONTROLLER FUNCTIONS
// listAllOrders function - To list all orders
exports.listAllOrders = (req, res) => {
Order.find({}, (err, orders) => {
if (err) {
return res.status(500).send(`Internal server error: ${error}`);
}
if (orders && orders.length === 0) {
return res.status(404).send(`No orders found!`);
}
return res.status(200).json(orders);
});
};
// createNewOrder function - To create new order
exports.createNewOrder = (req, res) => {
const customerId = req.body?.customerId;
const productsArray = req.body?.products;
let productsToUpdateInDbArray = [];
if (!validator.isMongoId(customerId)) {
return res.status(400).send('Invalid customer Id');
}
Customer.findById(customerId, async (err, customer) => {
if (err) {
return res.status(500).send(`Internal server error: ${error}`);
}
if (!customer) {
return res.status(404).send(`No customers found!`);
}
if (!productsArray || productsArray.length === 0) {
return res.status(400).send(`No products found in the order!`);
}
for (let product of productsArray) {
if (!validator.isMongoId(product?.productId)) {
return res.status(400).send('Invalid product Id');
}
if (!product?.quantity || product?.quantity < 1) {
return res.status(400).send('Invalid product quantity');
}
let productFound = await Product.findById(product?.productId).exec();
if (!productFound) {
return res.status(404).send('Product not found!');
}
if (productFound.stockQty < product.quantity) {
return res.status(400).send('Not enough product quantity in stock')
}
productFound.stockQty -= product.quantity;
productsToUpdateInDbArray.push(productFound);
}
console.log(productsToUpdateInDbArray)
const newOrder = new Order(req.body);
newOrder.save((err, order) => {
if (err) {
return res.status(500).send(`Internal server error: ${error}`);
}
for (let item of productsToUpdateInDbArray) {
const filter = { _id: item._id };
const update = { stockQty: item.stockQty };
Product.findOneAndUpdate( filter, update )
}
return res.status(201).json(order);
});
});
};
And my models are:
'use strict';
// Import mongoose
const mongoose = require("mongoose");
// Declare schema and assign Schema class
const Schema = mongoose.Schema;
// Create Schema Instance and add schema propertise
const ProductSchema = new Schema({
name: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
description: {
type: String,
required: true
},
imageUrl: {
type: String,
required: true
},
stockQty: {
type: Number,
required: true
}
});
// create and export model
module.exports = mongoose.model("Products", ProductSchema);
'use strict';
// Import mongoose
const mongoose = require("mongoose");
// Declare schema and assign Schema class
const Schema = mongoose.Schema;
// Create Schema Instance and add schema propertise
const OrderSchema = new Schema({
products: [
{
productId: {
type: Schema.Types.ObjectId,
required: true,
ref: "Products"
},
quantity: {
type: Number,
default: 1
}
}
],
customerId: {
type: Schema.Types.ObjectId,
required: true,
ref: "Customers"
}
});
// create and export model
module.exports = mongoose.model("Orders", OrderSchema);

findOneAndUpdate will only execute the query when a callback is passed. So in your case you can either add an await or callback.
await Product.findOneAndUpdate( filter, update );
or
Product.findOneAndUpdate( filter, update, callback );

Related

Crud operations mongodb , nodejs , express , mongoose

import mongoose from "mongoose";
const productSchema = mongoose.Schema(
{
name: {
type: String,
required: true
},
category: {
type: mongoose.Schema.Types.ObjectId,
ref: "Category",
required: true
}
},
{ timestamps: true }
);
const Product = mongoose.model("Product", productSchema);
export default Product;
import mongoose from "mongoose";
const categorySchema = mongoose.Schema(
{
name: {
type: String,
required: true,
unique: true
}
},
{ timestamps: true }
);
const Category = mongoose.model("Category", categorySchema);
export default Category;
// create Products
const createProduct = asyncHandler(async (req, res) => {
const { name } = req.body;
const product = new Product({
name,
category: req.category._id
});
if (product) {
const createdProduct = await product.save();
res.status(201).json(createdProduct);
} else {
res.status(404).json({ message: "Product already exists" });
}
});
// update product
const updateProduct = asyncHandler(async (req, res) => {
const { name, categoryName } = req.body;
const product = await Product.findById(req.params.id);
if (product) {
product.name = name;
product.categoryName = categoryName;
const updatedProduct = await product.save();
res.json(updatedProduct);
} else {
res.status(404);
throw new Error("Product not found");
}
});
I'm not able to get the category data into the product data its shows
BSON error , I want the data to look this way.
> products = [ {
> _id : "", name: "", category : { _id:"", name:"" } } ]
and i want to use this data to as create api - product name &
category name which automatically creates a id for product & category
just including products & category names
I will try to help you as what I have understood from your post.
First of all in the product schema you are only saving category ID and you are not mentioning the product category name field. You need to add that to your productSchema as shown below.
const productSchema = mongoose.Schema(
{
name: {
type: String,
required: true
},
categoryId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Category",
required: true
},
categoryName:{
type:String
}
},
{ timestamps: true }
);
const Product = mongoose.model("Product", productSchema);
Now what you have to do is that, since you have the categoryId, you have to fire a query to your Category collection before saving as shown below.
// Create Products
const createProduct = asyncHandler(async (req, res) => {
const { name, categoryId } = req.body;
//Querying Category collection
const category = await Category.find({_id:categoryId})
//Creating Product Object
const product = new Product({
name,
categoryId,
categoryName:category.name
});
if (product) {
const createdProduct = await product.save();
res.status(201).json(createdProduct);
} else {
res.status(404).json({ message: "Product already exists" });
}
});
Now you will be able to save the name also in the product collection.
And going through your update handler you should not update the category name in your product document, what the best practice is to use categoryId from req.body and query to category collection for the name. If the you want to change the category name change it in your category collection and use that particular object Id to update it in your product collection.
Hope this helps you.
Thanks

How to save a document that is associated to another foreign key? in MONGODB (NO SQL)

I would like to ask on how I can save a Sub Category by getting the ID of the Category.
Category Model
import mongoose from 'mongoose'
const categorySchema = mongoose.Schema({
name: {
type: String,
required: true,
},
})
const Category = mongoose.model('Category', categorySchema)
export default Category
Sub Category model
import mongoose from 'mongoose'
const subCategorySchema = mongoose.Schema({
subname: {
type: String,
required: true,
},
categoryid: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'Category',
},
})
const SubCategory = mongoose.model('SubCategory', subCategorySchema)
export default SubCategory
My Controller
const createSubCategory = asyncHandler(async (req, res) => {
const { subname, categoryid} = req.body
const subCategoryExists = await SubCategory.findOne({
subcategoryname,
}).populate('categoryname')
if (subCategoryExists) {
res.status(400)
throw new Error('SubCategory already exists')
}
const name = await Category.find({}).populate('_id')
const subCategory = new SubCategory({
categoryid: name,
subcategoryname,
})
const createdSubCategory = await subCategory.save()
if (createdSubCategory) {
res.status(201).json({
_id: createdSubCategory._id,
categoryid: createdSubCategory,
subname: createdSubCategory.subname,
})
} else {
res.status(400)
throw new Error('Invalid SubCategory')
}
})
My question is
How am I going to save my Sub Category in which I will select the ID Value of the Category and insert it to the Sub Category - categoryname field ? Is is working but all I gets is the first ID in the category even if you typed another ID.
I wanted to have something like this upon saving in mongodb
SubCategory
{
_id: 123,
subcategory: "Sub Category",
categoryname:{
_id: 123456(categoryid),
categoryname: "Categoryname"
}
}
I have managed to figure it out, you need to make it match. In your request.body you have your field name called categoryid so in the Category.FindOne({_id:}
you need to match what you type in categoryid to the findone of _id in order for you to get that. :D
const createSubCategory = asyncHandler(async (req, res) => {
const { subname, categoryid} = req.body
const subCategoryExists = await SubCategory.findOne({
subcategoryname,
}).populate('categoryname')
if (subCategoryExists) {
res.status(400)
throw new Error('SubCategory already exists')
}
const id= await Category.find({_id: categoryid}).populate('_id')
const subCategory = new SubCategory({
categoryid: id,
subcategoryname,
})
const createdSubCategory = await subCategory.save()
if (createdSubCategory) {
res.status(201).json({
_id: createdSubCategory._id,
categoryid: createdSubCategory,
subname: createdSubCategory.subname,
})
} else {
res.status(400)
throw new Error('Invalid SubCategory')
}
})

MongoDB Update a document's key when another document is updated

I am using Express.js, Node.js, MongoDB, and Mongoose stack
I have three documents in the database which are Warehouses, Inventories, and Items.
A Warehouse has one Inventory, and each Inventory is assigned to a Warehouse.
An Inventory contains many Items.
items.model.js
const mongoose = require('mongoose');
const itemSchema = new mongoose.Schema({
name:{
type: String
},
dateCreated:{
type: Date,
default: Date.now()
},
price:{
type:Number
},
type:{
type:String
},
category:{
type:String
},
description:{
type: String
},
picture:{
type: String
}
})
const Item = mongoose.model('item',itemSchema)
module.exports.Item=Item
module.exports.itemSchema = itemSchema
inventories.model.js
const mongoose = require('mongoose');
const itemSchema = require('../items/items.model').itemSchema;
const inventorySchema = new mongoose.Schema({
name:{
type: String,
unique: true
},
dateCreated:{
type: Date
},
items:{
type:[itemSchema]
}
})
const Inventory = mongoose.model('inventory',inventorySchema)
module.exports.inventorySchema=inventorySchema;
module.exports.Inventory=Inventory;
warehouses.model.js
const mongoose = require('mongoose');
const inventorySchema = require('../inventories/inventories.model').inventorySchema
const warehouseSchema = new mongoose.Schema({
name:{
type:String
},
location:{
type:String
},
inventory:{
type:inventorySchema
},
dateCreated:{
type:Date,
default:Date.now()
},
numberOfEmployees:{
type:Number
}
})
const Warehouse = mongoose.model('warehouse',warehouseSchema)
module.exports.Warehouse=Warehouse
I have an endpoint which assigns an Inventory to a Warehouse based on the Inventory's name. I also have an endpoint that adds an Item to an Inventory
warehouses.controller.js
/**
*
* Assign an inventory to warehouse based on
* Warehouse (id), inventory (name)
*/
const assignInventory = () => async (req, res) => {
try {
const inventoryName = req.body.inventory
const inventoryDoc = await Inventory.findOne({ name: inventoryName })
if (!inventoryDoc) {
return res.status(404).json('No Inventory with this ID exists!')
}
const warehouseID = req.params.id
const doc = await Warehouse.findByIdAndUpdate(
{ _id: warehouseID },
{ ...req.body, inventory: inventoryDoc }
)
if (!doc) {
return res.status(404).end()
}
return res.status(200).json({
message: 'Assigned Inventory to warehouse successfully',
data: doc
})
} catch (error) {
console.error(error)
res.status(400).end()
}
}
inventories.controller.js
/**
*
* Add an Item to an Inventory based on
* Inventory (id), Item (name)
*/
const addItemToInventory = () => async(req,res)=>{
try {
//Retreive the inventory id from the URL Parameters
const inventoryID= req.params.id
//Retreive the item name from the request body
const itemName= req.body.name;
//Fetch the item document from the database based on the request body
const itemDoc = await Item.find({name:itemName})
if(!itemDoc)
{
return res.status(404).json(`Can't find item with this name`)
}
console.log(itemDoc)
//Update the inventory with the the new item document added to its item list
const inventoryDoc = await Inventory.findById({_id:inventoryID})
console.log(inventoryDoc)
var inventoryList = inventoryDoc.items
console.log('Updated')
inventoryList.push(itemDoc)
console.log(inventoryList)
const updatedInventoryDoc = await Inventory.findByIdAndUpdate({_id:inventoryID},{$push:{items:itemDoc}})
if(!updatedInventoryDoc){
return res.status(404).end()
}
res.status(200).json({data: updatedInventoryDoc})
} catch (e) {
console.error(e)
}
}
The problem is that whenever I add an Item to an Inventory that is already assigned to a Warehouse, if I fetched that Warehouse, it will not show the added Item.
In the image above, I added an Item to the Inventory and fetched that Inventory.
The Inventory was previously added to a Warehouse.
Yet as seen above here, The Warehouse still has only one item inside it.
Is there anything that can reference an Inventory inside a Warehouse? so that after any update to any Inventory, the Warehouse will listen to these updates.
Thanks in advance.
Edit 1: Added Inventory, Warehouse, and Item models.
After searching I found out that I should change the structure of the warehouse.model.js to make it referring to the ObjectID of an Inventory, so it is going to look like this.
const mongoose = require('mongoose');
const inventorySchema = require('../inventories/inventories.model').inventorySchema
const warehouseSchema = new mongoose.Schema({
name:{
type:String
},
location:{
type:String
},
//Instead of inventory:inventorySchema
inventory:{
type: mongoose.Schema.Types.ObjectId,
ref: "inventory"
},
dateCreated:{
type:Date,
default:Date.now()
},
numberOfEmployees:{
type:Number
}
})
const Warehouse = mongoose.model('warehouse',warehouseSchema)
module.exports.Warehouse=Warehouse
and inside the warehouses.controller.js, we should call populate on any database call that retrieves the Warehouse documents like this
const readWarehouse = () => async (req, res) => {
try {
const doc = await Warehouse.find({}).populate('inventory')
if (!doc) {
return res.status(404).end()
}
res.status(200).json({ data: doc })
} catch (e) {
console.error(e)
res.status(400).end()
}
}
const readOneWarehouse = () => async (req, res) => {
try {
const id = req.params.id
const doc = await Warehouse.findOne({ _id: id }).populate('inventory')
if (!doc) {
return res.status(404).end()
}
res.status(200).json({ data: doc })
} catch (e) {
console.error(e)
res.status(400).end()
}
}
Also updated the assignInventory() to be like that
const assignInventory = () => async (req, res) => {
try {
const inventoryName = req.body.inventory
const inventoryDoc = await Inventory.findOne({ name: inventoryName })
if (!inventoryDoc) {
return res.status(404).json('No Inventory with this ID exists!')
}
const inventoryDocID=inventoryDoc._id
const warehouseID = req.params.id
const doc = await Warehouse.findByIdAndUpdate(
{ _id: warehouseID },
{ ...req.body, inventory: inventoryDocID }
)
if (!doc) {
return res.status(404).end()
}
return res.status(200).json({
message: 'Assigned Inventory to warehouse successfully',
data: doc
})
} catch (error) {
console.error(error)
res.status(400).end()
}
}

How to waiting while document updating in mongoose?

I have this code:
module.exports = async (msg) => {
const postId = msg.wall.copyHistory[0].id;
const userId = msg.wall.ownerId;
const bonusePostManager = new BonusePostManager(postId)
const post = await bonusePostManager.getPost(postId);
if (!post) return;
if (post.reposters.includes(userId)) return;
const balanceManager = new BalanceManager(userId, 0);
const doubleCheckReposter = await bonusePostManager.getPost(postId);
if (doubleCheckReposter?.reposters.includes(userId)) return;
bonusePostManager.addReposter(userId)
.catch(console.error)
.then((res) => {
balanceManager.plusBalance(post.bonuseAmount, 'balance').then(async (res) => {
await messageAssistant.sendMessage({
peer_id: userId,
text: `Вы сделали репост, вы получаете ${numberWithSpace(post.bonuseAmount)}`
})
})
})}
If a person makes a repost from two devices at the same time, then the document does not have time to update and allows the possibility of a double repost. I tried using the $addToSet operator:
addReposter(userId, postId = this.postId) {
return Bonuse.updateOne({
id: postId,
}, {
$addToSet: {
'reposters': userId
}
})
}
But it doesn't help, I really don't know how to fix it. I return the promiss everywhere, try to wait for them, but this does not fix the situation, please help me!
I also attach the BonusePost scheme:
const { Schema } = require('mongoose');
const PostSchema = new Schema({
postId: {
type: Number,
unique: true,
index: true
},
active: {
type: Boolean,
default: true,
index: true,
},
bonuseAmount: {
type: Number,
},
reposters: {
type: Array,
default: [],
}
})
module.exports = PostSchema;
And model:
const { model } = require('mongoose');
const PostSchema = require('./schema');
const Bonuse = new model('Bonuse', PostSchema)
module.exports = Bonuse;

Cascade Delete in mongo

I am new to MongoDB. I created 4 collections & they are connected with each other. (I am using node.js to write it)
Here, it's my question. How can I delete all records at once? Is there something like deep level population?
This one holds all models.
const DataModel = mongoose.Schema({
_id: { type: mongoose.Schema.Types.ObjectId, ref: 'User', require: true},
order: { type: mongoose.Schema.Types.ObjectId, ref: 'Order', require: true},
});
User model
const userSchema = mongoose.Schema({//other stuff});
Order model
const orderSchema = mongoose.Schema({
product: { type: mongoose.Schema.Types.ObjectId, ref: 'Product', required: true },
//other stuff
});
Product model
const productSchema = mongoose.Schema({//other stuff});
I can delete the entry with these code from the database, but the other entries still there
exports.delete_data = (req, res, next) => {
const id = req.params.userId;
userDataModel.deleteOne({_id: id})
.exec()
.then(docs => {
res.status(200).json({
message: 'Record Deleted',
request: {
type: 'POST'
}
});
})
.catch(err => {
console.log(err);
res.status(500).json({
error: err
});
});
};
Update: However, I wonder, Could I call other defined delete functions for order, product inside delete_data
As #Geert-Jan suggest, cascade delete is my solution. The link that geert-jan gave solve my problem. However, I wonder, Could I call other defined delete functions for order, product inside delete_data
i did this and it could be good for someone who wants to delete documents in cascade linked to any field of a model.
async blackHole() {
try {
const rtn = new ApiResponse<any>();
const userId = id;
const accountId = mongoose.Types.ObjectId(id);
var CollectionNames: any[] = [];
mongoose.connection.db.listCollections().toArray(function (err, collections) {
CollectionNames = collections.map(c => c.name);
CollectionNames.forEach((element: any) => {
mongoose.connection.db.collection(element).deleteMany({ "account": accountId });
});
});
const accountController = new AccountController(this.wsParams);
await accountController.delete(id)
await super.delete(userId);
return rtn;
} catch (error: any) {
const rtn = new ApiResponse<any>();
rtn.message = error;
rtn.success = false;
rtn.status = 422;
return rtn;
}
}
I hope you can use it :D

Resources