Displaying friends list using node.js and MongoDB - node.js

I am new to a node.js and I am trying to use this application https://github.com/knoldus/Node.js_UserLogin_Template
However, I cannot see friends list. I do not know what is the problem with ?
Could you help me with issue ?
Thanks

//Your user schema is looks like that
const userSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
//unique: true,
required: true
},
password: {
type: String,
required: true
},
friends: {
type: Array // here you can put your friends collections _id like this [ObjectId("id1"), ObjectId("id2")]
}
});
const FriendSchema = new Schema({
user_from: {
type: Schema.Types.ObjectId,
ref: "users",
required: true
},
user_to: {
type: Schema.Types.ObjectId,
ref: "users",
required: true
},
is_accepted: {
type: Boolean,
default: false
},
date: {
type: Date,
default: Date.now
}
});
//and your query will look like that --
FriendModel.aggregate([
{
$lookup: {
from: "users", // users collection
localField: "_id", // friends collection id
foreignField: "friends", // friends field in you users collection document
as: "myfriends" //any alias name you can use
}
},
{
$match: { // matching condition for current user's friends from friends collection including current user
$or: [
{ user_to: mongoose.Types.ObjectId(req.user.id) }, // req.user.id referrers to logged in user object id
{ user_from: mongoose.Types.ObjectId(req.user.id) },
]
},
$match: { is_accepted: true} // that condition is for is user accepted friend request or not.
}
,
{ // filtering logged in user from the friend list
$project: {
myfriends: { // myfriends is alias name that you used in $loopup part
$filter: {
input: "$myfriends",
as: "item",
cond: { $ne: [ "$$item._id", mongoose.Types.ObjectId(req.user.id) ] }
}
}
}
}
])

Related

How to find sum in Mongo aggregate method with match in different collection in an array of object?

I am trying to find the sum by matching array of object using mongoose. I have 2 collection such as
const accountSchema = new mongoose.Schema({
groupId: {
type: Number,
required: true
},
account_no: {
type: String,
required: true
},
account_name: {
type: String,
required: true
},
opening_balance: {
type: Number,
default: 0
}
})
And second collection as:
const mongoose = require('mongoose')
const AutoIncrement = require('mongoose-sequence')(mongoose);
const accountJournalSchema = new mongoose.Schema({
journal_no: {
type: Number
},
user: {
type: mongoose.Schema.ObjectId,
ref: 'Users',
required: [true, 'User ID is required.'],
},
groupId: {
type: Number,
required: true
},
date: {
type: Date,
required: true
},
receipt: [
{
account_no: {
type: mongoose.Schema.ObjectId,
ref: 'Accounts',
required: true
},
debit: {
type: Number,
default: 0
},
credit: {
type: Number,
default: 0
},
}
]
})
And my aggregate method is:
await Accounts.aggregate([
{
$match: {
$and: [
{ groupId: {$eq: parseInt(req.params.group_id)} },
{ 'Account_jour.groupId': { $eq: parseInt(req.params.group_id) } }
]
}
},
{ unwind: '$Account_jour' },
{
$lookup: {
from : 'account_journals',
localField: '_id',
foreignField: 'receipt.account_no',
as: 'Account_jour'
}
}
])
I am getting error from the above statement:
Arguments must be aggregate pipeline operators
And after solving the issue I also want to find the sum of debit and credit.
Thank you!!
Try this:
await Accounts.aggregate([
{
$match: {
$and: [{ groupId: { $eq: parseInt(req.params.group_id) } }, { "Account_jour.groupId": { $eq: parseInt(req.params.group_id) } }]
}
},
{ $unwind: '$Account_jour' },
{
$lookup: {
from : 'account_journals',
localField: '_id',
foreignField: 'receipt.account_no',
as: 'Account_jour'
}
}
])

how to sort after lookup and project aggregation in mongodb?

I have 2 Collections one for users and other for posts(Posts colllection have _id of users as postedBy).
In users collection each user is having friends array which have _id of users in it.I want to get all the Posts of My friends and mine post in sorted order(sorted By CreatedAt).
This is my Userschema in which i am having friends array of mongoose object type ref to user collection,
here i'm storing users id who is friend.
`//UserSchema
const userSchema = new Schema({
profileImg : {
type: String,
},
name: {
type: String,
required: [true, 'Please Enter Your Name!']
},
about: {
type: String,
},
email: {
type: String,
required: [true, 'Please Enter Email!'],
unique: [true, 'Already Registered!'],
match: [/\S+#\S+\.\S+/, 'is invalid!']
},
password: {
type: String,
required: [true, 'Please Enter Your Password!'],
},
friends: [{
type: mongoose.Types.ObjectId,
ref: 'USER'
}],
address: {
line1: {
type: String,
required: [true, 'Please Enter Your Address!']
},
line2: {
type: String
},
city: {
type: String,
required: [true, 'Please Enter Your City!']
},
state: {
type: String,
required: [true, 'Please Enter Your State!']
},
}
}, { timestamps: true })
This is my Post Schema where userId is ref to users collection and here the _id of user who is uploading post is saved.
//POST SCHEMA
const postSchema = new Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "USER",
required: true
},
postImage: {
type: String,
required: [true, 'Please Upload the Image!']
},
caption: {
type: String
},
likes: [likeSchema],
comments: [commentSchema]
}, { timestamps: true })
`
What I am Doing:
1st I am finding the user through _id
2nd from found user's friend array ,lookup in posts collection to get post of friends
3rd Now to get owns post again look up in post collection with own _id
4th concat the both array obtain from friend post and user post as Posts
Now here after step 4 i want to sort the Posts by createdAt but its not working..
How to sort it?
const posts = await User.aggregate([
{
$match: {
_id: mongoose.Types.ObjectId(req.user_id)
}
},
{
$lookup: {
from: "posts",
localField: "friends",
foreignField: "userId",
as: "friendposts"
}
},
{
$lookup: {
from: "posts",
localField: "_id",
foreignField: "userId",
as: "userposts"
}
},
{
$project: {
"Posts": {
$concatArrays: ["$friendposts", "$userposts"]
},
_id: 0
}
}
])
you can use 1 lookup instead of 2 .
for sorting you have 3 ways
sort in the code level (using sort function)
use $unwind $sort and group (if mongo db version is less than 5.2)
use $sortArray (applicable for mongodb version 5.2+)
if using 2nd method.
User.aggregate([
{
'$match': {
'_id': mongoose.Types.ObjectId(req.user_id)
}
}, {
'$addFields': {
'users': {
'$concatArrays': [
'$friends', [
mongoose.Types.ObjectId(req.user_id)
]
]
}
}
}, {
'$lookup': {
'from': 'posts',
'localField': 'users',
'foreignField': 'userId',
'as': 'posts'
}
}, {
'$unwind': {
'path': '$posts'
}
}, {
'$sort': {
'posts.createdAt': -1
}
}, {
'$group': {
'_id': '$_id',
'posts': {
'$push': '$posts'
},
'name': {
'$first': '$name'
}
}
}
])
you can add any other field needed in final response like wise i added name .

mongoose $lookup always returning empty array

I have already looked up some questions related to this but with no luck at all. I have the following schemas
const Card= new mongoose.Schema(
{
name: { type: String },
collectionId: { type: mongoose.Schema.Types.ObjectId, ref: "Collection" },
price: { type: Number}
},
{ timestamps: true }
);
const Collection= new mongoose.Schema(
{
name: { type: String },
},
{ timestamps: true }
);
const Transactions = new mongoose.Schema(
{
amount: { type: Number},
cardId: { type: mongoose.Schema.Types.ObjectId, ref: "Card" },
collectionId: {type: mongoose.Schema.Types.ObjectId, ref: "Collection"},
userId: {type: mongoose.Schema.Types.ObjectId, ref: "User" }
},
{ timestamps: true }
);
lets say I want to aggregate transactions to get the user who paid most to buy cards and his info then I will do something like this
const T= require("transaction model")
const User = require("user model")
const res = await T.aggregate([
{
$group: {
_id: "$userId",
totalPaid: { $sum: "$amount" },
cardsBought: { $sum: 1 },
},
},
{
$lookup: {
from: "users" / User.document.name
localField: "userId",
foreignField: "_id",
as: "userInfo",
},
},
])
I followed similar questions answers and made sure that the collection name is correct in the "from" field (I tried both users and User.document.name) and made sure the localField and foreignField types are the same(they are both mongo ObjectId) but still I get userInfo as empty array and I am not really sure why.
A work around that I have tried is making two queries like the following
const res = await T.aggregate([])
await User.populate(res, {path: "_id", select: {...}})
but the issue is that I can't do multiple lookups (lets say I want to populate some other data as well)

use mongoose.populate along with mongoose-pagivate-v2, when data is inside array of object

I am getting some data in an array of object like this :
{
"success": true,
"result": {
"docs": [
{
"_id": "60a602901a74f62935a4898f",
"user": "607030ba3c82e235443db610",
"weekNum": 19,
"__v": 0,
"createdAt": "2021-05-20T06:32:48.742Z",
"data": [
{
"activity": "6063f898232d3f2acca5d2ae",
"_id": "60a6063668f27715b0f08753",
"project": "60702d1f3c82e235443db5ff",
"task": "60702d3d3c82e235443db601",
"workingDate": "2021-05-10T18:30:00.000Z",
"dayVal": 1,
"description": ""
}
],
"managersComment": "leleleleelelel",
"status": "Submitted",
"updatedAt": "2021-05-20T06:48:22.163Z"
}
],
"paginator": {
"itemCount": 1,
"offset": 0,
"perPage": 10000,
"pageCount": 1,
"currentPage": 1,
"slNo": 1,
"hasPrevPage": false,
"hasNextPage": false,
"prev": null,
"next": null
}
}
}
my schema for this collection in like this:
const timesheetSchema = new Schema({
managersComment: {
type: String
},
weekNum: {
type: Number
},
data:[{
project: {
type: Schema.ObjectId,
ref: projectModel
},
task: {
type: Schema.ObjectId,
ref: taskModel
},
activity: {
type: Schema.ObjectId,
default: null,
ref: activityModel
},
workingDate: {
type: Date
},
dayVal: {
type: Number
},
description: {
type: String
},
}],
user: { type: ObjectId, ref: userModel },
status: {
type: String,
enum: ['Saved', 'Submitted', 'Approved', 'Rejected', 'Reset']
},
}, { timestamps: true });
timesheetSchema.plugin(mongoosePaginate);
const timesheetModel = mongoose.model('timesheet', timesheetSchema);
my code for getting data is something like this:
try {
console.log('populateRequired --------------------------------------------------')
const populateArray = [
{ path: "task", select: "taskName" },
{ path: "project", select: "projectName" },
{ path: "activity", select: "title" },
];
const query = {
user: req.params.userId,
status: req.query.status,
};
const paginationParams = {
populate: populateArray,
customLabels: customLabels,
limit: req.query.limit,
};
console.log("USER QUERY ", query);
const userTimesheet = await getTimesheetDataByUserId(
query,
paginationParams
);
console.log(userTimesheet);
res.send({ success: true, result: userTimesheet });
} catch (err) {
console.log(err);
next(err);
}
But as shown in return data above i am not getting populate applied in data array. Please help not sure what to do.
According to the data you posted, I think that the issue is that you're not creating virtual fields to populate with your references. Your fields project, task and activity in each array element, or user, are meant to be ids referring to the corresponding models. But those ids alone will not implement the population, they are only the pointers that the population will need in order to be executed. To make that a little bit more clear, I would change those names to userId: { type: ObjectId, ref: userModel }.
After that, you will need to create the virtual fields:
timesheetSchema.virtual("user", {
ref: "userModel",
localField: "userId",
foreignField: "_id",
justOne: true,
});
Finally, if you want to have the virtual field timesheet.user populated each time you query your collection, you will have to add some middleware to your schema. For me, the most reasonable way to make this work is:
timesheetSchema.pre("find", function (next) {
this.populate("user");
next();
});
Just to have a complete solution: I think this will solve your problem for the timesheet.user field. But I don't think it will work in your data array. In fact, I'm not 100% sure the way you're defining it is really going to work: creating a timesheet with an array of imputations doesn't make too much sense to me. A more coherent approach would be creating a collection of all the imputations that looked like this:
const dataSchema = new Schema({
projectId: {
type: Schema.ObjectId,
ref: projectModel
},
taskId: {
type: Schema.ObjectId,
ref: taskModel
},
activityId: {
type: Schema.ObjectId,
default: null,
ref: activityModel
},
userId: {
type: ObjectId,
ref: userModel
},
workingDate: {
type: Date
},
dayVal: {
type: Number
},
description: {
type: String
},
});
With virtual fields like:
dataSchema.virtual("project", {
ref: projectModel,
localField: "projectId",
foreignField: "_id",
justOne: true
});
And so on. I would populate each field just like I showed you with the user example. Then, for the timesheet schema I would only reference userId, and populate data like this:
const timesheetSchema = new Schema({
managersComment: {
type: String
},
weekNum: {
type: Number
},
userId: {
type: ObjectId,
ref: dataModel
},
status: {
type: String,
enum: ['Saved', 'Submitted', 'Approved', 'Rejected', 'Reset']
},
}, { timestamps: true });
timesheetSchema.virtual("data", {
ref: dataModel,
localField: "userId",
foreignField: "userId"
});
timesheetSchema.virtual("user", {
ref: userModel,
localField: "userId",
foreignField: "_id",
justOne: true
});
This way you would have a collection with all the imputations for all the users, and you would be able to query and filter that collection for each userId, projectId or anything you would need. Having an array inside your timesheet collection would make this quite more difficult.
One simple solution I found on another SO post is like this:
const result = await timesheetModel.findOne(query).populate({
path: 'data.project data.activity data.task'
});

Cascaded join using mongoose node js

I am trying to fetch data from MongoDB using Node Js. I have three schemas: Projects, Users, and Teams.
I need to retrieve the project details based on it's type with the worker users.
I got stuck in making join for these schemas:
Projects:
const Project = new Schema({
projectName: { type: String, required: true, trim: true },
type: { type: String, required: true, trim: true },
teamID: { type: Schema.Types.ObjectId, required: true },
});
Teams
const Team = new Schema({
teamId: { type: Schema.Types.ObjectId, required: true, trim: true },
users: { type: [Schema.Types.ObjectId], required: true, trim: true },
teamName: { type: String, required: true },
});
Users:
const User = new Schema({
userId: { type: Schema.Types.ObjectId, required: true, trim: true },
name: { type: String, required: true, trim: true },
profilePicture: { type: String, required: true, trim: true },
});
I am trying to find a way to get
[
{
projectName: "s",
type: "w",
users: ["Jon", "Ali", "Mark"]
},
{
projectName: "a",
type: "w",
users: ["Jon", "Mark"]
}, {
projectName: "s",
type: "w",
users: ["Jon", "Ali", "Mark"]
},
]
I tried to use $lookup, but I can not use it because the relation is complex many to many relations.
Is there a way more efficient than retrieving all users, all teams, and all projects?
I think there is no other efficient way except aggregation and without lookup we can't join collections, You can use nested lookup,
$match condition for type
$lookup to join Team collection using teamID
$match teamID
$lookup to join User collection using users array
$project to convert user's name array using $map
$addFields to get users array in users using $arrayElemAt
db.Project.aggregate([
{ $match: { type: "w" } },
{
$lookup: {
from: "Team",
let: { teamID: "$teamID" },
as: "users",
pipeline: [
{ $match: { $expr: { $eq: ["$$teamID", "$teamId"] } } },
{
$lookup: {
from: "User",
localField: "users",
foreignField: "userId",
as: "users"
}
},
{
$project: {
users: {
$map: {
input: "$users",
in: "$$this.name"
}
}
}
}
]
}
},
{ $addFields: { users: { $arrayElemAt: ["$users.users", 0] } } }
])
Playground
Second possible way, you can combine $project and $addFields stages in single stage,
{
$addFields: {
users: {
$arrayElemAt: [
{
$map: {
input: "$users.users",
in: "$$this.name"
}
},
0
]
}
}
}
Playground

Resources