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.
Related
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.
how can I update many orderStatus instead of only one?
request.body.type is by default string and contains only one type;
and when isCompleted for the type go true I want even for previous enum index isCompleted go true
is it possible or do I need to modify it in the front-end?
here is the code
const orderSchema = new mongoose.Schema(
{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
orderStatus: [
{
type: {
type: String,
enum: ["ordered", "packed", "shipped", "delivered"],
default: "ordered",
},
date: {
type: Date,
},
isCompleted: {
type: Boolean,
default: false,
},
},
],
}
exports.updateOrder = (req, res) => {
Order.updateOne(
{ _id: req.body.orderId, "orderStatus.type": req.body.type },
{
$set: {
"orderStatus.$": [
{ type: req.body.type, date: new Date(), isCompleted: true },
],
},
}
).exec((error, order) => {
Hey You can use updateMany() operation
db.collection.updateMany(
<query>,
{ $set: { status: "D" }, $inc: { quantity: 2 } },
...
)
Consider :
EightWeekGamePlan.aggregate(
[
{ $match: { LeadId: { $in: leads }, Week: week,
// total: { $lt: 5 } // This part doesn't work
} },
{
$group: {
_id: {
LeadId: "$LeadId",
total: { $sum: "$TotalClaimsLeftToBeClaimedByClientType" }
}
}
}
]
How can I pick all the documents that their sum of $TotalClaimsLeftToBeClaimedByClientType is less than 5 ?
I've tried with total: { $lt: 5 }but I got an empty array.
Here is the Schema :
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const EightWeekGamePlanSchema = new Schema({
Week: {
type: Number,
required: true
},
LeadId: {
type: String,
required: true
},
PackageId: {
type: String,
required: true
},
BusinessName: {
type: String,
required: true
},
PhoneNumberMasque: {
type: String,
required: true
},
City: {
type: String,
required: true
},
Rooms: {
type: Number,
required: true
},
LeadStartDate: {
type: Date
},
LeadEndDate: {
type: Date
},
TargetedToBeClaimedByClientType: {
type: Number,
required: true
},
TotalClaimsLeftToBeClaimedByClientType: {
// incresed by 1 every time it's claimed
type: Number,
required: true
},
TotalClaimsToBeClaimedByClientType: {
// Stays fixed
type: Number,
required: true
},
Status: {
type: Number,
required: true
},
InsertDate: {
type: Date,
default: Date.now
}
});
module.exports = EightWeekGamePlan = mongoose.model(
"eightweekgameplan",
EightWeekGamePlanSchema
);
From your query, try this :
EightWeekGamePlan.aggregate(
[
{
$match: {
LeadId: { $in: leads }, Week: week
}
},
{
$group: {
_id: {
LeadId: "$LeadId",
total: { $sum: "$TotalClaimsLeftToBeClaimedByClientType" }
}
}
}, {
$match: {
'_id.total': { $lte: 5 }
}
}
])
From the above it $match did not work because your total is not a top level field, it's inside _id. So it's basically grouped based on LeadId + sum of TotalClaimsLeftToBeClaimedByClientType. Just in case if you wanted to group only based on LeadId check below one.
(Or) you can change the query :
EightWeekGamePlan.aggregate(
[
{
$match: {
LeadId: { $in: leads }, Week: week
}
},
{
$group: {
_id: {
LeadId: "$LeadId"
},
total: { $sum: "$TotalClaimsLeftToBeClaimedByClientType" }
}
}, {
$match: {
'total': { $lte: 5 }
}
}
])
userSchema={
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
role: {
type: String
}
}
influencerSchema={
user_id: {
type: Schema.Types.ObjectId,
ref: 'users'
},
profile: {
firstname: {
type: String
},
lastname: {
type: String,
default: null
},
mob: {
type: String,
default: null
},
email: {
type: String,
default: null
},
dob: {
type: Date,
default: null
},
gender: {
type: String,
default: null
}
}
}
campaign_schema={
CampaignName: {
type: String,
required: true
},
Brandcreated: {
type: Schema.Types.ObjectId,
required: true
},
status: {
type: Number,
default: 0
},
influencers: [{
influencerId: {
type: Schema.Types.ObjectId,
ref:"influencer"
},
status: {
type: Number,
default: 0
}
}]
}
The above are 3 schema i.e User , influencer, Campaign schema.
Code use for populate is give below:
function(body) {
return new Promise(function(resolve, reject) {
campaign.find({
"_id": body.campaignId
}).populate('influencer')
.exec(function(err, doc) {
console.log(err);
if (err) {
reject();
} else {
resolve(doc);
}
});
});
}
the result given by above function is
[
{
"status": 0,
"_id": "5bc4a9bf0c67a642d74ab6d1",
"influencers": [
{
"status": 0,
"_id": "5bccc0052db612466d8f26fb",
"influencerId": "5bbef7cd8c43aa1c4e9a09b5"
}
],
"CampaignName": "testCampaign",
"Brandcreated": "5bbf7857a7a55d30426cde37",
"__v": 0
}
]
and the result expecting
[
{
"status": 0,
"_id": "5bc4a9bf0c67a642d74ab6d1",
"influencers": [
{
"status": 0,
"_id": "5bccc0052db612466d8f26fb",
"influencerId": "5bbef7cd8c43aa1c4e9a09b5"
user_id: {}
profile:{}
}
],
"CampaignName": "testCampaign",
"Brandcreated": "5bbf7857a7a55d30426cde37",
"__v": 0
}
]
Can someone told ref use influencers field in campaign schema ,i want to refer that field to user_id in influencer Schema instead of_id field i dont how to do it.
You are using the populate wrong. You actually dont want to populate influencer, but InfluencerId if I understand correctly. Instead of
.populate("influencers")
use
.populate({path: "influencers.influencerId"})
However that will not exacltly turn out the way you want to. as it will resolve to something like
"influencers" : [
"status" : 0,
"influencerId" : {
//content of influencer
}
]
If you want your the result as you stated you need to map the array afterwards.
I have a Mongoose model called Session with a field named course (Course model) and I want to perform full text search on sessions with full text search, also I wanna aggregate results using fields from course sub field and to select some fields like course, date, etc.
I tried the following:
Session.aggregate(
[
{
$match: { $text: { $search: 'web' } }
},
{ $unwind: '$course' },
{
$project: {
course: '$course',
date: '$date',
address: '$address',
available: '$available'
}
},
{
$group: {
_id: { title: '$course.title', category: '$course.courseCategory', language: '$course.language' }
}
}
],
function(err, result) {
if (err) {
console.error(err);
} else {
Session.deepPopulate(result, 'course course.trainer
course.courseCategory', function(err, sessions) {
res.json(sessions);
});
}
}
);
My models:
Session
schema = new mongoose.Schema(
{
date: {
type: Date,
required: true
},
course: {
type: mongoose.Schema.Types.ObjectId,
ref: 'course',
required: true
},
palnning: {
type: [Schedule]
},
attachments: {
type: [Attachment]
},
topics: {
type: [Topic]
},
trainer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'trainer'
},
trainingCompany: {
type: mongoose.Schema.Types.ObjectId,
ref: 'training-company'
},
address: {
type: Address
},
quizzes: {
type: [mongoose.Schema.Types.ObjectId],
ref: 'quiz'
},
path: {
type: String
},
limitPlaces: {
type: Number
},
status: {
type: String
},
available: {
type: Boolean,
default: true
},
createdAt: {
type: Date,
default: new Date()
},
updatedAt: {
type: Date
}
},
{
versionKey: false
}
);
Course
let schema = new mongoose.Schema(
{
title: {
type: String,
required: true
},
description: {
type: String
},
shortDescription: {
type: String
},
duration: {
type: Duration
},
slug: {
type: String
},
slugs: {
type: [String]
},
program: {
content: {
type: String
},
file: {
type: String
}
},
audience: [String],
requirements: [String],
language: {
type: String,
enum: languages
},
price: {
type: Number
},
sections: [Section],
attachments: {
type: [Attachment]
},
tags: [String],
courseCategory: {
type: mongoose.Schema.Types.ObjectId,
ref: 'course-category',
required: true
},
trainer: {
type: mongoose.Schema.Types.ObjectId,
ref: 'trainer'
},
trainingCompany: {
type: mongoose.Schema.Types.ObjectId,
ref: 'training-company'
},
status: {
type: String,
default: 'draft',
enum: courseStatus
},
path: {
type: String
},
cover: {
type: String,
required: true
},
duration: {
type: Number,
min: 1
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date
}
},
{ versionKey: false }
);
I am not sure if what I tried is gonna bring me what I want and I am getting this error concerning the $unwind operator:
MongoError: exception: Value at end of $unwind field path '$course'
must be an Array, but is a OID
Any kind of help will be really appreciated.
You can try below aggregation.
You are missing $lookup required to pull course document by joining on course object id from session document to id in the course document.
$project stage to keep the desired fields in the output.
Session.aggregate([
{
"$match": {
"$text": {
"$search": "web"
}
}
},
{
"$lookup": {
"from": "courses",
"localField": "course",
"foreignField": "_id",
"as": "course"
}
},
{
"$project": {
"course": 1,
"date": 1,
"address": 1,
"available": 1
}
}
])
Course is an array with one course document. You can use the $arrayElemAt to project the document.
"course": {"$arrayElemAt":["$course", 0]}