Group and populate related schema in mongoose - node.js

I have these 2 schemas
const UserSchema = mongoose.Schema({
username: {
type: String,
maxlength: 50,
required: false
},
//other fields...
}
});
module.exports = mongoose.models.User || mongoose.model('User', UserSchema);
const PictureSchema = mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
title: {
type: String,
minLength: 3,
maxlength: 80,
required: true
},
category: {
type: String,
required: false
},
// other fields
}, {timestamps: true} );
module.exports.Picture = mongoose.models.Picture || mongoose.model('Picture', PictureSchema);
I am doing an aggregate query
let aggPictures = await Picture.aggregate([
{$sort: {
createdAt: -1
}
},
{$group: {
_id: '$category' ,
pictures: { $push: "$$ROOT" },
}
},
{$project: {
pictures: {
$slice:["$pictures", 1]
}
}}
])
;
That one works but I am strugling to populate the user. What I tried is to add this right after $project
{$lookup: {
from: 'Users',
localField: 'user',
foreignField: 'username',
as: 'name'
}
},
{
$unwind: '$user'
}
still no luck. any suggestion?

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'
}
}
])

Joining two mongoose collections with many to many relationship

I have two models which are Product & Category with many to many relationship.
Following are my models
This is the model for Product
//Product.js
const mongoose = require("mongoose");
const { Schema } = mongoose;
const { ObjectId } = Schema;
const product = {
name: {
type: String,
required: true,
trim: true,
minlength: [2, "Too short"],
maxlength: [32, "Too long"],
unique: true,
},
slug: {
type: String,
unique: true,
lowercase: true,
index: true,
},
category: [
{
type: ObjectId,
ref: "Category",
},
],
description: {
type: String,
maxlength: 200,
},
price: {
type: Number,
required: true,
trim: true,
maxlength: 32,
},
shipping: {
type: String,
enum: ["Yes", "No"],
},
color: [
{
type: String,
enum: ["Black", "Brown", "Silver", "White", "Blue"],
},
],
sold: {
type: Number,
default: 0,
},
quantity: {
type: Number,
},
images: [
{
public_id: String,
url: String,
},
],
};
const productSchema = new Schema(product, { timestamps: true });
const Product = mongoose.model("Product", productSchema);
module.exports = Product;
This is the model for Category
//Category.js
const mongoose = require("mongoose");
const { Schema } = mongoose;
const { ObjectId } = Schema;
const category = {
name: {
type: String,
required: true,
trim: true,
max: 32,
unique: true,
},
subCategories: [
{
type: Schema.Types.ObjectId,
ref: "Category",
},
],
parent: {
type: Schema.Types.ObjectId,
ref: "Category",
},
products: [
{
type: ObjectId,
ref: "Product",
},
],
slug: {
type: String,
required: "URL can't be empty",
unique: true,
},
};
const categorySchema = new Schema(category, { timestamps: true });
//Validate the slug to ensure proper url is given
// categorySchema.path("slug").validate((val) => {
// urlRegex =
// /(ftp|http|https):\/\/(\w+:{0,1}\w*#)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%#!\-/]))?/;
// return urlRegex.test(val);
// }, "Invalid URL.");
const autoPopulateChildren = function (next) {
this.populate("subCategories");
// this.populate("parent");
next();
};
categorySchema
.pre("findOne", autoPopulateChildren)
.pre("findById", autoPopulateChildren)
.pre("find", autoPopulateChildren);
const Category = mongoose.model("Category", categorySchema);
module.exports = Category;
So, product has a foreign keys of category in array of category. Whereas Category has foreign keys of product in array of products. How do you join them ?
I tried this & it doesn't work. It returns empty object
router.get("/products", [RequireSignIn], async (req, res) => {
try {
const products = await Product.find({}).aggregate({
$lookup: {
from: "Category",
localField: "category",
foreignField: "_id",
as: "ProductCategories",
},
});
return res.status(200).send(products);
} catch (error) {
return res.status(200).send(error);
}
});

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 .

Graphql query returns null id in mongodb aggregation

Graphql query returns id null (in frontend) after mongodb aggregation, but if I print a console.log in the resolvers aggregation, _id is not null. I don't know why in the backend I have a id and in front is null. Anybody can help me? Thanks
Client Schema
const ClientSchema = mongoose.Schema({
surname: {
type: String,
require: true,
trim: true
},
company: {
type: String,
require: true,
trim: true
},
salesman: {
type: mongoose.Schema.Types.ObjectId,
require: true,
ref: 'User'
}
})
module.exports = mongoose.model('Client', ClientSchema)
Order Schema
const OrderSchema = mongoose.Schema({
order: {
type: Array,
require: true,
},
total: {
type: Number,
require: true,
},
client: {
type: mongoose.Schema.Types.ObjectId,
require: true,
ref: 'Client'
},
salesman: {
type: mongoose.Schema.Types.ObjectId,
require: true,
ref: 'User'
},
stage: {
type: String,
default: "PENDENT"
},
})
module.exports = mongoose.model('Order', OrderSchema)
Resolver
getOrdersBySalesman: async (_, { }, ctx) => {
const mongoObjectId = mongoose.Types.ObjectId(ctx.user.id);
try {
const orders = await Order.aggregate([
{ $match: { salesman: mongoObjectId }},
{ $lookup: {
localField: "client",
foreignField: "_id",
from: "clients",
as: "client"
}},
{ $unwind: '$client' },
{ $sort: { 'client.company': 1 }}
]);
console.log(orders);
return orders;
} catch (error) {
console.log(error);
}
}
Graphql query
const GET_ORDERS = gql`
query getOrdersBySalesman {
getOrdersBySalesman {
id
order{
name
quantity
}
total
client {
id
surname
company
}
stage
}
}`
Result console.log resolver _id is fine
{ _id: 5ee0da703b683e071c0adfe2,
order: [ [Object] ],
stage: 'PENDENT',
total: 6166.65,
client:
{ _id: 5ea813b3085b4417b8627557,
surname: 'NOU2',
company: 'NN',
salesman: 5ea568905d47ed2760e8d11c,
__v: 0 },
salesman: 5ea568905d47ed2760e8d11c,
__v: 0 } ]
Result in frontend after graphql query id: null
{ id: null,
order: [{…}]
stage: 'PENDENT',
total: 6166.65,
client:
{ id: null,
surname: 'NOU2',
company: 'NN',
}
}
Use type ID for your id in GraphQL Schema as long as you are returning ObjectID and it should work just fine.
type Order {
id: ID
...
}

how to get count of unread messages against each user Mongoose

I want to select user list from users collection and if a user has an unread message which status is 0/false in messages schema then select unread messages count too, which can be 0/5/10/1/0. thanking you in advance! i am sure it can be done with aggregate framework and already tried some query(below) but with that i am not getting what i need to be the result.
//User Schema
import mongoose from 'mongoose';
const userSchema = new mongoose.Schema({
name: { type: String, required: true, trim: true },
email:{type: String, required: true, unique: true, minlength: 3, trim: true},
password: { type: String, required: true, minlength: 6, trim: true },
});
export default mongoose.model('user', userSchema);
//Message Schema
import mongoose from 'mongoose';
const messageSchema = new mongoose.Schema({
from: { type: mongoose.Schema.Types.ObjectId, ref: 'user' },
to: { type: mongoose.Schema.Types.ObjectId, ref: 'user' },
text: { type: String, trim: true },
unread: { type: Boolean, default: false }
});
messageSchema.set('timestamps', true);
export default mongoose.model('message', messageSchema);
I want the result should be
[ { _id: 5cc984981fa38539f4a61ce0,
name: 'Jhon Doe',
unreadTotal: 5 },
{ _id: 5cc98f651fa38539f4a61cfd,
name: 'Markas',
unreadTotal: 3 },
{ _id: 5cc994b164745026c4e25546,
name: 'Mike',
unreadTotal: 0 } ]
// i tried the following but getting unread of total not for each user
const userId = 'loggedinUserId'
const userList = await User.aggregate([
{
$match: { _id: {$ne: ObjectId(userId) } }
},
{ $lookup:
{
from: 'messages',
let: { 'to': ObjectId(userId) },
pipeline: [
{
$match:
{
'unread': false,
$expr: { $eq: [ '$$to', '$to' ] }
}
},
{ $count: 'count' }
],
as: 'messages'
}
},
{
$addFields:
{
'unreadTotal': { $sum: '$messages.count' }
}
}
]);
Not sure this will help you or not, but you will get all unread messages of each user against the logged in user as you have described and later you can do whatever you want with like count etc.
const userList = await User.aggregate([
{
$match: { _id: {$ne: ObjectId(userId) } }
},
{
$lookup:
{
from: 'messages',
localField: '_id',
foreignField: 'from',
as: 'messages'
},
},
{
$project:
{
name: 1,
email: 1,
profilePhoto: 1,
messages:
{
$filter:
{
input: "$messages",
as: "message",
cond: {
$and: [
{ $eq: [ '$$message.unread', false ] },
{ $eq: [ '$$message.to', ObjectId(userId) ] }
]
}
}
}
}
}
]);

Resources