Mongoose: Populate nested array of objectIds - node.js

I have a Usermodel the schema is as below, I'm trying to populate the subcategories in the deals and the wishes but not able to.
image: { type: String, default: 'NA' },
firstName: { type: String, default: 'first name' },
lastName: { type: String, default: 'last name' },
email: { type: String, lowercase: true, unique: true, trim: true },
password: { type: String, min: 6 },
deals: [
{
_id : false,
category: { type: Schema.Types.ObjectId, ref: 'Category' },
subCategory: [{ type: Schema.Types.ObjectId, ref: 'Subcategory' }]
}
],
wishes: [
{
_id : false,
category: { type: Schema.Types.ObjectId, ref: 'Category' },
subCategory: [{ type: Schema.Types.ObjectId, ref: 'Subcategory' }]
}
]
Here is a sample JSON response
"deals": [
{
"subCategory": [
"5bc419cea25fc606153bdbe4",
"5bc419cea25fc606153bdbe3"
],
"category": "5bc419cea25fc606153bdbe2"
}
],
"wishes": [
{
"subCategory": [
"5bc41a40a25fc606153bdbe5",
"5bc41a40a25fc606153bdbe7"
],
"category": "5bc41a40a25fc606153bdbe5"
}
]
I am able to populate the deals.category but not deals.subcategory
// method to get users with populated deals and wishes
allUsers: async (req, res, next) => {
const users = await User.find({})
.populate('deals.category', 'id name')
.populate('wishes.category', 'id name')
.populate('deals.subCategory');
return respondSuccess(res, null, users);
},
// currently the response i'm getting
"deals": [
{
"subCategory": [],
"category": {
"_id": "5bc419cea25fc606153bdbe2",
"name": "Auto"
}
}
],
"wishes": [
{
"subCategory": [
"5bc41a40a25fc606153bdbe5",
"5bc41a40a25fc606153bdbe7"
],
"category": {
"_id": "5bc41a40a25fc606153bdbe5",
"name": "Baby Sitting"
}
}
]
looking for much-needed help.
thank you

Related

Api to list ordered product count based on date using node js and mongoDB express

Node.js:
how to create api for list ordered product count based on date .this order model and how to i create the controller for that...
const mongoose = require('mongoose')
const schema = mongoose.Schema
const orderSchema = new schema({
orderID:{type:String,required:true},
customer: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'userdatas',
},
orderItems:
{
// productName: { type: String, required: true },
// qty: { type: Number, required: true },
// amount: { type: String, required: true },
product: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'productsdatas',
},
},
shippingAddress : {
address: { type: String, required: true },
city: { type: String, required: true },
postalCode : { type: String, required: true },
country: { type: String, required: true },
},
orderDate:{type: String,
required: true},
totalAmount: {
type: Number,
required: true,
default: 0.0,
},
},
{
timestamps: true,
}
)
const Order = mongoose.model('Order', orderSchema)
module.exports = Order
mongoDB:
this is mongoDB data for order i should already created and customer and product details i populateded to show the output that time..
{
"_id": {
"$oid": "630f47d44a772822d2370794"
},
"orderID": "1",
"customerName": "vikiram",
"customer": {
"$oid": "630ccd62121a7425d6918e95"
},
"orderItems": {
"product": {
"$oid": "630d95d659998496b12f4f36"
}
},
"shippingAddress": {
"address": "123 IT street",
"city": "chennai",
"postalCode": "6000100",
"country": "india"
},
"orderDate": "25/04/2022",
"totalAmount": 11700000,
"createdAt": {
"$date": {
"$numberLong": "1650886612732"
}
},
"updatedAt": {
"$date": {
"$numberLong": "1661945812732"
}
},
"__v": 0
}
json:
i want json output in postman like this..
{
"data" : {
"May-2019" : 1,
"January-2020" : 1,
"February-2020" : 2,
"April-2020" : 1
}
}
You can achieve it by using aggregation. https://www.mongodb.com/docs/manual/aggregation/

Mongoose calculating average from another collection

I am new to MongoDB/Mongoose. I am trying to do a search based on a key string for a 'Resource' which will return a list of resources based on average of ratings for that resource. I am having a hard time calculating and returning the average. This is my schema.
Resource Schema:
const ResourceSchema = mongoose.Schema({
title: {
type: String,
required: true,
},
type: {
type: String,
required: true,
},
url: {
type: String,
required: true,
},
createdDate: {
type: Date,
default: Date.now,
},
});
module.exports = mongoose.model("Resource", ResourceSchema);
Rating Schema:
const RatingSchema = mongoose.Schema({
resourceId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Resource",
},
createdDate: {
type: Date,
default: Date.now,
},
rating: {
type: Number,
required: true,
min: 1,
max: 5,
},
review: {
type: String,
required: true,
},
});
module.exports = mongoose.model("Rating", RatingSchema);
Each Resource will have multiple Ratings. I am trying to calculate the average of ratings in my list of fetched Resources and add it to the search results.
This is what I have for my search:
Resource.find({
$or: [
{ title: { $regex: req.params.searchStr.toLowerCase(), $options: "i" } },
{ url: { $regex: req.params.searchStr.toLowerCase(), $options: "i" } },
],
})
Here's one way you could do it.
db.resources.aggregate([
{ // filter resources
"$match": {
"title": {
"$regex": "passenger",
"$options": "i"
},
"url": {
"$regex": "https",
"$options": "i"
}
}
},
{ // get ratings for resource
"$lookup": {
"from": "ratings",
"localField": "_id",
"foreignField": "resourceId",
"pipeline": [
{
"$project": {
"_id": 0,
"rating": 1
}
}
],
"as": "ratings"
}
},
{ // calculate average
"$set": {
"avgRating": { "$avg": "$ratings.rating" }
}
},
{ // don't need ratings array anymore
"$unset": "ratings"
}
])
Try it on mongoplayground.net.

MongoDB: How to aggregate and $group then filter specific date

Employee Schema
const employeeSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
trim: true,
},
email: {
type: String,
unique: true,
required: true,
trim: true,
lowercase: true,
validate(value) {
if (!validator.isEmail(value)) {
throw new Error('Email is invalid');
}
},
},
password: {
type: String,
required: true,
trim: true,
minLength: 6,
validate(value) {
if (value.toLowerCase().includes('password')) {
throw new Error("Password can not contain a word 'password'.");
}
},
},
birthdate: {
type: Date,
required: true,
},
cellphone: {
type: String,
required: true,
trim: true,
},
gender: {
type: String,
enum: ['남성', '여성'],
required: true,
},
hourly_wage: {
type: Number,
trim: true,
default: 0,
},
timeClocks: [
{
type: new mongoose.Schema({
start_time: {
type: Date,
required: true,
},
end_time: {
type: Date,
},
wage: {
type: Number,
required: true,
},
total: {
type: Number,
},
totalWorkTime: {
type: Number
}
}),
},
],
role: {
type: String,
enum: ['staff'],
default: 'staff',
},
stores: [
{
location: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'Location',
},
},
],
status: {
//현재 재직상태
type: String,
enum: ['재직자', '퇴직자'],
default: '재직자',
},
tokens: [
{
token: {
type: String,
required: true,
},
},
],
},
{
timestamps: true,
}
);
What I have done so far
const employees = shifts.map((d) => d.owner._id);
//timeclock
const temp = await Employee.aggregate([
{
$match: {
_id: { $in: employees },
},
},
{
$sort: { 'timeClocks.start_time': 1 },
},
{
$unwind: { path: '$timeClocks', preserveNullAndEmptyArrays: true },
},
{
$group: {
_id: '$_id',
name: { $first: '$name' },
timeClock: {
$push: '$timeClocks',
},
},
},
]);
My result
{
"shifts": [
{
"_id": "60e05b188be53900280bcdf2",
"date": "2021-07-09T00:00:00.000Z",
"day": "Fri",
"start": "2021-07-09T09:41:00.000Z",
"end": "2021-07-09T21:42:00.000Z",
"owner": {
"_id": "60cd9a3cb4ddcc00285b0df9",
"name": "Dr. dd"
},
"location": "60cd99b1b4ddcc00285b0df3",
"__v": 0
}
],
"timeClock": [
{
"_id": "60cd9a3cb4ddcc00285b0df9",
"name": "Dr. dd",
"timeClock": [
{
"_id": "60def63d19648a00286f0539",
"start_time": "2021-05-04T02:19:00.000Z",
"end_time": "2021-05-04T14:42:00.000Z",
"wage": 8720,
"total": 107735,
"totalWorkTime": 743
},
{
"_id": "60def63f19648a00286f053d",
"start_time": "2021-05-02T08:12:00.000Z",
"end_time": "2021-05-02T22:24:00.000Z",
"wage": 8720,
"total": 123540,
"totalWorkTime": 852
},
{
"_id": "60def64119648a00286f0541",
"start_time": "2021-05-10T20:14:00.000Z",
"end_time": "2021-05-10T22:17:00.000Z",
"wage": 8720,
"total": 17835,
"totalWorkTime": 123
},
}
]
Expected Result(2021-05-10)
{
"shifts": [
{
"_id": "60e05b188be53900280bcdf2",
"date": "2021-07-09T00:00:00.000Z",
"day": "Fri",
"start": "2021-07-09T09:41:00.000Z",
"end": "2021-07-09T21:42:00.000Z",
"owner": {
"_id": "60cd9a3cb4ddcc00285b0df9",
"name": "Dr. dd"
},
"location": "60cd99b1b4ddcc00285b0df3",
"__v": 0
}
],
"timeClock": [
{
"_id": "60cd9a3cb4ddcc00285b0df9",
"name": "Dr. dd",
"timeClock": {
"_id": "60def64119648a00286f0541",
"start_time": "2021-05-10T20:14:00.000Z",
"end_time": "2021-05-10T22:17:00.000Z",
"wage": 8720,
"total": 17835,
"totalWorkTime": 123
},
}
]
I am receiving the 'date string' example('URL/2021-05-10') via params and trying to query all employees that have the same date timeClocks.
also trying to send back everything I queried without different dates from timeClocks.
How can I filter out non-same dates?
You have string 2021-05-10 now you need a $match stage before your group so you can filter out timeClock. Something like:
{ $match: { 'timeClocks.start_time': new Date('2021-05-10') } }
Modify the match stage to your requirements like maybe add $gte or $lte or something like that.

aggregate nested array of objects using mongoose

I have the following model and I want to query a specific user on _id field and populate the inbox.messages array with the necessary data that matches the corresponding _id field in the users model and more importantly i also want to group each message by the 'from' field and return that result
const UserSchema = new Schema({
username: {
type: String,
required: true,
},
blockedUsers: {
users: [
{
userId: {type: Schema.Types.ObjectId, ref: 'User', required: true },
}
]
},
favorites: {
users: [
{
userId: {type: Schema.Types.ObjectId, ref: 'User', required: true },
}
]
},
profileViews: {
views: [
{
userId: {type: Schema.Types.ObjectId, ref: 'User', required: true },
date: {type: Date}
}
]
},
inbox: {
messages: [
{
messageId: {type: Schema.Types.ObjectId},
from: {type: Schema.Types.ObjectId, ref: 'User', required: true },
content: {type: String, required: true},
date: {type: Date}
}
]
},
images: {
"imagePaths": [
{
imageId: {type: Schema.Types.ObjectId},
path: { type: String, required: true},
date: {type: Date}
}
],
}
})
what I have so far
let incomingId = '5e29fd75fdfd5320d0e42bc4';
let myUser = await User.aggregate([
{ $match: {"_id": mongoose.Types.ObjectId(incomingId) }},
{ $lookup: { }}
])
Not sure exactly what to put in the $lookup field or if this is even correct.
As a sample I would like the documents to look like:
[
{
"from": "5e240f7480a24e07d832c7bd",
"username":"hable0",
"images": {
imagePaths: [
'images/2020-09-24-Z_34234342_12.jpg'
],
},
"inbox": {
"messages": [
{
"messageId": "5e2a110a21c64d63f451e39e",
"content": "Message content",
"date": "2020-01-23T21:32:58.126Z"
},
{
"messageId": "5e2a111321c64d63f451e3a0",
"content": "Message content",
"date": "2020-01-23T21:33:07.378Z"
},
{
"messageId": "5e2a112321c64d63f451e3a2",
"content": "Message content",
"date": "2020-01-23T21:33:23.036Z"
}
]
}
}
]
You could try the following pipeline with aggregate().
Find the document that matches the id
Unwind inbox.messages
Group by from field
Perform a $lookup to get another document
Perform a $unwind to destruct the array
Specify fields to be included in the output
let myUser = await User.aggregate([
{
$match: { "_id": mongoose.Types.ObjectId(incomingId) }
},
{
$unwind: "$inbox.messages"
},
{
$group: {
_id: { from: "$inbox.messages.from" },
messages: {
$push: {
messageId: "$inbox.messages.messageId"
// Add more info of the message here as needed
}
}
},
},
{
$lookup: {
from: "User",
localField: "_id.from",
foreignField: "_id",
as: "extraUserInfo"
}
},
{
$unwind: "$extraUserInfo"
},
{
$project: {
_id: 0,
from: "$_id.from",
inbox: { messages: "$messages" },
username: "$extraUserInfo.username",
images: "$extraUserInfo.images"
}
}
]);
Sample output:
{
"from": "user1",
"inbox": {
"messages": [{
"messageId": "message1-from-user1"
}]
},
"username": "user1-username",
"images": {
"imagePaths": ["image-path-user1"]
}
} {
"from": "user2",
"inbox": {
"messages": [{
"messageId": "message1-from-user2"
}, {
"messageId": "message2-from-user2"
}, {
"messageId": "message3-from-user2"
}]
},
"username": "user2-username",
"images": {
"imagePaths": ["image-path-user2"]
}
} {
"from": "user3",
"inbox": {
"messages": [{
"messageId": "message1-from-user3"
}, {
"messageId": "message2-from-user3"
}]
},
"username": "user3-username",
"images": {
"imagePaths": ["image-path-user3"]
}
}
Hope this answers part of your question. Though I'm not very clear how you would like to populate the messages array with the user info who sent the messages. But you can perform a $lookup() with a pipeline after $group() operation to attach additional info from the sender to the result.
Read more about $unwind, $group, $project and $lookup.

MongoDB: A complex query with array input

I'm stuck at finding a solution for the following query.
1) a user can select many categories and subcategories.
2) the user can see all other users how are selected the same categories and subcategories within a certain radius.
Here is the Schema of the user
const userSchema = new Schema(
{
image: { type: String, default: 'NA' },
firstName: { type: String, default: 'first name' },
lastName: { type: String, default: 'last name' },
email: { type: String, lowercase: true, unique: true, trim: true },
password: { type: String, min: 6 },
gender: { type: String, emun: ['male','female','other'] },
about: { type: String, default: 'about you' },
address: {
zipCode: { type: Number, default: 000000 },
place: { type: String, default: 'place' },
street: { type: String, default: 'street' },
country: { type: String, default: 'Country' },
location: {
type: { type: String, default:'Point'},
coordinates: { type:[Number], index:'2dsphere', default:[0,0] }
}
},
interests: [
{
_id : false,
category: {
id: { type: Schema.Types.ObjectId, ref: 'Category' },
name: { type: String }
},
subCategory: [
{
_id : false,
id: { type: Schema.Types.ObjectId, ref: 'Subcategory' },
name: { type: String }
}
]
}
]
}
);
In my controller here is what I tried
homeData: async (req, res, next) => {
const limit = Number(req.params.limit);
const { latitude, longitude, minDistance, maxDistance } = getUserCurrentLocation(req);
const usersWithSameInterests = await User.aggregate([
{
"$geoNear": {
"near": {
"type": "Point",
"coordinates": [longitude, latitude]
},
"distanceField": "distance",
"minDistance": minDistance,
"maxDistance": maxDistance,
"spherical": true,
"query": { "location.type": "Point" }
}
},
{
"$match": { "interests": { "$elemMatch": {name: 'Sports'} }} // hard coded for testing
},
{ "$sort": { "distance": 1 } },
{ "$limit" : limit },
{
"$project": {
"_id": 1,
"image": 1,
"firstName":1,
"lastName":1,
"distance": 1,
"createdAt": 1
}
}
]);
return respondSuccess(res, null, {
newNotification: false,
usersWithSameInterests: usersWithSameInterests
});
},
The response i'm getting is
{
"success": true,
"message": "query was successfull",
"data": {
"newNotification": false,
"usersWithSameInterests": []
}
}
Sample categories and subcategories
Category: Sports
Subcategories: Cricket, Football, Hockey, Tennis
Category: Learning Languages
Subcategories: English, German, Spanish, Hindi
looking forward for much-needed help.
thank you.
It seems that you have a few mismatched columns.
On the $geonear pipeline, the line "query": { "location.type": "Point" } should be: 'query': {'address.location.type': 'Point'}.
And on the $match pipeline, the line { "interests": { "$elemMatch": {name: 'Sports'} } should be 'interests': { '$elemMatch:' {'category.name': 'Sports'} }
Edit:
To match multiple interests on the category and subcategory field, You can use the $in operator on the $match pipeline. Like this:
{
'interests.category.name': { $in: ['Sports'] },
'interests.subCategory.name': {$in: ['Soccer']}
}
It'll return anyone that have Sports in the category name, and Soccer on subcategory name.

Resources