Limit in mongoose for one query - javascript

My schema:
export const MessagesAllSchema = new Schema({
senderName: {type: String, required: true},
senderId: {type: String, required: true},
content: String,
date: {type: Date, default: Date.now()},
roomId: Schema.Types.ObjectId,
});
My query:
AllMessages.find(
{roomId: [roomId1, roomId2]},
(err, messages) => {
console.log(messages);
},
).sort({date: -1});
My code return
My code returns several messages from room 1 and room 2.
I want to achieve
I want to my code return one message for room 1 and one message for room 2. If I apply .limi(2) I got a 2 message for room 1, but I want to get one message per room.

It is not possible with find(), You can try aggregate() method,
$match roomId in array of roomIds
$group by roomId and get first message form multiple grouped messages in root variable
$replceWith to replace root object in root
$sort by date in descending order
$limit 2 documents only
const messages = await AllMessages.aggregate([
{
$match: {
roomId: { $in: [roomId1, roomId2] }
}
},
{
$group: {
_id: "$roomId",
root: { $first: "$$ROOT" }
}
},
{ $replaceWith: "$root" },
{ $sort: { date: -1 } },
{ $limit: 2 }
]).exec();
console.log(messages);
Playground

Related

Remove object of array schema if the ID does not exist from another array schema

I do have 2 different schemas, one for the user and one for the jobs.
Job has something like manage which is an array of objects and there goes the ID of the user that applied for that job and the ID of the job into user which has an array messages.
If I delete that Object of manage from database then when I get the messages of the user I want to check if that my ID it is into manage, if not then $pull/delete the object with the remaining ID.
Below is what I tried
Job Schema.
manage_: [{
_id: false,
createdAt: Date,
userId: String,
contactedID: String,
listPosition: Number,
note: String,
}]
User Schema.
{
firstName: String,
lastName: String,
messages: [{
_id: mongoose.Schema.Types.ObjectId,
userId: String,
messageID: String,
jobId: String,
timestamp: { type: Date, default: Date.now },
},
],
}
This is the function of the controller which is getting called from the API.
async userJobMessages(req, res , next) {
//req.params.id it is always the authenticated ID
let user = await User.findById(req.params.id);
let fromDate = new Date(Date.now() - 60 * 60 * 24 * 30 * 1000);
user.messages.map(async (t) => {
let job = await Job.find({ _id: t.jobId });
console.log(job, "T")
if (job.length < 1) {
let test2 = await User.findByIdAndUpdate(req.params.id, {
$pull: {
messages: {
jobId: t.jobId,
}
}
}, { new: true });
}});
res.status(200).json({
messages: user.messages,
});
},
I tried somehow to find if my ID exists into manage if not then delete that jobID from my messages.
Based on what #nimrodserok said this is the following which still not working.
I am getting an error message like this.
(node:47922) UnhandledPromiseRejectionWarning: MongoError: let not supported
async userJobMessages(req, res , next) {
let user = await User.findById(req.params.id);
let fromDate = new Date(Date.now() - 60 * 60 * 24 * 30 * 1000);
User.aggregate([
{$match: {_id: req.params.id}},
{$lookup: {
from: "jobs",
let: {jobIds: "$messages.jobId", userId: {$first: "$messages.userId"}},
pipeline: [
{$match: {$expr: {$in: [{$toString: "$_id"}, "$$jobIds"]}}},
{$project: {manage_: {
$filter: {
input: "$manage_",
cond: {$eq: ["$$this.userId", "$$userId"]}
}
}}},
{$match: {"manage_.0": {$exists: true}}},
{$project: {_id: {$toString: "$_id"}}}
],
as: "jobs"
}},
{$set: {
messages: {$filter: {
input: "$messages",
cond: {$in: ["$$this.jobId", "$jobs._id"]}
}}
}},
{$unset: "jobs"},
{$merge: {into: "users"}}
])
res.status(200).json({
messages: user.messages,
});
},
One option is to do it on a single query with $lookup:
Fins the relevant user
Using $lookup, find all jobs listed on this user, but keep only the _ids of jobs that the user is listed on their manage_. Assign them to jobs key.
Filter the user messages to keep only jobs with _ids from our jobs list.
$unset the jobs key
$merge back into the users collection to update the user
const user = await User.aggregate([
{$match: {_id: req.params.id}},
{$lookup: {
from: "jobs",
let: {jobIds: "$messages.jobId", userId: {$first: "$messages.userId"}},
pipeline: [
{$match: {$expr: {$in: [{$toString: "$_id"}, "$$jobIds"]}}},
{$project: {manage_: {
$filter: {
input: "$manage_",
cond: {$eq: ["$$this.userId", "$$userId"]}
}
}}},
{$match: {"manage_.0": {$exists: true}}},
{$project: {_id: {$toString: "$_id"}}}
],
as: "jobs"
}},
{$set: {
messages: {$filter: {
input: "$messages",
cond: {$in: ["$$this.jobId", "$jobs._id"]}
}}
}},
{$unset: "jobs"},
{$merge: {into: "users"}}
])
See how it works on the playground example
EDIT:
For mongoDB version 4.2 or older you can use playground example

How to filter by dayOfWeek in mongodb node.js

I'm new at this, I need to get all the persons from the db that matches the day of the week of the attribute createdAt (a Date) and a day of the week obtained through a parameter in the request. My data model is:
const personSchema = new mongoose.Schema({
firstName: {
required: true,
type: String
},
lastName: {
required: true,
type: String
},
age: {
required: true,
type: Number
},
email: {
type: String
},
createdAt: {
type: Date,
default: Date.now
}
})
I'm working with MongoDB and Node.js
I've tried this:
const people = await Person.aggregate(
[{
$addFields: {
dayyOfWeek: {
$dayOfWeek: "$createdAt"
}
}
}, {
$match: {
dayyOfWeek: {
$eq: req.params.weekday
}
}
}]
)
and tried too with $where and $function in the find function, but it goes wrong because that gives me an error, "MongoError: $where is not allowed in this atlas tier"

How to find document's array item by its "_id" field in MongoDB (Mongoose)?

I have the following schema (NestJS + Mongoose):
#Schema({ timestamps: true })
export class Listing {
#Prop({
type: [{
bidderId: { type: Types.ObjectId, required: true, select: false, ref: User.name, index: true },
amount: { type: Number, required: true },
date: { type: Date, required: true }
}],
select: false
})
bids!: Bid[]
}
so basically every listing document has an array of bids.
now I notice that automatically mongoDB (or mongoose) creates _id field for every bid item I put into the bids array.
My question is, If I have a bid's _id, how can I query it's item from the listing's bids array? something like:
// Adding a new bid to the listing, and retrieving the updated listing
const listingWithAddedBid = await this.listingModel.findByIdAndUpdate(listingId, {
$push: {
bids: {
$each: [{
amount: bidInfo.amount,
bidderId: new Types.ObjectId(user.id),
date: new Date()
}],
$sort: { amount: -1 }
}
}
}, { new: true })
// Getting the new bid's _id from the array (it will be at index 0 because we sort buy amount)
const newBidId = listingWithAddedBid.bids[0]._id
// here how can I query the entire bid item from the array with 'newBidId'? this won't work
this.listingModel.findById(newBidId) // returns null
https://docs.mongodb.com/manual/tutorial/query-array-of-documents/
https://docs.mongodb.com/manual/indexes/#default-_id-index
this.listingModel.findOne({ "bids._id": newBidId, {}, {}, (error, doc) =>
{
if (error) {
....
}
if (doc) {
....
} else {
...//'Not Found';
}
});

Dynamically push, pull, and set on mongoose schema update

I am trying to setup my patch api so that I can create a dynamic query to push, pull, and set data in my mongoose schema. I have plenty of values that I would change using set, but I also have an array of objects which would require me to call push when I need to insert and pull when I need to remove an item. I'm trying to find the best way to combine this into a dynamic structure.
Schema:
const StepSchema = new Schema({
position: {
type: Number,
required: true
},
name: {
type: String,
required: true
},
due_date: {
type: Date
},
status: [{
label: {
type: String,
enum: ['Inactive', 'In Progress', 'Flagged', 'Complete'],
default: 'Inactive'
},
user: {
type: Schema.Types.ObjectId,
ref: 'users',
},
date: {
type: Date
}
}],
comments: [{
user: {
type: Schema.Types.ObjectId,
ref: 'users',
required: true
},
body: {
type: String,
required: true
},
date: {
type: Date,
required: true
},
}],
});
Api:
router.patch('/',
async (req, res) => {
let setQuery = req.body;
let pushQuery = {};
let pullQuery = {};
//remove id from set query
delete setQuery.id;
//if there is a comment
if(req.body.comment){
pushQuery.comments = req.body.comment
}
//if I need to remove a comment
if(req.body.remove_comment){
pullQuery.comments = {_id: req.body.remove_comment.id}
}
//Push new status into array
if(req.body.status) {
pushQuery.status = {
label: req.body.status,
user: req.user._id,
date: new Date()
};
delete setQuery.status;
}
//update step
await Step.findByIdAndUpdate(req.body.id, {$set: setQuery, $push: pushQuery, $pull: pushQuery})
.then(step => {
if(!step){
errors.noflow = "There was a problem updating the step";
return res.status(400).json(errors);
}
res.json(step)
})
.catch(err => {
console.log(err);
res.status(404).json(err);
});
});
I've been getting the following error when trying to push a new status into my document:
operationTime: Timestamp { bsontype: 'Timestamp', low: 1, high_:
1560978288 }, ok: 0, errmsg: 'Updating the path \'status\' would
create a conflict at \'status\'', code: 40, codeName:
'ConflictingUpdateOperators', '$clusterTime': { clusterTime:
Timestamp { bsontype: 'Timestamp', low: 1, high_: 1560978288 },
signature: { hash: [Object], keyId: [Object] } },
Oh, you're doing that $set and $push on a status. Your pushQuery is trying to have status be an array on the document, and your setQuery wants to set it to whatever it was on the actual body (I'm guessing the same object.
A quickfix would be to remove it from the set object:
delete setQuery.status
A reasonable and stable way to do this would be to actually only take the things from req.body which you really want for each of the stages. Example:
const { position, name, dueDate, status, comment, remove_comment } = req.body;
const setQuery = { position, name, dueDate };
const pushQuery = { status, comments: comment };
// ...
That way your queries are not conflicting in any way.

How to use .where() with after a populate in mongoose

So i have two schemas
var subcategories = new Schema({
//the category being populated needs to be the same case ;
categoryId: [{ type: Schema.ObjectId, ref: 'categories' }],
name: String,
description: String,
display: Boolean,
active: Boolean,
sortOrder: Number,
createDate: Date,
updateDate: Date,
type: String,
startDate: Date,
endDate: Date,
authorId: String
});
And
var categories = new Schema({
name: String,
description: String,
display: Boolean,
active: Boolean,
sortOrder: Number,
createDate: Number,
updateDate: Number,
type: String,
startDate: Date,
endDate: Date,
authorId: String
});
And I want to have a query to only return if active/display is true in both category/subcategory. What I'm having trouble with is how to properly set the filter for categoryId after a populate. Here is what I have so far
exports.generateList = function (req, res) {
subcategories
.find({})//grabs all subcategoris
.where('categoryId').ne([])//filter out the ones that don't have a category
.populate('categoryId')
.where('active').equals(true)
.where('display').equals(true)
.where('categoryId.active').equals(true)
.where('display').in('categoryId').equals(true)
.exec(function (err, data) {
if (err) {
console.log(err);
console.log('error returned');
res.send(500, { error: 'Failed insert' });
}
if (!data) {
res.send(403, { error: 'Authentication Failed' });
}
res.send(200, data);
console.log('success generate List');
});
};
The only problem is even when i have a category with display = false it will still get returned.
To build query conditions for populated references there are special ways that can be referenced here:
Query conditions and other options
What if we wanted to populate our fans array based on their age, select just their names, and return at most, any 5 of them?
Story
.find(...)
.populate({
path: 'fans',
match: { age: { $gte: 21 }},
select: 'name -_id',
options: { limit: 5 }
})
.exec()
So in your case, you need to do something similar to this:
subcategories
.find({})//grabs all subcategoris
.where('categoryId').ne([])//filter out the ones that don't have a category
.where('active').equals(true)
.where('display').equals(true)
.populate({
path: 'categoryId',
match: {
active: true,
display: true,
}
})
.exec()

Categories