I have two model one is productModel.js and another is reviewModel.js. I did child referencing in reviewSchema. product Schema has ratingsAverage and ratingsQuantity property. I want to manipulate these properties of properties inside from reviewModel.js. So that I have used statics method on reviewSchema.
reviewModel.js:
const Product = require("./productModel");
const { Schema } = require("mongoose");
const mongoose = require("mongoose");
const reviewSchema = new Schema({
review: {
type: String,
},
rating: {
type: Number,
min: 1,
max: 5,
},
createdAt: {
type: Date,
default: Date.now(),
},
product: {
type: mongoose.Schema.ObjectId,
ref: "Product",
required: [true, "A review must belong to a tour."],
},
user: {
type: mongoose.Schema.ObjectId,
ref: "User",
required: [true, "A review must belong to a user"],
},
});
// Preventing from duplication of review on same tour from same user
// Combined index
reviewSchema.index({ product: 1, user: 1 }, { unique: true });
// Creating calcAverageRating() static method for calculating average rating and number of ratings on product. It should work on creating, updating and deleting of review.
reviewSchema.statics.calcAverageRating = async function (productId) {
// "this" represents here model
const stats = await this.aggregate([
{
$match: { product: productId },
},
{
$group: {
_id: "$product",
nRating: { $sum: 1 },
avgRating: { $avg: "$rating" },
},
},
]);
console.log(stats, "rrrr");
// console.log(Product, "product");
// Problem is coming here i am not able to access productModel
// const doc = await Product.findByIdAndUpdate(
// productId,
// {
// ratingsAverage: stats[0],
// ratingsQuantity: stats[0],
// },
// {
// new: true,
// runValidators: true,
// }
// );
};
// As you know static methods only call on Model so we have to think about how to call calcAverageRating() static method. problem is that we have to get this product id which one is used to creating review so we only get this id on document middleware("post") for creating and for updating and deleting we will get from get from query middleware(/^findOne/).
// Document middleware("save")
reviewSchema.post("save", function () {
this.constructor.calcAverageRating(this.product);
});
reviewSchema.post(/^findOneAnd/, async function () {
// console.log(await this.findOne());
this.r = await this.clone().findOne();
console.log(this.r, "query");
console.log(Product, "testing");
});
reviewSchema.post(/^findOneAnd/, function () {
this.r.constructor.calcAverageRating(this.r.product);
});
// reviewSchema.pre(/^find/, function (next) {
// this.populate({
// path: "product",
// select: "name",
// });
// next();
// });
// CREATING MODEL
const Review = mongoose.model("Review", reviewSchema);
// We can call here static method but we will not get productId so we have to call this static method before creating of Model.
// Review.calcAverageRating();
// EXPORTING
module.exports = Review;
productModel.js:
const mongoose = require("mongoose");
const { Schema } = require("mongoose");
const Review = require("./reviewModel");
// CREATING SCHEMA
const productSchema = new Schema(
{
name: {
type: String,
required: [true, "Please enter product name"],
unique: true,
trim: true,
},
slug: String,
price: {
type: Number,
required: [true, "A product must have a price"],
maxlength: [8, "Price cannot exceeds 8 characters"],
},
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 below regular price",
},
},
stock: {
type: Number,
required: [true, "Please Enter product Stock"],
maxLength: [4, "Stock cannot exceed 4 characters"],
default: 1,
},
summary: {
type: String,
required: [true, "A product must have a summary"],
},
description: {
type: String,
required: [true, "A product have a description"],
},
images: [
{
public_id: {
type: String,
required: [true, "Images public id is required"],
},
url: {
type: String,
required: [true, "Images url is required"],
},
},
],
ratingsAverage: {
type: Number,
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,
},
category: {
type: String,
required: [true, "A product must belong to a category"],
},
createdAt: {
type: Date,
default: Date.now(),
},
// ratingsAverage: {
// type: Number,
// default: 4.5,
// min: [1, "Rating must be above 1.0"],
// max: [5, "Rating must be below 5.0"],
// // Declaring custom function for do some opeartion from get value
// // set: (val) => Math.round(val * 10) / 10, // 4.666666, 46.6666, 47, 4.7
// set: (val) => Math.round(val * 10) / 10,
// },
// ratingsQuantity: {
// type: Number,
// default: 0,
// },
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
productSchema.index({ name: "text", description: "text" });
// VIRTUAL POPULATE
productSchema.virtual("reviews", {
ref: "Review",
foreignField: "product",
localField: "_id",
});
// CREATING MODEL
const Product = mongoose.model("Product", productSchema);
// EXPORTING
module.exports = Product;
Related
I'm using mongoose-paginate-v2 for my TransactionModel.
I can't seem to filter by the user name or email.
If I don't use any query the docs return with the populated objects, like the user's name, and email, so I believe the issue is with the filter variable.
I want to keep using mongoose-paginate-v2
const transactionSchema: Schema = new Schema(
{
user: { type: Types.ObjectId, ref: CollectionNames.user, required: false },
subscription: { type: Types.ObjectId, ref: CollectionNames.subscription, required: false },
paidAt: { type: Date, required: true, default: new Date() },
amount: { type: Number, required: true },
currency: { type: String, required: true, default: CurrencyCodes.USD },
status: { type: String, required: true, default: TransactionStatuses.pending },
refundedAt: { type: Date, required: false },
refundAmount: { type: Number, required: false },
provider: { type: String, required: true },
providerTransactionId: { type: String, required: true },
invoiceLink: { type: String, required: false },
referral: { type: referralSchema, required: false },
eventsHistory: { type: [String], required: false, default: [] }
},
{
timestamps: true
}
);
// The function body:
const { page, limit, query: searchQuery } = query;
const options = {
page: page || 1,
limit: limit || 10,
sort: {
createdAt: -1
},
populate: [
{
path: 'user',
select: 'name socialAccounts email lastLoginEmail notificationEmail'
},
{
path: 'referral',
select: 'user commission payoutExpectedAt paidAt status'
}
],
lean: true
};
const filter = {};
if (searchQuery) {
const searchQueryRegex = new RegExp(searchQuery, 'i');
Object.assign(filter, {
$or: [
{
providerTransactionId: {
$regex: searchQueryRegex
}
},
{
'user.name': {
$regex: searchQueryRegex
}
},
{
'user.email': {
$regex: searchQueryRegex
}
},
]
});
}
const { docs, totalDocs, totalPages, hasNextPage } = await TransactionModel.paginate(filter, options);
hi i have this mongose models
new mongoose.Schema({
type_order: {
type: String,
required: true,
},
article: {
required: true,
type: [{
id: {type: mongoose.Schema.Types.ObjectId, ref: 'product'},
type_article: String,
quanty: Number,
}],
},
status: {
required: true,
type: String,
},
profile_id: {
required: true,
type: mongoose.Schema.Types.ObjectId,
ref: 'customer',
},
}, {timestamps: true});
module.exports = mongoose.model('order', dataSchema);
const dataSchema = new mongoose.Schema({
profile_id: {
type: String,
required: true,
},
title_lower: {
required: true,
type: String,
},
title: {
required: true,
type: String,
},
category_id: {
required: true,
type: String,
},
status: {
required: true,
type: String,
},
brand: {
required: true,
type: String,
},
description: {
required: true,
type: String,
},
stock: {
required: true,
type: Number,
},
price: {
required: true,
type: Number,
},
discount: {
required: true,
type: Number,
},
images: {
type: [{
url: String,
first: Boolean,
}],
},
}, {timestamps: true});
module.exports = mongoose.model('product', dataSchema);
I am trying to obtain the orders that have in their article field an article or several that belong to the profile id of the product that would be the person who published that product.
I am currently doing it bringing all the orders for the person's products but I know that it is not the most optimal way to do it and in the long run the performance will be affected.
i am trying something like this:
const profile = await customer.findOne({userId: req.user.id});
if (profile) {
// eslint-disable-next-line max-len
const articles = await Product.find({profile_id: profile._id});
const response=[];
const preresponse=[];
for (let index = 0; index < articles.length; index++) {
const element = articles[index];
const orders= await getOrdersByArticleId(element._id, res);
preresponse.push({
'article_id': element._id,
'title': element.title,
'price': element.price,
'stock': element.stock,
orders,
});
articles[index].orders = orders;
}
preresponse.forEach((art)=> {
art.orders.forEach((order)=>{
console.log(order);
const result = response.find(({order_id}) => order_id == order.order_id); ;
console.log(result);
if (result) {
console.log('holaa');
} else {
response.push({
'order_id': order.order_id,
'type': order.type_order,
'status': order.status,
'date': order.date.toDateString(),
'articles': [{'product_id': order.article_id,
'quanty': order.quanty}],
});
}
});
});
return res.status(200).json(articles);
}
I am creating a search API and using the $regex operator of MongoDB to filter from search text. But I also want to add a category inside the $or operator. I have a category Id and this is an id of another collection whenever I want to get the category I have to populate the category id. But I want to use the same $regex operator on the category as given below.
Category Schema
const mongoose = require('mongoose');
const categorySchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
slug: {
type: String,
required: true,
unique: true
},
type: {
type: String
},
categoryImage: {
type: String
},
parentId: {
type: String
}
}, {
timestamps: true
})
module.exports = mongoose.model('Category', categorySchema);
Product Shema
const mongoose = require("mongoose");
const productSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
trim: true,
},
slug: {
type: String,
required: true,
unique: true,
},
price: {
type: Number,
required: true,
},
quantity: {
type: Number,
required: true,
},
description: {
type: String,
required: true,
trim: true,
},
offer: {
type: Number,
},
productPictures: [{ img: { type: String } }],
views: {
type: Number,
min: 0,
default: 0,
required: true
},
ratings: [
{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
rate: {
type: Number,
required: true,
max: 5,
},
reviewTitle: {
type: String,
},
review: {
type: String,
},
reviewImages: [
{
img: String,
},
],
date: {
type: Date,
required: true,
}
},
],
category: {
type: mongoose.Schema.Types.ObjectId,
ref: "Category",
required: true,
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
},
{
timestamps: true,
}
);
module.exports = mongoose.model("Product", productSchema);
Search Product Controller
exports.searchProducts = async (req, res) => {
try {
const { page = 1, limit = 20 } = req.query;
const pagination = {
skip: (Number(page) - 1) * Number(limit),
limit: Number(limit),
};
const search = req?.query?.s?.split("+").join("") || "";
const fields = req?.query?.fields?.split(",").join(" ");
const sort = req?.query?.sort?.split(",")?.join(" ");
const { products, matchCount, pageNeeded, showingFrom, showingTo } =
await searchProductsService(search, fields, sort, pagination);
if (products.length === 0) {
return res.status(400).json({ error: "No products found" });
}
res
.status(200)
.json({ products, matchCount, pageNeeded, showingFrom, showingTo });
} catch (error) {
return res.status(400).json({ error: "Something Wen't wrong" });
}
};
Search Product Service (Where I am doing database opearations)
exports.searchProductsService = async (search, fields, sort, pagination) => {
await SearchQuery.updateOne(
{ title: search },
{ $inc: { searchedCount: 1 } },
{ upsert: true }
);
const query = {
$or: [
{ name: { $regex: search, $options: "i" } },
{ description: { $regex: search, $options: "i" } },
],
};
const products = await Product.find(query)
.skip(pagination.skip)
.limit(pagination.limit)
.select(fields || "name productPictures price")
.sort(sort);
const matchCount = await Product.find(query).count();
const pageNeeded = Math.ceil(matchCount / pagination.limit);
const showingFrom = pagination.skip + 1;
const showingTo = pagination.skip + products.length;
return { products, matchCount, pageNeeded, showingFrom, showingTo };
};
These are the schema from mongoose subdocuments example.
let classTypeSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
abilities: {
type: Array,
default: []
},
skills: {
type: Array,
default: []
}
});
let heroSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true,
minlength: 3,
maxlength: 24
},
classType: [classTypeSchema],
level: {
type: Number,
default: 1
},
currency: {
type: Number,
default: 0
}
});
I tried this way
Hero = mongoose.model('Hero',heroSchema);
console.log(typeof Hero);
console.log(JSON.stringify(Hero, null, 4));
Output
function
undefined
Console.dir gives very detailed output.I am interested only in schemas part
subpaths: {
'classType.name': [SchemaString],
'classType.abilities': [SchemaArray],
'classType.skills': [SchemaArray],
'classType._id': [ObjectId],
'classType.abilities.$': [Mixed],
'classType.skills.$': [Mixed]
},
Is there any other way to print Mongoose properties and methods?
I have added a virtual environment for reviews but for some reason it doesn't appear in postman when i try to get it. If worked correctly it should display reviews with a value of null. I am fairly new to this but i did read through the mongoose documentation and other online sources but everything seems similar to what i wrote.
const mongoose = require('mongoose');
const slugify = require('slugify');
const tourSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Name required'],
unique: true,
maxlength: [40, 'Less then or equal to 40 characters'],
minlength: [10, 'More then or equal to 10 characters'],
},
slug: String,
duration: {
type: Number,
required: [true, 'Duration required'],
},
maxGroupSize: {
type: Number,
required: [true, 'Group size required'],
},
difficulty: {
type: String,
required: [true, 'Difficulty required'],
enum: {
values: ['easy', 'medium', 'difficult'],
message: 'Difficulty is either easy, medium, difficult',
},
},
ratingsAverage: {
type: Number,
default: 4.5,
max: [5, 'Less than or equal to 5'],
min: [1, 'More than or equal to 1'],
},
ratingsQuantity: {
type: Number,
default: 0,
},
price: {
type: Number,
required: [true, 'Price required'],
},
priceDiscount: {
type: Number,
validate: {
validator: function (val) {
// This only points to current doc on New doc creating
return val < this.price ? true : false;
},
message: 'Discount should be less than the regular price',
},
},
summary: {
type: String,
trim: true,
required: [true, 'Summary required'],
},
description: {
type: String,
trim: true,
},
imageCover: {
type: String,
required: [true, 'Image required'],
},
images: [String],
createAt: {
type: Date,
default: Date.now(),
},
startDates: [Date],
secretTour: {
type: Boolean,
default: false,
},
startLocation: {
// GeoJSON (At least 2 field names e.g. type and coordinates)
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('durationWeeks').get(function () {
return this.duration / 7;
});
// Virtual populate
tourSchema.virtual('reviews', {
ref: 'Review',
foreignField: 'tour',
localField: '_id',
});
Here is the controller for the tour
const Tour = require('../models/tourModel');
const APIFeatures = require('../utils/apiFeatures');
const catchAsync = require('../utils/catchAsync');
const AppError = require('../utils/appError');
const {
deleteOne,
updateOne,
createOne
} = require("./handlerFactory")
const {
populate
} = require('../models/tourModel');
const aliasTopTours = catchAsync(async (req, res, next) => {
req.query.limit = '5';
req.query.sort = '-ratingAverage,price';
req.query.fields = 'name, price, ratingAverage, summary, difficulty';
next();
});
const getAllTours = catchAsync(async (req, res, next) => {
// Execute query
const features = new APIFeatures(Tour.find(), req.query)
.filter()
.sort()
.limit()
.pagination();
const tours = await features.query;
// Send response
res.status(200).json({
status: 'Success',
length: tours.length,
message: tours,
});
});
const getTour = catchAsync(async (req, res, next) => {
const tourId = await (await Tour.findById(req.params.Id)).populate('reviews');
if (!tourId) {
return next(new AppError('No tour found with that ID', 404));
}
res.status(200).json({
status: 'Success',
data: {
tourId,
},
});
});
const postTour = createOne(Tour)
const patchTour = updateOne(Tour)
const deleteTour = deleteOne(Tour)
const getTourStats = catchAsync(async (req, res, next) => {
const stats = await Tour.aggregate([{
$match: {
ratingAverage: {
$gte: 4.5,
},
},
},
{
$group: {
_id: '$difficulty',
aveRating: {
$avg: '$ratingAverage',
},
avePrice: {
$avg: '$price',
},
minPrice: {
$min: '$price',
},
maxPrice: {
$max: '$price',
},
totalRating: {
$sum: '$ratingQuantity',
},
totalTours: {
$sum: 1,
},
},
},
{
$sort: {
avePrice: 1,
},
},
]);
res.status(200).json({
status: 'Success',
message: stats,
});
});
const getMonthlyPlan = catchAsync(async (req, res, next) => {
const year = req.params.year * 1;
const plan = await Tour.aggregate([{
$unwind: '$startDates',
},
{
$match: {
startDates: {
$gte: new Date(`${year}-01-01`),
$lte: new Date(`${year}-12-31`),
},
},
},
{
$group: {
_id: {
$month: '$startDates',
},
numTourStarts: {
$sum: 1,
},
tours: {
$push: '$name',
},
},
},
{
$addFields: {
month: '$_id',
},
},
{
$project: {
_id: 0,
},
},
{
$sort: {
numTourStarts: -1,
},
},
{
$limit: 12,
},
]);
res.status(200).json({
status: 'Success',
length: plan.length,
message: plan,
});
});
module.exports = {
getAllTours,
getTour,
postTour,
patchTour,
deleteTour,
aliasTopTours,
getTourStats,
getMonthlyPlan,
};
const tourId = await (await Tour.findById(req.params.Id)).populate('reviews');
The problem is here, you need to remove one await here, no need to use 2 await in this line