Mongoose : update object in array (bulkwrite) - node.js

i'm trying to update a object in array.
My field wasn't an array before, i used to use this code to update my field :
exports.updateStock = (req, res, next) => {
console.log(req.body.order.products);
let myOperations = req.body.order.products.map((prod) => {
return {
updateOne: {
filter: { _id: prod._id },
update: { $inc: { stock: -prod.quantity, sold: +prod.quantity } },
},
};
});
Product.bulkWrite(myOperations, {}, (err, products) => {
if (err) {
console.log("im here", err);
return res.status(400).json({
error: "Bulk operation failed",
});
}
next();
});
};
field :
stock: [
{
size: {
type: String,
required: true,
},
stock: {
type: Number,
required: true,
},
},
],
any idea how i can update my stock.stock based on size?
the size is passed with product -> prod.size
Example :
in store i have size : M | 100 stocks
L | 150 stocks
XS| 10 stocks
user : want to buy size M and quantity 2
so after the order i have to update Size m stocks
100-2 = 98 stocks remaing after order :
M | 98 stocks
L | 150 stocks
XS| 10 stocks
My entire product schema :
const mongoose = require("mongoose");
const { ObjectId } = mongoose.Schema;
const productSchema = new mongoose.Schema(
{
name: {
type: String,
trim: true,
required: true,
maxlength: 32,
},
description: {
type: String,
trim: true,
required: true,
maxlength: 2000,
},
discountprice: {
type: Number,
maxlength: 32,
trim: true,
},
price: {
type: Number,
required: true,
maxlength: 32,
trim: true,
},
category: [
{
type: ObjectId,
ref: "Category",
required: true,
},
],
subcategory: [
{
type: ObjectId,
ref: "SubCategory",
required: true,
},
],
stock: [
{
size: {
type: String,
required: true,
},
stock: {
type: Number,
required: true,
},
},
],
sold: {
type: Number,
default: 0,
},
photo: [String],
},
{ timestamps: true }
);
module.exports = mongoose.model("Product", productSchema);

You can use arrayfilters to update stock on the base of size in array stock,
updateOne: {
filter: { _id: prod._id },
update: { $inc: { "stock.$[elem].stock": -prod.quantity, sold: +prod.quantity } },
arrayFilters: [{ "elem.size": prod.size }]
}

Related

MongoDB geting an empty array from an aggregation

So I created this controller to get me the sum of all the orders totalPrice made by months
const getMonthlyOrderTotal = async (req, res) => {
try {
const year = req.params.year;
const aggregatePipeline = [
{
$match: {
createdAt: {
$gte: new Date(`${year}-01-01T00:00:00.000`),
$lt: new Date(`${year}-12-31T23:59:59.999`)
}
}
},
{
$group: {
_id: {
$month: "$createdAt"
},
total: {
$sum: "$totalPrice"
}
}
},
{
$sort: {
_id: 1
}
}
];
const orderTotals = await Order.aggregate(aggregatePipeline);
res.json(orderTotals);
} catch (err) {
res.status(500).json({ message: err.message });
}
};
this is the orderModel I am using
import mongoose from "mongoose";
const orderSchema = mongoose.Schema(
{
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: "User",
},
client: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: "Client",
},
orderItems: [
{
name: { type: String, required: true },
qty: { type: Number, required: true },
image: { type: String },
price: { type: Number, required: true },
product: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: "Product",
},
},
],
totalPrice: {
type: Number,
required: true,
default: 0.0,
},
taxPrice: {
type: Number,
required: true,
default: 0.0,
},
isPaid: {
type: Boolean,
required: true,
default: false,
},
paidAt: {
type: Date,
},
isDelivered: {
type: Boolean,
required: true,
default: false,
},
deliveredAt: {
type: Date,
},
},
{
timestamps: true,
}
);
const Order = mongoose.model("Order", orderSchema);
export default Order;
and when I try to test this API in postman "http://localhost:5001/orders/orderstotal/2022" I always get an empty array even though there is stored data in mongoDB orders Collection

One to many with MongoDB (Mongoose) with .populate()

What I am doing
I am trying to return a player by playerId with all ref items from each object in it's schema. In this example, I am specifically talking about the players inventory.
How can I return all reference items and their properties?
In my service file, I am getting my player with:
/**
* Get a player by playerId
* #param playerId
* #returns {Promise<*>}
*/
module.exports.getPlayer = async (playerId) => {
return await Player.findOne({ playerId: playerId }).populate('inventory');
};
And this is my returned JSON
{
"success": true,
"data": {
"_id": "63bb1f3ec17d33f4d2a87194",
"playerId": 4183489039,
"name": "AlanGreyjoy",
"currency": {
"copper": 500,
"silver": 10,
"gold": 0
},
"online": false,
"inventory": [
{
"currentDurability": 0,
"item": "63ba0c54407456969ba38615",
"amount": 1,
"_id": "63bc4fa070eaa247288e3573",
"createdAt": "2023-01-09T17:32:16.643Z",
"updatedAt": "2023-01-09T17:32:16.643Z"
}
],
"bank": [],
"questTracker": [],
"friends": [],
"knownRecipes": [],
"jobs": [],
"createdAt": "2023-01-08T19:53:34.903Z",
"updatedAt": "2023-01-09T17:32:16.643Z",
"__v": 1
}
}
As you can see, the item in the inventory array is not populating with the ref item from the items collection.
I have googled and googled and tried so many different things, but nothing is working.\
The item does exist in the db
My Models
My Player.model.js
const mongoose = require('mongoose');
const toJSON = require('../plugins/mongoToJson');
const Schema = mongoose.Schema;
const questTracker = new Schema(
{
quest: { type: mongoose.Schema.Types.ObjectId, ref: 'Quest' },
complete: { type: Boolean, default: false },
},
{ timestamps: true }
);
const friends = new Schema(
{
player: { type: mongoose.Schema.Types.ObjectId, ref: 'Player' },
isFavorite: { type: Boolean, default: false },
},
{ timestamps: true }
);
const knownRecipes = new Schema(
{
recipe: { type: mongoose.Schema.Types.ObjectId, ref: 'Recipe' },
},
{ timestamps: true }
);
const jobs = new Schema(
{
job: { type: mongoose.Schema.Types.ObjectId, ref: 'Job' },
},
{ timestamps: true }
);
const inventoryItems = new Schema(
{
item: { type: mongoose.Schema.Types.ObjectId, ref: 'Item' },
amount: { type: Number, default: 0 },
currentDurability: { type: Number, default: 0 },
},
{ timestamps: true }
);
const bank = new Schema(
{
item: { type: mongoose.Schema.Types.ObjectId, ref: 'Item' },
amount: { type: Number, default: 0 },
},
{ timestamps: true }
);
const playerSchema = new Schema(
{
playerId: {
type: Number,
unique: true,
required: true,
},
name: {
type: String,
required: true,
unique: true,
},
currency: {
type: Object,
default: {
copper: 500,
silver: 10,
gold: 0,
},
},
inventory: [inventoryItems],
bank: [bank],
questTracker: [questTracker],
friends: [friends],
knownRecipes: [knownRecipes],
jobs: [jobs],
online: {
type: Boolean,
default: false,
},
},
{ timestamps: true }
);
const PlayerModel = mongoose.model('player', playerSchema);
module.exports = PlayerModel;
My Item.model.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const toJSON = require('../plugins/mongoToJson');
const vendorPriceSchema = mongoose.Schema({
copper: {
type: Number,
default: 0,
},
silver: {
type: Number,
default: 0,
},
gold: {
type: Number,
default: 0,
},
});
const itemSchema = new Schema(
{
title: {
type: String,
required: true,
unique: true,
},
assetId: {
type: Number,
required: true,
},
description: {
type: String,
},
equipDescription: {
type: String,
},
additionalDescription: {
type: String,
},
consumableUseDescription: {
type: String,
},
itemLevel: {
type: Number,
},
requiredLevel: {
type: Number,
},
type: {
type: String,
required: true,
},
subtype: {
type: String,
required: true,
},
stamina: {
type: Number,
},
intellect: {
type: Number,
},
criticalStrike: {
type: Number,
},
agility: {
type: Number,
},
mastery: {
type: Number,
},
maxDurability: {
type: Number,
},
vendorPrice: { vendorPriceSchema },
minDamage: {
type: Number,
default: 0,
},
maxDamage: {
type: Number,
default: 0,
},
speed: {
type: Number,
},
maxStack: {
type: Number,
},
},
{ timestamps: true }
);
const ItemModel = mongoose.model('Item', itemSchema);
module.exports = ItemModel;
You can use something like this:
module.exports.getPlayer = async (playerId) => {
return await Player.findOne({ playerId: playerId }).populate({
path: "inventory",
populate: {
path: "item",
},
});
};

Joining two mongoose collections with many to many relationship

I have two models which are Product & Category with many to many relationship.
Following are my models
This is the model for Product
//Product.js
const mongoose = require("mongoose");
const { Schema } = mongoose;
const { ObjectId } = Schema;
const product = {
name: {
type: String,
required: true,
trim: true,
minlength: [2, "Too short"],
maxlength: [32, "Too long"],
unique: true,
},
slug: {
type: String,
unique: true,
lowercase: true,
index: true,
},
category: [
{
type: ObjectId,
ref: "Category",
},
],
description: {
type: String,
maxlength: 200,
},
price: {
type: Number,
required: true,
trim: true,
maxlength: 32,
},
shipping: {
type: String,
enum: ["Yes", "No"],
},
color: [
{
type: String,
enum: ["Black", "Brown", "Silver", "White", "Blue"],
},
],
sold: {
type: Number,
default: 0,
},
quantity: {
type: Number,
},
images: [
{
public_id: String,
url: String,
},
],
};
const productSchema = new Schema(product, { timestamps: true });
const Product = mongoose.model("Product", productSchema);
module.exports = Product;
This is the model for Category
//Category.js
const mongoose = require("mongoose");
const { Schema } = mongoose;
const { ObjectId } = Schema;
const category = {
name: {
type: String,
required: true,
trim: true,
max: 32,
unique: true,
},
subCategories: [
{
type: Schema.Types.ObjectId,
ref: "Category",
},
],
parent: {
type: Schema.Types.ObjectId,
ref: "Category",
},
products: [
{
type: ObjectId,
ref: "Product",
},
],
slug: {
type: String,
required: "URL can't be empty",
unique: true,
},
};
const categorySchema = new Schema(category, { timestamps: true });
//Validate the slug to ensure proper url is given
// categorySchema.path("slug").validate((val) => {
// urlRegex =
// /(ftp|http|https):\/\/(\w+:{0,1}\w*#)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%#!\-/]))?/;
// return urlRegex.test(val);
// }, "Invalid URL.");
const autoPopulateChildren = function (next) {
this.populate("subCategories");
// this.populate("parent");
next();
};
categorySchema
.pre("findOne", autoPopulateChildren)
.pre("findById", autoPopulateChildren)
.pre("find", autoPopulateChildren);
const Category = mongoose.model("Category", categorySchema);
module.exports = Category;
So, product has a foreign keys of category in array of category. Whereas Category has foreign keys of product in array of products. How do you join them ?
I tried this & it doesn't work. It returns empty object
router.get("/products", [RequireSignIn], async (req, res) => {
try {
const products = await Product.find({}).aggregate({
$lookup: {
from: "Category",
localField: "category",
foreignField: "_id",
as: "ProductCategories",
},
});
return res.status(200).send(products);
} catch (error) {
return res.status(200).send(error);
}
});

why i have got whole product details by console.log(req.product.photo.data); instead of only photo data?

when i "console.log(req.product.photo.data)" i have got whole product details there photo related details are first then others(_id, name, description etc) but i need only photo data. but in postman got right image.
router request-->
router.get("/product/photo/:productId",photo);
product model-->
const mongoose = require("mongoose");
const { ObjectId }=mongoose.Schema;
const productSchema = new mongoose.Schema({
name: {
type: String,
trim: true,
required: true,
maxlegth: 32,
},
description: {
type: String,
required: true,
maxlegth: 2000
},
price: {
type: Number,
trim: true,
required: true,
maxlegth: 32
},
price: {
type: Number,
trim: true,
required: true,
maxlegth: 32
},
category:{
type: ObjectId,
ref:'Category',//refer to category models
required:true
},
quantity:{
type:Number,
},
sold:{
type:Number,
default:0
},
photo:{
data:Buffer,
contentType:String
},
shipping:{
required:false,
type:Boolean
}
},
{timestamps:true}
);
photo function code in product controller-->
exports.photo=(req,res,next)=>{
console.log(req.product.photo.data);
if(req.product.photo.data)
{
res.set("Content-Type",req.product.photo.contentType);
return res.send(req.product.photo.data);
}
next();
}
productById middleware code in product controller-->
exports.productById = (req, res, next, id) => {
Product.findById(id).exec((err, product) => {
if (err || !product) {
return res.status(400).json({
error: "Product not found",
});
}
req.product = product;
next();
});
};

Implementing addFields with Mongoose(MongoDB)

I am trying to calculate the average value of the ratings for a Product. Instead of calculating the average rating of the Product every time when we need the value. I calculate it every time someone rates it. I am expecting to see additional fields in the Document but for some reason console.log('stats: ', stats); is printing out an empty array and the fields are not added to the Document. Let me know what I am doing wrong.
const calculateAvgRating = async (id) => {
try {
const stats = await Product.aggregate([
{ $match: { _id: id } },
{
$addFields: {
avgRating: {
$avg: '$ratings.star',
},
nRatings: {
$size: '$ratings',
},
},
},
]);
console.log('stats: ', stats);
} catch (err) {
console.log(`calculateAvgRating error: ${err}`);
}
};
Schema
import mongoose from 'mongoose';
const { ObjectId } = mongoose.Schema;
const ParentSchema = new mongoose.Schema(
{
title: {
type: String,
trim: true,
required: true,
maxlength: 32,
text: true,
},
slug: {
type: String,
unique: true,
lowercase: true,
index: true,
},
price: {
type: Number,
required: true,
trim: true,
maxlength: 32,
},
quantity: Number,
ratings: [
{
star: Number,
postedBy: { type: ObjectId, ref: 'User' },
},
],
},
{
timestamps: true,
}
);
export default mongoose.models.Product ||
mongoose.model('Product', ParentSchema);

Resources