Mongoose not adding virtual property in the response object using virtual populate - node.js

So in my project i have two different models . Tour Model and Review Model. Review Model has parent reference of tour id. I want to add virtual populate on tour model so that I can get all reviews related to the tour. Basically doing it other way round.
So what I am expecting here is that whenever I hit a route on getTour I should see a property called "reviews" added to the response object but there is actually isn't. Please tell me is there anything I'm missing here.
const mongoose = require('mongoose');
const slugify = require('slugify');
const User = require('./userModel');
const Review = require('./reviewModel');
const tourSchema = new mongoose.Schema(
{
name: {
type: String,
required: [true, 'A tour must have a name'],
unique: true,
maxlength: [40, 'A tour namme must have less or equal 40 characters'],
minlength: [10, 'A tour namme must have greater or equal 10 characters'],
},
slug: String,
duration: {
type: Number,
required: [true, 'A tour must have a duration'],
},
maxGroupSize: {
type: Number,
required: [true, 'A tour must have a GroupSize'],
},
difficulty: {
type: String,
required: [true, 'A tour must have difficulty'],
},
ratingsQuantity: {
type: Number,
default: 0,
},
ratingsAverage: {
type: Number,
default: 4.5,
min: [1, 'Rating must be above 1.0'],
max: [5, 'Rating must be below 5.0'],
},
price: {
type: Number,
required: [true, 'A tour must have a price '],
},
priceDiscount: {
type: Number,
validate: {
validator: function (val) {
return val < this.price;
},
message: 'Discount must be lower than the price',
},
},
summary: {
type: String,
trim: true,
required: [true, 'A tour must have summary'],
},
description: {
type: String,
trim: true,
},
imageCover: {
type: String,
required: [true, 'A tour must have a cover image'],
},
images: [String],
createdAt: {
type: Date,
default: Date.now(),
},
startDates: [Date],
secretTour: {
type: Boolean,
dafault: false,
},
startLocation: {
//Geo json
type: {
type: String,
default: 'Point',
enum: ['Point'],
},
coordinates: [Number],
address: String,
description: String,
},
locations: [
{
type: {
type: String,
default: 'Point',
enum: ['Point'],
},
coordinates: [Number],
address: String,
description: String,
day: Number,
},
],
guides: [
{
type: mongoose.Schema.ObjectId,
ref: 'User',
},
],
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
tourSchema.virtual('reviews', {
ref: 'Review', //name of model
foreignField: 'tour', //foreign property in review model
localField: '_id',
});
const Tour = mongoose.model('Tour', tourSchema);
module.exports = Tour;
const mongoose = require('mongoose');
const slugify = require('slugify');
const reviewSchema = new mongoose.Schema(
{
review: {
type: String,
required: true,
},
rating: {
type: Number,
min: 1,
max: 5,
required: true,
},
createdAt: {
type: Date,
default: Date.now,
},
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: [true, 'Review must belong to a user'],
},
tour: {
type: mongoose.Schema.ObjectId,
ref: 'Tour',
required: [true, 'Review must belong to a tour'],
},
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
reviewSchema.pre(/^find/, function (next) {
this.populate({
path: 'user',
select: 'name',
}).populate({
path: 'tour',
select: 'name',
});
next();
});
const Review = mongoose.model('Review', reviewSchema);
module.exports = Review;

You just have to populate reviews field. so after doing virtual property write this code below it in file contains tourSchema.
If you want to find single tour
tourSchema.pre('findOne', function (next) {
this.populate('reviews');
next();
});

Related

How to update a specific object in a array of objects by Item ID in Node.js and Mongoose

I need a code in Express.Js and Mongodb where I can change an exact string inside an object through the id of the item, I need to change the string "colorSelected" to a new value, changing only what I put in the body, in my previous failed attempts every change I made would change the entire object, I don't want that, thank you in advance.
Router Cart.js
//update color cart
router.patch("/cart/:id", Auth, async (req, res) => {
});
Model Cart.js
const mongoose = require('mongoose')
const ObjectID = mongoose.Schema.Types.ObjectId
const cartSchema = new mongoose.Schema({
owner : {
type: ObjectID,
required: true,
ref: 'User'
},
items: [{
itemId: {
type: ObjectID,
ref: 'Item',
required: true
},
img: String,
name: String,
colorSelected: String,
colors: Array,
stock: Number,
quantity: {
type: Number,
required: true,
min: 1,
default: 1
},
price: Number
}],
bill: {
type: Number,
required: true,
default: 0
}
}, {
timestamps: true
})
const Cart = mongoose.model('Cart', cartSchema)
module.exports = Cart
Model Item.js
const mongoose = require('mongoose')
const ObjectID = mongoose.Schema.Types.ObjectId
const reviewSchema = mongoose.Schema(
{
name: { type: String, required: true },
rating: { type: Number, required: true },
comment: { type: String, required: true },
user: {
type: ObjectID,
required: true,
ref: 'User'
},
},
{
timestamps: true,
}
)
const itemSchema = new mongoose.Schema({
images: [{
name: {
type: String,
required: true
},
src: {
type: String,
required: true
},
color: {
type: String,
required: true
},
}],
owner : {
type: ObjectID,
required: true,
ref: 'User'
},
name: {
type: String,
required: true,
trim: true
},
description: {
type: String,
required: true
},
category: {
type: Number,
required: true
},
price: {
type: Number,
required: true
},
stock: {
type: Number,
required: true
},
visibility: {
type: Boolean,
required: true
},
reviews: [reviewSchema],
rating: {
type: Number,
required: true,
default: 0,
},
numReviews: {
type: Number,
required: true,
default: 0,
}
}, {
timestamps: true
})
const Item = mongoose.model('Item', itemSchema)
module.exports = Item

How to get data from mongoDB on the basis of a value inside a nested object

Below is my API for fetching all the orders of a specific restaurant from a mongoDB database.
My search is based on the restaurant id which I am getting from params and passing it to the query object.
query1 is working but query2 does not return my data.
It is returning an empty array. If I pass a simple object in find() then I am getting a response. But when i use a nested object for getting data I get nothing.
exports.getOrders = async (req, res) => {
const restId = req.params.restId;
console.log("restaurant id", restId);
if (!restId) return res.status(404).send("No restaurant ID found in params");
const query1 = {grandTotal: 600};
const query2 = { restaurant: { restaurantId: restId } };
const response = await Orders.find(query2);
console.log("Printing response inside API function", response);
}
Below is my MongoDB Schema of Orders.
const mongoose = require("mongoose");
const orderSchema = mongoose.Schema({
customer: {
name: {
type: String,
required: true,
},
contact: {
type: String,
required: true,
},
customerId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Customer",
required: true,
},
customerAddress: {
type: String,
required: true,
},
},
restaurant: {
restaurantName: {
type: String,
required: true,
},
contact: {
type: String,
required: true,
},
restaurantId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Restaurant",
required: true,
},
},
/*date: {
day: {
type: Number,
required: true,
},
month: {
type: String,
required: true,
},
year: {
type: Number,
required: true,
},
},
type: {
type: String,
enum: ["delivery","takeaway"],
required: true,
},*/
items: [
{
itemId: { type: String, required: true },
itemName: { type: String, required: true },
itemDescription: { type: String, required: true },
price: {type: Number, required: true},
quantity: { type: Number, required: true },
total: {type: Number, required: true},
},
],
grandTotal: {type: Number, required: true},
status: {
type: String,
enum: ["pending", "rejected","cancelled","accepted","received"],
required: true,
},
});
orderSchema.virtual("booking", {
ref: "Booking",
localField: "_id",
foreignField: "orderId",
});
orderSchema.set("toObject", { virtual: true });
orderSchema.set("toJSON", { virtual: true });
const Orders = mongoose.model("Orders", orderSchema);
exports.Orders = Orders;
change query2 to this
const query2 = { "restaurant.restaurantId": restId } };

Want to get a user who uploaded the product in the order model schema mongo db mongoose

I have a product schema in which user(who uploaded ,created the product) is there who creates a product and then there is an order , order schema has the product included , I want it to include the products uploader user . Can anyone help me to do that ? I really hope i make it clear enough it's haunting me for days and I have searched the internet
const mongoose = require('mongoose')
const orderSchema = mongoose.Schema({
shippingInfo: {
address: {
type: String,
required: true
},
city: {
type: String,
required: true
},
phoneNo: {
type: String,
required: true
},
postalCode: {
type: String,
required: true
},
country: {
type: String,
required: true
}
},
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User'
},
orderItems: [
{
name: {
type: String,
required: true
},
quantity: {
type: Number,
required: true
},
image: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
product: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'Product'
},
}
],
paymentInfo: {
id: {
type: String
},
status: {
type: String
}
},
paidAt: {
type: Date
},
itemsPrice: {
type: Number,
required: true,
default: 0.0
},
taxPrice: {
type: Number,
required: true,
default: 0.0
},
shippingPrice: {
type: Number,
required: true,
default: 0.0
},
totalPrice: {
type: Number,
required: true,
default: 0.0
},
orderStatus: {
type: String,
required: true,
default: 'Processing'
},
deliveredAt: {
type: Date
},
createdAt: {
type: Date,
default: Date.now
}
})
module.exports = mongoose.model('Order', orderSchema)
this is my product Schema
const mongoose = require('mongoose')
const productSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Please enter product name'],
trim: true,
maxLength: [100, 'Product name cannot exceed 100 characters']
},
price: {
type: Number,
required: [true, 'Please enter product price'],
maxLength: [5, 'Product name cannot exceed 5 characters'],
default: 0.0
},
description: {
type: String,
required: [true, 'Please enter product description'],
},
ratings: {
type: Number,
default: 0
},
images: [
{
public_id: {
type: String,
required: true
},
url: {
type: String,
required: true
},
}
],
category: {
type: String,
required: [true, 'Please select category for this product'],
enum: {
values: [
'Electronics',
'Cameras',
'Laptops',
'Accessories',
'Headphones',
'Food',
"Books",
'Clothes/Shoes',
'Beauty/Health',
'Sports',
'Outdoor',
'Home'
],
message: 'Please select correct category for product'
}
},
seller: {
type: String,
required: [true, 'Please enter product seller']
},
stock: {
type: Number,
required: [true, 'Please enter product stock'],
maxLength: [5, 'Product name cannot exceed 5 characters'],
default: 0
},
numOfReviews: {
type: Number,
default: 0
},
reviews: [
{
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true
},
name: {
type: String,
required: true
},
rating: {
type: Number,
required: true
},
comment: {
type: String,
required: true
}
}
],
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true
},
createdAt: {
type: Date,
default: Date.now
}
})
module.exports = mongoose.model('Product', productSchema);
this is my user Schema
const mongoose = require('mongoose');
const validator = require('validator');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken')
const crypto = require('crypto')
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Please enter your name'],
maxLength: [30, 'Your name cannot exceed 30 characters']
},
email: {
type: String,
required: [true, 'Please enter your email'],
unique: true,
validate: [validator.isEmail, 'Please enter valid email address']
},
password: {
type: String,
required: [true, 'Please enter your password'],
minlength: [6, 'Your password must be longer than 6 characters'],
select: false
},
avatar: {
public_id: {
type: String,
required: true
},
url: {
type: String,
required: true
}
},
role: {
type: String,
default: 'user'
},
createdAt: {
type: Date,
default: Date.now
},
resetPasswordToken: String,
resetPasswordExpire: Date
})
// Encrypting password before saving user
userSchema.pre('save', async function (next) {
if (!this.isModified('password')) {
next()
}
this.password = await bcrypt.hash(this.password, 10)
})
// Compare user password
userSchema.methods.comparePassword = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password)
}
// Return JWT token
userSchema.methods.getJwtToken = function () {
return jwt.sign({ id: this._id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_TIME
});
}
// Generate password reset token
userSchema.methods.getResetPasswordToken = function () {
// Generate token
const resetToken = crypto.randomBytes(20).toString('hex');
// Hash and set to resetPasswordToken
this.resetPasswordToken = crypto.createHash('sha256').update(resetToken).digest('hex')
// Set token expire time
this.resetPasswordExpire = Date.now() + 30 * 60 * 1000
return resetToken
}
module.exports = mongoose.model('User', userSchema);
Order.find(any order).populate([ { path: 'orderItems.product', model: 'Order', populate: [ { path: 'user', model: 'User', select: 'name', }, ], }, ]) ` Note: you can edit it for correct paths

want to show orders only to specific product owners to which orders has been given in mongodb MERN STACK DEVELOPMENT

This is my order Schema
the person who actually starts the order , can get his SPECIFIC ORDERS, but the person who gets order to his product can not get specific orders.Which are his , i am copying a tutorial and making a product please help with that
const mongoose = require('mongoose')
const orderSchema = mongoose.Schema({
shippingInfo: {
address: {
type: String,
required: true
},
city: {
type: String,
required: true
},
phoneNo: {
type: String,
required: true
},
postalCode: {
type: String,
required: true
},
country: {
type: String,
required: true
}
},
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User'
},
orderItems: [
{
name: {
type: String,
required: true
},
quantity: {
type: Number,
required: true
},
image: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
product: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'Product'
}
}
],
paymentInfo: {
id: {
type: String
},
status: {
type: String
}
},
paidAt: {
type: Date
},
itemsPrice: {
type: Number,
required: true,
default: 0.0
},
taxPrice: {
type: Number,
required: true,
default: 0.0
},
shippingPrice: {
type: Number,
required: true,
default: 0.0
},
totalPrice: {
type: Number,
required: true,
default: 0.0
},
orderStatus: {
type: String,
required: true,
default: 'Processing'
},
deliveredAt: {
type: Date
},
createdAt: {
type: Date,
default: Date.now
}
})
module.exports = mongoose.model('Order', orderSchema)
this is my product schema
const mongoose = require('mongoose')
const productSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Please enter product name'],
trim: true,
maxLength: [100, 'Product name cannot exceed 100 characters']
},
price: {
type: Number,
required: [true, 'Please enter product price'],
maxLength: [5, 'Product name cannot exceed 5 characters'],
default: 0.0
},
description: {
type: String,
required: [true, 'Please enter product description'],
},
ratings: {
type: Number,
default: 0
},
images: [
{
public_id: {
type: String,
required: true
},
url: {
type: String,
required: true
},
}
],
category: {
type: String,
required: [true, 'Please select category for this product'],
enum: {
values: [
'Electronics',
'Cameras',
'Laptops',
'Accessories',
'Headphones',
'Food',
"Books",
'Clothes/Shoes',
'Beauty/Health',
'Sports',
'Outdoor',
'Home'
],
message: 'Please select correct category for product'
}
},
seller: {
type: String,
required: [true, 'Please enter product seller']
},
stock: {
type: Number,
required: [true, 'Please enter product stock'],
maxLength: [5, 'Product name cannot exceed 5 characters'],
default: 0
},
numOfReviews: {
type: Number,
default: 0
},
reviews: [
{
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true
},
name: {
type: String,
required: true
},
rating: {
type: Number,
required: true
},
comment: {
type: String,
required: true
}
}
],
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
required: true
},
createdAt: {
type: Date,
default: Date.now
}
})
module.exports = mongoose.model('Product', productSchema);
these are my order controller function
exports.myOrders = catchAsyncErrors(async (req, res, next) => {
const orders = await Order.find({ user: req.user.id })
res.status(200).json({
success: true,
orders
})
})
// Get all orders - ADMIN => /api/v1/admin/orders/
exports.allOrders = catchAsyncErrors(async (req, res, next) => {
const orders = await Order.find({ user: req.user.id })
//let totalAmount = 0;
/* orders.forEach(order => {
totalAmount += order.totalPrice
})
*/
res.status(200).json({
success: true,
// totalAmount,
orders
})
})
the user who initiate the order can see the orders which he initiated but the person to which order was given can not get specific order
I hope i make it clear enough , please help me I am a noob
For user you can use Model.find({ user: user_id });
For seller, you should use Model.find({ user: user_id, seller: seller_id })
This will match orders from the USER for the SELLER

$Geonear does not work in the aggregation pipeline

I've got a tour collection with the following model:
const tourSchema = new mongoose.Schema(
{
slug: {
type: String,
},
name: {
type: String,
required: ['true', 'a tour must have a name'],
unique: true,
trim: true,
maxlength: [40, 'a tour name must not be more than 40 characters'],
minlength: [10, 'a tour must not be less than 10 characters'],
// validate: [validator.isAlpha, 'name should only contain A-Z'],
},
duration: {
type: Number,
required: [true, 'a tour must have a duration'],
},
maxGroupSize: {
type: Number,
required: [true, 'a tour must have a group size'],
},
difficulty: {
type: String,
required: [true, 'a tour should have a difficulty'],
enum: {
values: ['easy', 'medium', 'difficult'],
message: 'Difficulty is either easy, medium or difficult',
},
},
ratingsAverage: {
type: Number,
default: 4.5,
min: [1, 'Rating must be more than 1'],
max: [5, 'Maximum rating is 5'],
set: (val) => Math.round(val * 10) / 10,
},
ratingsQuantity: {
type: Number,
default: 0,
},
price: {
type: Number,
required: ['true', 'a tour must have a price'],
},
priceDiscount: {
type: Number,
validate: {
validator: function (val) {
// this only points to current doc on NEW document creation
return val < this.price;
},
message: 'Discount price {VALUE} should be lower than regualr price',
},
},
summary: {
type: String,
trim: true,
required: ['true', 'a tour must have a description'],
},
description: {
type: String,
trim: true,
},
imageCover: {
type: String,
required: ['true, a tour must have a cover image'],
},
images: [String],
createdAt: {
type: Date,
default: Date.now(),
select: false,
},
startDates: [Date],
secretTour: {
type: Boolean,
default: false,
},
startLocation: {
// GEOJSON
type: {
type: String,
default: 'Point',
enum: ['Point'],
},
coordinates: [Number],
address: String,
description: String,
},
locations: [
{
type: {
type: String,
default: 'Point',
enum: ['Point'],
},
coordinates: [Number],
address: String,
description: String,
day: Number,
},
],
guides: [{ type: mongoose.Schema.ObjectId, ref: 'User' }],
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
I've written an aggregate of type $GeoNear like this:
const distances = await Tour.aggregate([
{
$geoNear: {
near: {
type: 'Point',
coordinates: [lng * 1, lat * 1],
},
distanceField: 'distance',
distanceMultiplier: multiplier,
},
},
]);
I've read the official documentation, and everything else that I could find online, left no stone upturned.
I'm aware that I should have a '2dSphere' index. And it exists on my mongoDB. I've also made sure that this is the first aggregation in the pipeline.
However the results are always empty! this is so frustrating! could be because of this https://github.com/parse-community/parse-server/pull/6540. Any opinion is much appreciated.

Resources