Search and lookup arrays nested in multiple objects - node.js

I find all websites that match the IDs I have in my array and in the below case it is two websites. I then want to look inside each of the conversations arrays of those websites and search a different collection called conversations for conversations that match those IDs. I then want to grab some/all of the fields from those conversations and add it to the JSON document I already have, grouping them by conversation within each website. I've been playing around with aggregate, unwind, and group but can't seem to get anything to work.
router.post('/staffLoadUpTwo', async (req, res) => {
var userId = '5e8f964a9c2d0780c0163825';
const company = await Company.findOne({ 'roles.admins': userId });
var unwrappedCompany = JSON.parse(JSON.stringify(company));
console.log(unwrappedCompany.websites);
const website = await Website.find({
_id: { $in: unwrappedCompany.websites },
});
// const unwindTest = await Website.aggregate([{$unwind : "$conversations"}]);
console.log(website);
});
console.log(website);
[ { conversations: [ '5e90d9ceb089812c9ba1a67b', '5e8f5a6a2582bf629998c3fd' ],
_id: 5e949cc02483c0c0056a1a98,
domain: 'http://x.com',
__v: 0 },
{ conversations: [ '5e8e23595ce6d611cec5033f', '5e8e3afee8e95e1ff94650d3' ],
_id: 5e949ce8f53450c0341b36cd,
domain: 'http://y.com',
__v: 0 } ]
ideal output
[{
_id: "5e949cc02483c0c0056a1a98",
domain: 'http://x.com'
conversations: [
{conversationId: "5e90d9ceb089812c9ba1a67b", messages: {messageArray: ['a'], timeSent: 2}},
{conversationId: "5e8f5a6a2582bf629998c3fd", messages: {messageArray: ['b'], timeSent: 6}}
]
}
_id: "5e949ce8f53450c0341b36cd",
domain: 'http://y.com'
conversations: [
{conversationId: "5e8e23595ce6d611cec5033f", messages: {messageArray: ['c'], timeSent: 1}},
{conversationId: "5e8e3afee8e95e1ff94650d3", messages: {messageArray: ['d'], timeSent: 8}}
]
}]

You need not stress yourself with MongoDB aggregation. Since you're using Mongoose, you can easily use mongoose populate to achieve the result you described in the question.
Provided you've defined the website scheme to be something like this:
const websiteSchema = {
// ...schema definition for other properties
/* Note the ref property used below, the value must be the name of the
conversation model, i.e the stringValue you passed into
mongoose.model(<stringValue>, conversationSchema); */
conversations: [ { type: mongoose.Types.ObjectId, ref: 'Conversations' } ]
}
A mongoose query like this:
const website = await Website.find({
_id: { $in: unwrappedCompany.websites },
}).populate('conversations');
will output an array of website documents whose conversations field are populated i.e, you get the actual conversation document and not just their _ids.
More about Mongoose populate here

Related

How to get categories and sub-categories in single API response

I have two collections Categories and Subcategories inside a Categories collection. I have an array that is storing the ids of subcategories that are stored in Subcategories collection. Below is my document structure:
Categories collection
{
id:65,
title:"Automotive",
active:true,
subcategories:[35,28,30]
}
subcategories collection
{
id:35,
title:"Automotive technology",
category_id:65,
active:true
},
{
id:28,
title:"Automotive coatings",
category_id:65,
active:true
},
{
id:30,
title:"Machinery and equipments",
category_id:65,
active:true
}
As seen in above collection 3 documents from subcategories collection have been associated with the category document. I want to fetch data in below format on single API hit.
API response should be in below format:
{
data:{
category:{
id:65,
title:"Automotive",
subcategories:[{
id:35,
name:"Automotive technology",
},
{
id:28,
name:"Automotive coatings",
},
{
id:30,
name:"Machinery and equipments",
}]
},
category:{
id:66,
title:"Food",
subcategories:[{
id:23,
name:"Drinks",
},
{
id:2,
name:"Additives",
}]
},
},
messsage:"Success",
code:200
}
As of now I am able to get data in 2 api hits that is like first getting all the categories
const category = await db.categories.find({});
Then on click of some particular category fetching all the sub categories based on the category id.
const subCategories = await db.SubCategories.find({category_id:id});
How can I get the above data in desired format in single API hit?
You need something like this, also, if you use mongoose, you can use .populate()
To format data you can use $project stage in aggregation pipeline or projection in .find()
If you want to use Mongoose with populate:
CategorySchema:
const CategorySchema= new mongoose.Schema({
...
subCategories: [{ type: mongoose.Schema.Types.ObjectId, ref: 'SubCategory' }],
...
});
need _id column on reference table
ref content is must be equal to model name like
module.exports = mongoose.model('SubCategory', SubCategorySchema);
Controller:
const categories = await Category.find({})
.populate({
path: 'subCategories'
})
path content is must be equal to column name
If you want to write with Mongo query:
db.getCollection("categories").aggregate([
{
$lookup:
{
from: 'subCategories',
localField: 'subCategories',
foreignField: 'id',
as: 'subCategories'
}
}
])
You get empty object because you are not using "_id" field for join schemas. If you want to use "id" field u need to use virtuals on Mongoose:
https://mongoosejs.com/docs/tutorials/virtuals.html#populate

Mongoose - How to populate only the first element in a nested array of every object

I am trying to create a Chat App in NodeJS, Mongoose and React Native and I want to show to the user the last message of every conversation.
I have an array of messages in my Conversation Schema as following:
const ConversationSchema = new mongoose.Schema({
{...}
messages: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Message'
}
]
{...}
})
I wonder if anyone can help me to be able to only populate the first message of every Conversation so the response will be:
conversations: [
{
"_id": ObjectId(...)
"messages": [
"text": "Last message"
]
},
{
"_id": ObjectId(...)
"messages": [
"text": "Last message"
]
},
...
]
I am currently using the populate function of mongoose but the problem is that if only populates the first conversation:
Conversation.find().populate(query).exec((err, conversations => {
{...}
})
const query = {
{
path: "messages",
options: {
limit: 1,
sort: { _id: -1 }
}
}
}
Note: If I do not specify the limit: 1 and sort: { _id: -1 } it correctly populates all elements of the array but that's not what I am looking for.
Thanks to anyone who can help me!
You need to use perDocumentLimit than Limit
If you need the correct limit, you should use the perDocumentLimit option (new in Mongoose 5.9.0). Just keep in mind that populate() will execute a separate query for each story, which may cause populate() to be slowe

transform raw query to mongodb query the efficient way

In a nodejs app with mongodb storage, I have the following query from user:
const rawQuery = [
'{"field":"ingredient","type":"AND","value":"green and blue"}',
'{"field":"ingredient","type":"AND","value":"black"}',
'{"field":"ingredient","type":"OR","value":"pink"}',
'{"field":"ingredient","type":"OR","value":"orange"}',
'{"field":"place","type":"AND","value":"london"}',
'{"field":"school","type":"NOT","value":"fifth"}',
'{"field":"food","type":"OR","value":"burger"}',
'{"field":"food","type":"OR","value":"pizza"}',
'{"field":"ownerFirstName","type":"AND","value":"Jimmy"}'
];
I have a collection called restaurant, and a collection called owners.
Would this query aim to handle such a search scenario?
const query = {
$and: : [
{ ingredient: 'green and blue' },
{ ingredient: 'black' },
{ $or : [
{ ingredient: 'pink' },
{ ingredient: 'orange' },
]
},
{ place: 'london' },,
{ school: { $ne: 'fifth' } },
{ $or : [
{ food: 'burger' },
{ food: 'pizza' },
]
}
]
};
How can I transform the rawQuery into this mongo query? (Given that it has to be dynamic, because I have many fields, and in this example I just included a couple of them.)
This example query aims to get the restaurants that match the description/place/school/food queries in the restaurant and also to match the owner's first name from another collection. Each restaurant document will have a ownerUuid field that points to the owner in the other collection.
What is the best solution to do a search in the mongodb for such a query in production env?
How can this be achieved with Elasticsearch?

populate with condition in mongoose

I have a movie booking data like below
movie order schema
{
"movie_id": "5d64fb7975214a183bf10f5b",
"variant_id": "5d64fda8fc7f911a77afd55c",
}
and movie schema data like below
{
"_id":"5d64fb7975214a183bf10f5b",
"description":"Sahoo movie ",
"options":[
],
"variants":[
{
"enabled":true,
"_id":"5d64fda8fc7f911a77afd55c",
"variant_name":"",
"regular_price":345,
"sale_price":125,
"stock_quantity":45,
},
{
"enabled":true,
"_id":"5d661c8181a4572a27f048dd",
"variant_name":"",
"regular_price":120,
"sale_price":50,
"stock_quantity":10,
}
],
"on_sale":false,
"variable":true
}
now I'm trying to querying the movie order
let data = await MovieOrder.find().populate(movie_id)
but it is giving movie details with all the variant.but what I'm looking for here is
In the movie order what is the variant_id is present based on that I need to populate the movie with variant based on that variant id on the movie order
Note: the result should be, what are the variant_id in the movie order schema is equal to variant id in the movie schema
Hope you guys understand my problem, please give me the solution
With the way your schema is designed it is hard for populate to filter the movies variants array with the variant_id in the movies order
as this is not how populate works.
In order to use populate properly, you would have to change the movies schema making the variants array as ref to
Variants model. For example, your schema definitions would need to look like
Schema definitions
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const variantSchema = new Schema({
enabled: Boolean,
variant_name: String,
regular_price: Number,
sale_price: Number,
stock_quantity: Number
})
const Variant = mongoose.model('Variant', variantSchema)
const movieSchema = new Schema({
description: String,
options: [String],
variants: [{ type: 'ObjectId', ref: 'Variant' }],
on_sale: Boolean,
variable: Boolean
})
const Movie = mongoose.model('Movie', movieSchema)
await MovieOrder.find().populate('movie_id variant_id')
This way your query just returns the movie and the variant it needs.
However, if the current schema design remains as it is, you can use $lookup in an aggregate pipeline to do the populate and then filter the resulting array using $filter on the variant that matches the variant_id field in your MovieOrder model:
await MovieOrder.aggregate([
{ '$lookup': {
'from': 'movies',
'localField': "movie_id", // field in the movieorders collection
'foreignField': "_id", // field in the movies collection
'as': "movie"
} },
{ '$addFields': {
'movie': { '$arrayElemeAt': ['$movie', 0 ] }
} },
{ '$addFields': {
'movie.variants': {
'$filter': {
'input': '$movie.variants',
'cond': { '$eq': ['$variant_id', '$$this._id'] }
}
}
} },
]).exec()

How to acess Subdocuments with reference in Mongoose?

I want to acess Subdocument using Mongoose
This is my conversations schema :
const Conversations = new mongoose.Schema({
userOneId: {
type: Schema.Types.ObjectId,
ref: 'User'
},
userTwoId: {
type: Schema.Types.ObjectId,
ref: 'User'
}
This is my User Model Schema:
....
conversations: [{ type: Schema.Types.ObjectId, ref: 'Conversations' }]
});
After inserted i get this:
{
"_id": {
"$oid": "5a6fa114ffc53523705d52af"
},
"created_at": {
"$date": "2018-01-29T22:32:52.930Z"
},
"messages": [],
"__v": 0
}
I have inserted this :
"conversations": [
{
"$oid": "5a6fa14a5562572a541bacae"
},
I have put this:
Object.assign(conversation, {userOneId: user._id});
Object.assign(conversation, {userTwoId: friend._id});
I want to acess the "$oid": "5a6fa114ffc53523705d52af" to get the userOneId and userTwoId information.
you need to use populate.
Basically, inside the "conversations" property in a user object, you only have the ObjectId of conversations, not the entire mongoose object. when you're querying the database for a user/users, you have to tell mongoose that you want it to replace the ObjectIds with the entire objects.
//lets say you have a variable, userId, inside of which you have the id
//of a user, and now you're querying the database for that user
User.findById(userId).populate("conversations")
.exec(function(err, foundUser){
if(err){
console.log(err):
} else {
console.log(foundUser.conversations);
}
});
if you were to run the code above with an actual user _id, then (unless you got an error) you would have printed in your console rather than an array of conversation mongoose ids, an array of conversation mongoose objects. the entire things.
if you only wanted the conversations to have two properties, userOneId and userTwoId, then combine populate with select. instead of
.populate("conversations")
use
.populate({
path: "conversations",
select: "userOneId userTwoId"
})

Resources