I am building a chat app using nodejs and mongoose. I need to retrieve chats for a user(logged in user) such that the retrieved data/chats will contain the latest message and the name of the user the logged in user had a chat with. I am not able to come app with a way to aggregate the data since the sender can also be a recipient in a chat. My message schema is shown below.
const messageSchema = new mongoose.Schema(
{
content: { type: String, required: true },
fromUser: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
toUser: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
users: Array,
messageRead: { type: Boolean, default: false },
},
{
timestamps: true,
}
);
The purpose of this is to have a list of chats render on the front end which when clicked will open an inbox containing messages between the two users.
You can just use the $or operator. This way it will return all elements where the user is the sender or recipient:
.aggregate([
{
$match: {
$or: [
{
fromUser: ObjectId("<UserID>")
},
{
toUser: ObjectId("<UserID>")
}
]
}
},
{ $sort: { createdDate: -1 } },
{ $limit: 1 },
])
Edit after feedback via chat
I grouped the documents by using the users array and basically just output all fields as in the initial document:
.aggregate([
{
$match: {
users: ObjectId("<UserID>")
}
},
{
$group: {
_id: "$users",
out: {
$top: {
output: {
_id: "$_id",
content: "$content",
fromUser: "$fromUser",
toUser: "$toUser",
users: "$users",
messageRead: "$messageRead",
createdAt: "$createdAt"
},
sortBy: {
createdAt: -1
}
}
}
}
},
{
"$replaceRoot": {
"newRoot": "$out"
}
}
])
Related
Hello I have the following collections
const TransactionSchema = mongoose.Schema({
schedule: {
type: mongoose.Schema.ObjectId,
required: true,
ref: "Schedule"
},
uniqueCode: {
type: String,
required: true
},
created: {
type: Date,
default: Date.now
},
status: {
type: String,
required: false
},
})
const ScheduleSchema = mongoose.Schema({
start: {
type: Date,
required: true,
},
end: {
type: Date,
required: false,
},
location: {
type: mongoose.Schema.ObjectId,
required: true,
ref: "Location"
},
})
and I want to return how many times the schedule appear in transaction ( where the status is equal to 'Active') and group it based on its location Id and then lookup the location collection to show the name.
For example I have the following data.
transaction
[
{
"_id":"identifier",
"schedule":identifier1,
"uniqueCode":"312312312312",
"created":"Date",
"status": 'Active'
},
{
"_id":"identifier",
"schedule":identifier1,
"uniqueCode":"1213123123",
"created":"Date",
"status": "Deleted"
}
]
schedule
[
{
"_id":identifier1,
"start":"date",
"end":"date",
"location": id1
},
{
"_id":identifier2,
"start":"date",
"end":"date",
"location": id2
}
]
and I want to get the following result and limit the result by 10 and sort it based on its total value:
[
{
"locationName":id1 name,
"total":1
},
{
"locationName":id2 name,
"total":0
}
]
thank you. Sorry for my bad english.
A bit complex and long query.
$lookup - schedule collection joins with transaction collection by matching:
_id (schedule) with schedule (transaction)
status is Active
and return a transactions array.
$lookup - schedule collection joins with location collection to return location array.
$set - Take the first document in location array so this field would be a document field instead of an array. [This is needed to help further stage]
$group - Group by location._id. And need the fields such as location and total.
$sort - Sort by total DESC.
$limit - Limit to 10 documents to be returned.
$project - Decorate the output documents.
db.schedule.aggregate([
{
$lookup: {
from: "transaction",
let: {
scheduleId: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [
"$schedule",
"$$scheduleId"
]
},
{
$eq: [
"$status",
"Active"
]
}
]
}
}
}
],
as: "transactions"
}
},
{
$lookup: {
from: "location",
localField: "location",
foreignField: "_id",
as: "location"
}
},
{
$set: {
location: {
$first: "$location"
}
}
},
{
$group: {
_id: "$location._id",
location: {
$first: "$location"
},
total: {
$sum: {
$size: "$transactions"
}
}
}
},
{
$sort: {
"total": -1
}
},
{
$limit: 10
},
{
$project: {
_id: 0,
locationName: "$location.name",
total: 1
}
}
])
Sample Mongo Playground
I have 2 schemas
userSchema:
const userSchema = new mongoose.Schema({
firstName: {
type: String,
},
lastName: {
type: String,
},
username: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
tokens: {
type: [
{
token: String,
},
],
},
});
useractivity:
const userActivitySchema = new mongoose.Schema({
ip: String,
ua: String,
date: {
type: Date,
default: Date.now,
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
});
I am adding a new entry in useractivity every time user logged into the system.
I wanted to featch list of users who haven't logIn in last 5 days for that I write query :
const dateToCheck = new Date();
dateToCheck.setDate(
dateToCheck.getDate() - process.env.LEASS_ACTIVE_USER_DAYS
);
const userActivity = await UserActivity.aggregate([
{ $match: { date: { $lt: dateToCheck } } },
{
$lookup: {
from: "users",
localField: "user",
foreignField: "_id",
as: "userdata",
},
},
{ $unwind: { path: "$userdata" } },
{
$project: {
date: 1,
"userdata.firstName": 1,
},
},
]);
with this, I am able to get date and firstName of users who haven't logged in last 5 days.
But the issue with this is as there are multiple records in useractivity, above query returns duplicate users in the result.
I tried using { $group: { _id: { user: "$user" } } } to group the result by foreign key but it returns [] in this case.
Any suggestions will be helpfull
Try this out, you already have access to the unique id of the user from your lookup, so use that to group your documents. Additionally, I am storing all the dates in an array from the grouping
UPDATE: Also attaching mongo playground link where I tried it
const userActivity = await UserActivity.aggregate([
{ $match: { date: { $lt: dateToCheck } } },
{
$lookup: {
from: "users",
localField: "user",
foreignField: "_id",
as: "userdata",
},
},
{ $unwind: { path: "$userdata" } },
{
$project: {
date: 1,
"userdata.firstName": 1,
"user_id": "$userdata._id"
},
},
{
$group: {
"_id": {
"user_id": "$user_id",
"user_name": "$userdata.firstName"
},
"dates": {
"$push": "$date"
}
}
}
]);
Imagine we have the structure like the following:
comments: [{
user: '...',
upVotes: 12,
text: '...'
replies: [{
user: '...',
upVotes: 34,
text: '...'
}]
}]
what we want is to retrieve the user and text of comments and replies with the same user!
I have implemented the following query but it doesn't work:
db.getCollection('links').aggregate([
{
$unwind: "$comments"
},
{
$unwind: "$comments.replies"
},
{
$project: {
sameAuthor: {
$eq: ["$comments.user", "$comments.replies.user"]
},
}
}
])
I don't know where the problem is!
If you need more clear result please add result sample in the comment.
db.links.aggregate([
{ $unwind: '$comments'},
{ $unwind: '$comments.replies'},
{ $match: { $expr: { $eq: ["$comments.user", "$comments.replies.user"] } } },
{ $group: {
_id: '$_id',
user: { $first: "$comments.user" },
commentText: { $first: "$comments.text" },
repliesTaxt: { $push: "$comments.replies.text" }
}
}
])
im using nodejs express and mongodb
this is part of my schema:
var UserSchema = mongoose.Schema({
friends: [{
type: mongoose.Schema.Types.ObjectId,
ref: "User",
unique: true
}],
sentRequests: [{
type: mongoose.Schema.Types.ObjectId,
ref: "User",
unique: true
}],
receivedRequests: [{
type: mongoose.Schema.Types.ObjectId,
ref: "User",
unique: true
}]
}
var User = mongoose.model('User', UserSchema);
i want to query if a user with a specefic id is in any array ** (friends, sentRequests or receivedRequests) **of another user :
i used this approach but I have to do this for 3 arrays and it's slow:
UserSchema.statics.relStatus = function(User1ID, User2ID) {
var User = this;
User.find({
_id: User1ID,
friends: {
_id: User2ID
}
}).then((err, res) => {
if (res) {
return ' they are friends';
}
});
You can try with following aggregation query (if you are using mongo 3.4 or higher):
User.aggregate([
{ $match: { _id: User1ID } },
{
$facet: {
friends: [{ $match: { friends: User2ID } }],
sentRequests: [{ $match: { sentRequests: User2ID } }],
receivedRequests: [{ $match: { receivedRequests: User2ID } }]
}
},
{
$project: {
relationship: {
$switch: {
branches: [
{ case: { $anyElementTrue: [ "$friends" ] }, then: "friends" },
{ case: { $anyElementTrue: [ "$sentRequests" ] }, then: "sentRequests" },
{ case: { $anyElementTrue: [ "$receivedRequests" ] }, then: "receivedRequests" },
],
default: false
}
}
}
}
])
Result will be object in following format {relationship: "<RELATIONSHIP_TYPE>"}
or {relationship: false} if they have no relationship.
First stage of the aggregation pipeline will match single document by _id (which is indexed by default), so you shouldnt have any performance issues.
In order to query for multiple field you can user '$or'.
User.find({
_id: User1ID,
{
$or: [
{friends: $in:[User2ID]},
{sentRequests: $in:[User2ID]},
{receivedRequests: $in:[User2ID]}
]
}
}).then((err, res) => {
if (res) {
return ' they are friends';
}
});
$or will return a user which have User2ID in either 'friends' or 'sentRequests' or 'receivedRequests'.
Read more about '$or' and '$in'
Hope this helps.
I the following schema.
var UserSchema = new Schema({
messages: [{
username: String,
read: [{
type: { type: String, enum: ['A','B','C'] },
}]
}],
});
var MySchema = new Schema({
users: [UserSchema]
});
I need to count how many occurrences in MySchema that the last message of each user don't have the read value as 'A'
I need to use aggregate, so that I can join with an existing query.
This should work, if it does not please include some sample data and expected result so that I will update my answer later:
db.collection.aggregate([
{
$unwind: '$users',
},
{
$project: {
lastMessage: {
$arrayElemAt: ['$users.messages', -1],
},
},
},
{
$unwind: '$lastMessage.read',
},
{
$group: {
_id: '$_id',
read: {
$push: '$lastMessage.read',
},
},
},
{
$match: {
read: {
$nin: ['A']
}
}
},
{
$count: 'count'
}
])