Getting items that referenced an object id - node.js

// PRODUCT MODEL
const productSchema = new mongoose.Schema(
{
category: {
type: mongoose.Schema.Types.ObjectId,
ref: "Category",
},
name: {
type: String,
required: true,
},
price: {
type: Number,
required: true,
default: 0,
},
colors: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Color",
},
],
sizes: {
type: Array,
default: [
{ id: 1, size: "XS" },
{ id: 2, size: "S" },
{ id: 3, size: "M" },
{ id: 4, size: "L" },
{ id: 5, size: "XL" },
{ id: 6, size: "XXL" },
],
},
},
{ timestamps: true }
);
const Product = mongoose.model("Product", productSchema);
export default Product;
// CATEGORY MODEL
const CategorySchema = new mongoose.Schema(
{
name: String,
},
{ timestamps: true, toJSON: true, toObject: true }
);
CategorySchema.virtual("items").get(async function () {
return await CategorySchema.aggregate([
{
$lookup: {
from: "Product",
localField: "category",
foreignField: "_id",
as: "items",
},
},
]);
});
const Category = mongoose.model("Category", CategorySchema);
export default Category;
Hello, please i'm trying to get list of items that referenced each categoryid on the product model, i want to add the result as a virtual field called "count" from the category model and not the product model. i'm getting this error "TypeError: Cannot use 'in' operator to search for 'transform' in true".
i want to get the result the way it was done in this example from mongodb doc about inventory and orders but i'm getting "items: {}". docs.mongodb.com/manual/reference/operator/aggregation/lookup

router.get(
"/counts",
catchAsync(async (req, res) => {
const stats = await Category.aggregate([
{
$lookup: {
from: "products",
localField: "_id",
foreignField: "category",
as: "count",
},
},
{
$project: {
name: 1,
image: 1,
count: {
$cond: {
if: { $isArray: "$count" },
then: { $size: "$count" },
else: "NA",
},
},
},
},
]);
if (!stats) {
return res.status(404).json("No data found");
}
res.status(200).json(stats);
})
);
For anyone that is trying to achieve the result i wanted, i used Category.aggregate on the category route instead of creating a virtual field from the category model.

Related

mongoose middleware, show length of array in model

I want to get the size of array in the model:
const UserSchema = mongoose.Schema(
{
username: { type: String, lowercase: true, required: true },
items: { type: [mongoose.Types.ObjectId], default: [] },
},
{
toJSON : {
virtuals : true
},
timestamps: true,
}
);
UserSchema.virtual("itemsCount").get(function () {
return this.items.length;
});
module.exports = {
UserModel: mongoose.model("user", UserSchema ),
};
const ProductSchema = mongoose.Schema(
{
name: { type: String, required: true },
owner: { type: mongoose.Types.ObjectId,required: true, ref:"user"
},
},
{
toJSON : {
virtuals : true
},
timestamps: true,
}
);
module.exports = {
ProductModel: mongoose.model("product", ProductSchema ),
};
But I want to hide items in the output whenever I try to use projection it gives an error:
Cannot read properties of undefined (reading 'length')
const newPosts = await ProductModel.find({}).populate([{ path: "owner", select: { itemsCount: 0 }}]);
if I don't use select it works:
const newPosts = await ProductModel.find({}).populate([{ path: "owner" }}]);
But I don't want to show items filed in output
You can use aggregation pipeline for this:
ProductModel.aggregate([
{
"$lookup": {
"from": "users",
"localField": "user",
"foreignField": "_id",
"as": "users"
}
},
{
"$unwind": "$users"
},
{
"$project": {
_id: "$_id",
name: "$name",
"users": {
itemsCount: {
$size: "$users.items"
}
}
}
}
])
Read more about $lookup, $unwind, $project to understand.
Here is the Mongodb playground to see the results: https://mongoplayground.net/p/R0ZQiV8I-YM

how to use mongoose aggregate to search an array within a collection

I am trying to group by products in my sales collection and add their totals to know which are the best selling products of my app.
MONGOOSE MODEL
const mongoose = require('mongoose');
const DHCustomerinvoiceSchema = mongoose.Schema({
Saledetail: {
type: Array,
required: true
},
date:{
type: Date,
required: true
},
total:{
type: Number,
required: true
},
pay:{
type: Number,
required: true,
default: 0
},
topay:{
type: Number,
required: true
},
user:{
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'UserDH'
},
customer:{
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'DHcontacto'
},
state:{
type: String,
default: "OWED"
},
created:{
type: Date,
default: Date.now()
},
});
module.exports = mongoose.model('DHCustomerinvoice', DHCustomerinvoiceSchema);
COLLECTION EXAMPLE
{
"id": "5ef6*****",
"Saledetail": [
{
"id": "5ebf*****",
"quantity": 9,
"price": 2000,
"totalline": 18000
}
],
"datesale": "1593129600000",
"grandtotal": 18000,
"user": "5eb9ab******",
"customer": {
"name": "isabella"
},
"state": "PAID"
},
RESOLVER:
mostSellingProducts: async (_,{},ctx)=>{
const Products = await invoice.aggregate([
{ $unwind: "$Saledetail" },
{ $match: { "state" : 'PAID'}},
{ $group: {
_id : "$Saledetail.id",
total: { $sum: '$Saledetail.totalline' }
}},
{
$lookup: {
from: 'dhproducts',
localField: '_id',
foreignField: "_id",
as: "producto"
}
},
{
$limit: 4
},
{
$sort : {total: -1}
}
]);
console.log(Products);
return Products;
},
I have used many methods that actually did not give me this result, but nevertheless I have achieved a positive result in terms of finding my best clients who actually develop it with aggregate, match and group also apply sort and limit ...
but with this example I have not been able to achieve success, and I imagine that it is because the architecture of the collection is distinguished due to the arrangement of the purchase detail
I don't have enough reputation to comment on your question. So I am sharing this as an answer.
I think you can use $elemMatch to search for the item in an array.
const Productos = await Factura.aggregate([{ detlle: { $elemMatch: { $gte: 80, $lt: 85 } } }])
For more detailed info elemMatch
below the answer to my question
mostSellingProducts: async (_,{},ctx)=>{
const Products = await Invoice.aggregate([
{ $unwind: "$Saledetail" },
{ $match: { "state" : 'PAY'}},
{ $group: {
_id : { $toObjectId: "$Saledetail.id" },
total: { $sum: '$Saledetail.totalline' }
}},
{
$lookup: {
from: 'dhproducts',
localField: '_id',
foreignField: "_id",
as: "products"
}
},
{
$limit: 4
},
{
$sort : {total: -1}
}
]);
return Products;
},

Mongoose $lookup object returns empty array

I have tried other similar kind of questions available but nothing seems to work for me.
I have two collections:
leads:
const mongoose = require("mongoose");
const id = mongoose.Schema.Types.ObjectId;
const leadsSchema = mongoose.Schema(
{
_id: id,
userId: { type: id, ref: "User", required: true },
leadName: String,
leads: [
{
_id: id,
name: String,
status: { type: String, required: false, default: "New" },
leadActivity: { type: String, required: false, default: "No Campaign Set" },
headline: { type: String, required: false },
location: { type: String, required: false },
leadType: { type: id, ref: "LeadsCategory", required: true },
}
],
campaignAssociated: {type: id, ref: "campaign"},
},
{
timestamps: true
}
);
module.exports = mongoose.model("lead", leadsSchema);
leadCategory
const mongoose = require("mongoose");
const leadsCategorySchema = mongoose.Schema(
{
_id: mongoose.Schema.Types.ObjectId,
name: {
type: String,
required: false,
},
leadsData: [{ type: Array, ref: "lead" }],
},
{ timestamps: true }
);
module.exports = mongoose.model("LeadsCategory", leadsCategorySchema);
I am trying to reference/populate the name of the lead from leadscategory schema into the leads
exports.get_single_lead_info = (req, res) => {
const { userId } = req.user;
const { leadid } = req.body;
let idToSearch = mongoose.Types.ObjectId(leadid);
Lead.aggregate([
{
$lookup: {from: 'leadscategories', localField: 'leadType', foreignField: 'name', as: 'type as'}
},
{
$match: {
userId: mongoose.Types.ObjectId(userId),
},
},
{
$unwind: "$leads",
},
{
$match: {
"leads._id": idToSearch,
},
},
])
.exec(function (err, result) {
if (err) {
return res.status(400).json({ message: "Unable to fetch data", err });
}
if (!result.length) {
res.status(404).json("No result found");
} else {
res.status(200).json({ message: "Lead info found", result });
}
});
};
But it outputs me the lookup result as an empty array everytime:
{
"message": "Lead info found",
"result": [
{
"_id": "5ece11cbac50c434dc4b7f2c",
"leadName": "python",
"leads": {
"status": "New",
"leadActivity": "Campaign Set",
"name": "Hailey",
"headline": "Machine Learning | Python",
"location": "New Delhi Area, India",
"_id": "5ece11cbac50c434dc4b7f29",
"leadType": "5ebce0f81947df2fd4eb1060"
},
"userId": "5eba83d37d4f5533581a7d58",
"createdAt": "2020-05-27T07:07:55.231Z",
"updatedAt": "2020-05-27T10:47:42.098Z",
"__v": 0,
"type as": [] //<--- Need lead type name associated inside this
}
]
}
Input: "leadid": "5ece11cbac50c434dc4b7f29"
Any help appreciated.
[
{
$match: {
userId: mongoose.Types.ObjectId(userId),
},
},
{
$unwind: "$leads",
},
{
$match: {
'leads._id': idToSearch,
},
},
{
$lookup: {
from: 'leadscategories',
localField: 'leads.leadType',
foreignField: '_id',
as: 'type as'
}
},
]

Implementing joins on multiple collections

I am trying to create a social networking application which can have connect (followers, following), posts, comments, likes, shares, etc. This is a MVP project, but still i wanted to explore mongoDB for this use case. I am having some doubt regarding the performance of this application.
I have three collection:
Posts: This is where a new post shall be added. This collection contains all the details related to a post.
Schema:
const postSchema = new mongoose.Schema({
user_id: String,
title: String,
text: String,
type: { type: String, enum: ["music", "movie", "tv"] },
mediaUrl: String,
thumbnailUrl: String,
accessType: { type: Number, enum: [1, 2, 3] },
tags: [String],
like_count: Number,
comment_count: Number,
share_count: Number,
insertDate: {
type: Date,
default: () => {
return new Date();
}
}
});
Feeds: This collection just add a metadata of user and post including tags. This i intend to use to get the relevant feed for a particular user.
Schema:
const feedSchema = new mongoose.Schema({
post_id: String,
user_id: String,
isTag: Boolean,
isPublic: Boolean,
insertDate: {
type: Date,
default: () => {
return new Date();
}
},
modifiedDate: {
type: Date,
default: () => {
return new Date();
}
}
});
Connects: This collection is for the relationship of users.
Schema:
const connectSchema = new mongoose.Schema({
followed_by: String,
user_id: String,
insertDate: {
type: Date,
default: () => {
return new Date();
}
}
});
My approach was to first find the posts from feeds collection basis users whom I am following, then fetching the posts from post collection.
Here goes my attempted query:
db.connects.aggregate([
{ $match: { followed_by: "5cbefd61d3b53a4aaa9a2b16" } },
{
$lookup: {
from: "feeds",
let: { user_id: "$user_id" },
pipeline: [{ $match: { $expr: { $or: [{ $eq: ["$user_id", "$$user_id"] }, { isPublic: true }] } } }],
as: "feedData"
}
},
{ $unwind: "$feedData" },
{ $replaceRoot: { newRoot: "$feedData" } },
{ $group: { _id: { post_id: { $toObjectId: "$post_id" }, modifiedDate: { $toLong: "$modifiedDate" } } } },
{ $replaceRoot: { newRoot: "$_id" } },
{ $sort: { modifiedDate: -1 } },
{ $skip: 0 },
{ $limit: 10 },
{
$lookup: { from: "posts", localField: "post_id", foreignField: "_id", as: "postData" }
},
{ $unwind: "$postData" },
{ $replaceRoot: { newRoot: "$postData" } },
{ $addFields: { userId: { $toObjectId: "$user_id" } } },
{
$lookup: { from: "users", localField: "userId", foreignField: "_id", as: "userData" }
},
{
$project: {
post_id: "$_id",
user_id: 1,
title: 1,
text: 1,
typeaccessType: 1,
mediaUrl: 1,
thumbnailUrl: 1,
insertDate: { $toLong: "$insertDate" },
like_count: 1,
comment_count: 1,
share_count: 1,
user_email: { $arrayElemAt: ["$userData.email", 0] },
user_profile_pic: { $arrayElemAt: ["$userData.profile_pic", 0] },
username: { $arrayElemAt: ["$userData.firstname", 0] }
}
}
]).pretty();
Please share your feedback on:
Which index should I use to boost up the performance?
Is there is a better way of doing the same in mongoDB. Also if any part of the query can be optimised?

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