Node.js - Angular - MongoDB - for `userid` , get `username` - node.js

I have such mongoose.Schema:
User.js:
const mongoose = require('mongoose');
const bcrypt = require('bcrypt-nodejs');
const Schema = mongoose.Schema;
const UserSchema = new Schema({
username: String,
password: String,
email: String,
});
module.exports = mongoose.model('users', UserSchema, 'users');
product.js:
const mongoose = require('mongoose');
var mongoosePaginate = require('mongoose-paginate');
const Schema = mongoose.Schema;
const ProductSchema = new Schema({
userid: {type:String, required: true},
product_name: {type:String, required: true}
});
ProductSchema.plugin(mongoosePaginate);
module.exports = mongoose.model('products', ProductSchema, 'products');
method:
exports.selectUsersProductsCount = function (req, res, next) {
Product.aggregate([
{"$group" : {_id:{userid:"$userid" }, count:{$sum:1} } }
],
function(err, result) {
if(err){ res.status(400).json({ success: false, message:'Error processing request '+ err }); }
res.status(201).json({
success: true,
data: result
});
console.log(result);
})
}
result:
[ { _id: { userid: '5ab655bbb94733156c438112' }, count: 3 },
{ _id: { userid: '5ab425c6f5bff145304092f7' }, count: 1 } ]
I want the username field to be displayed as well. I do not know how to use join. Counts correctly, but does not display the username field.
How to correct the aggregate method to display:
userid, username, count
[ { _id: { userid: '5ab655bbb94733156c438112', username: 'Jon Alon' }, count: 3 },
{ _id: { userid: '5ab425c6f5bff145304092f7', username: 'Jonson Con' }, count: 1 } ]
examples data:
Product
{
_id:O bjectId(5ab7da972ade533790268f47),
userid:"5ab655bbb94733156c438112",
product_name:"gs",
__v:0
},{
_id: ObjectId(5ab7daa92ade533790268f48),
userid:"5ab655bbb94733156c438112",
product_name:"dg",
__v:0
}
User
{
_id: ObjectId(5ab655bbb94733156c438112),
username: "rrrr",
email:"rrrr",
__v:0
}

You have to use the $lookup function to achieve that but you need to have some property in common between the 2 collections.
You would need to add userId on UserSchema or some other property so you could "join"
In your example u could try
db.product.aggregate([ {$lookup:{ from: "user", localField: "ObjectId(userid)", foreignField: "ObjectId(_id)", as: "username" }}, {"$group" : {_id:{userid:"$userid", username : "$username" }, count:{$sum:1} } } ])

solution
products.aggregate([
{$lookup:{
from: "users",
localField: "ObjectId(userid)",
foreignField: "ObjectId(_id)",
as: "users"
}},
{ "$group" :
{
_id: { userid: "$userid" },
name: { $last: "$users.username" } ,
count: { $sum: 1 }
}
}, {$sort: {"_id.userid": 1}}
], function(err, result) {
console.log(result);
if(err){ res.status(400).json({ success: false, message:'Error processing request '+ err }); }
res.status(201).json({
success: true,
data: result
});
}
);
}
returns:
[ { _id: { userid: '5ab425c6f5bff145304092f7' },
name: [ 'cccc', 'rrrr', 'zzzz' ],
count: 3 },
{ _id: { userid: '5ab655bbb94733156c438112' },
name: [ 'cccc', 'rrrr', 'zzzz' ],
count: 1 },
{ _id: { userid: '5aba7e8c045115340496becd' },
name: [ 'cccc', 'rrrr', 'zzzz' ],
count: 2 } ]

Related

NodeJS, Mongoose prevent Push to write if same data is already in the array

I have app where I have Users. Every user can be an owner of an item or multiple items..
If user is already owner of that item prevent to push the item object into array, if already exists.
I already tried different solutions (I will write what I tried in the end of the question).
User model:
import * as mongoose from "mongoose";
const Schema = mongoose.Schema;
const UserSchema = new mongoose.Schema({
email: { type: String, required: true, min: 6, max: 255 },
password: { type: String, required: true, min: 4, max: 1024 },
role: { type: String, required: true, default: "User" },
owners: [
{
type: Schema.Types.ObjectId,
ref: "Owners",
required: false,
},
],
});
module.exports = mongoose.model("Users", UserSchema);
Add owner to user controller:
exports.addOwnerToUser = async (req: Request, res: Response) => {
try {
console.log("here");
let ObjectID = require("mongodb").ObjectID;
const mongoose = require("mongoose");
const user = {
email: req.body.email,
ownerId: req.body.ownerId,
};
const updatedUser = await User.findOneAndUpdate(
{
_id: req.params.userId,
owners: { $ne: req.body.ownerId },
},
{
$push: { owners: req.body.ownerId },
}
);
res.status(201).json({ sucess: true, msg: "User updated sucessfully" });
} catch (err) {
res.status(404).json(err);
}
};
I already tried solutions like this, but nothing works as expected.. (check the commented code)
exports.addOwnerToUser = async (req: Request, res: Response) => {
try {
console.log("here");
let ObjectID = require("mongodb").ObjectID;
const mongoose = require("mongoose");
// add get user and find if he already has this id.. if has then json 200
// if not i execute line 230
const user = {
email: req.body.email,
ownerId: req.body.ownerId,
};
/* const updatedUser = await User.findOneAndUpdate(
{ _id: req.params.userId },
{
"ownerId.ownerId": {
$ne: ObjectID(req.body.ownerId),
},
},
{
$addToSet: {
"ownerId.ownerId": ObjectID(req.body.ownerId),
},
},
{
new: true,
}
); */
const updatedUser = await User.findOneAndUpdate(
/* {
_id: req.params.userId,
},
{
$addToSet: {
owners: req.body.ownerId,
},
},
{
new: true,
} */
{
_id: req.params.userId,
owners: { $ne: req.body.ownerId },
},
{
$push: { owners: { ownerId: req.body.ownerId } },
}
);
console.log(updatedUser);
/* const updatedUser = await User.findOneAndUpdate(
{ _id: req.params.userId },
{
$push: { ownerId: { ownerId: req.body.ownerId } },
}
);
console.log(updatedUser); */
// $addToSet: { members: { name: 'something', username: 'something' } }
/*
User.findByIdAndUpdate(req.params.user_id,{$set:req.body},{new:true}, function(err, result){
if(err){
console.log(err);
}
console.log("RESULT: " + result);
res.send('Done')
});
};
*/
res.status(201).json({ sucess: true, msg: "User updated sucessfully" });
} catch (err) {
res.status(404).json(err);
}
};

Empty array output from the $lookup aggregation operator

I'm trying to get the most popular recipes.
I have 2 relevant collections: recipes and favoriterecipes.
Things to consider: I've double checked collections names and ids.
In the db itself the _recipe field in favoriterecipes is type string and in recipes it is an ObjectId. (maybe a type conversion is required? even though I didn't see such thing in "lookup" examples).
favoriteRecipe.js:
const mongoose = require("mongoose");
const Recipe = require("./recipe");
const User = require("./user");
const FavoritesRecipesSchema = new mongoose.Schema({
_recipe: { type: mongoose.Schema.Types.ObjectId, ref: Recipe },
_user: { type: mongoose.Schema.Types.ObjectId, ref: User },
});
module.exports = mongoose.model("FavoriteRecipe", FavoritesRecipesSchema);
recipe.js:
const mongoose = require("mongoose");
const User = require("./user");
const RecipeScheme = new mongoose.Schema({
name: String,
ingredients: [String],
instructions: String,
image: String,
date: { type: Date, default: Date.now },
tags: [String],
_user: { type: mongoose.Schema.Types.ObjectId, ref: User },
});
module.exports = mongoose.model("Recipe", RecipeScheme);
controller.js:
exports.popular = async function (req, res, next) {
try {
const popular_recipes = await favoriteRecipe.aggregate([
{
$group: {
_id: "$_recipe",
recipeCount: { $sum: 1 },
},
},
{ $sort: { recipeCount: -1 } },
{
$lookup: {
from: "recipes",
localField: "_id",
foreignField: "_id",
as: "recipe",
},
},
// { $unwind: "$recipe" },
// {
// $project: {
// _id: "$recipe",
// recipeCount: 1,
// },
// },
]);
res.json(popular_recipes);
} catch (error) {
next(error);
}
};
response output:
[
{
"_id": "6053349353b5f5632986b2c2",
"recipeCount": 3,
"recipe": []
},
{
"_id": "6053349353b5f5632986b2c3",
"recipeCount": 2,
"recipe": []
},
{
"_id": "605603945b4aeb0d2458153e",
"recipeCount": 1,
"recipe": []
}
]
Eventually I found that I have to convert the id string to an object id. the solution:
exports.popular = async function (req, res, next) {
try {
const popular_recipes = await favoriteRecipe.aggregate([
{
$group: {
_id: { $toObjectId: "$_recipe" },
recipeCount: { $sum: 1 },
},
},
{ $sort: { recipeCount: -1 } },
{
$lookup: {
from: "recipes",
localField: "_id",
foreignField: "_id",
as: "recipe",
},
},
{ $unwind: "$recipe" },
{
$project: {
_id: "$recipe",
recipeCount: 1,
},
},
]);
res.json(popular_recipes);
} catch (error) {
next(error);
}
};

How to get array of objects from current collection and data from another collection in same query

I have a users collection. Each user can have multiple friends whose _id values are stored in an array in their user document.
I want to render a list which has a user's list of friend names and under each individual name I want to have a location but the locations are in another document because each user can have multiple locations with no limit on the number, like their visited/favourite places.
These are the 2 queries in isolation but I need to somehow combine them.
This is the query which looks at the user ID values in the user's array of friends and then gets the relevant user info based on the ID.
friends = await User.findOne({ _id: userId })
.populate("friends", "name mobile", null, { sort: { name: 1 } })
.exec();
Then, to get the places for a particular user:
const place = await Place.find({ creator: userId });
So, I basically want to list the friends in a loop, each with their places under their name like:
Joe Soap
- London Bridge
- Eiffel Tower
Bob Builder
- Marienplatz
The data in mongoDb look like this:
Users:
{
"_id": {
"$oid": "5f2d9ec5053d4a754d6790e8"
},
"friends": [{
"$oid": "5f2da51e053e4a754d5790ec"
}, {
"$oid": "5f2da52e053d4a754d6790ed"
}],
"name": "Bob",
"email": "bob#gmail.com",
"created_at": {
"$date": "2020-08-07T18:34:45.781Z"
}
}
Places:
{
"_id": {
"$oid": "5f3695d79864bd6c7c94e38a"
},
"location": {
"latitude": -12.345678,
"longitude": 12.345678
},
"creator": {
"$oid": "5f2da51e053e4a754d5790ec"
},
}
The first join is basically getting data from the array inside the same user collection. The second one is getting data from another collection, places.
UPDATE: almost working
friends = await User.aggregate([
{ $match: { _id: new mongoose.Types.ObjectId(userId) } },
{
$lookup: {
from: "users",
localField: "friends",
foreignField: "_id",
as: "friends_names",
},
},
{ $unwind: "$friends_names" },
{
$lookup: {
from: "places",
localField: "friends",
foreignField: "creator",
as: "friends_places",
},
},
{ $unwind: "$friends_places" },
{
$project: {
"friends_names.name": 1,
"friends_places.saved_at": 1,
},
},
]);
Data returned:
[
{
_id: 5f2d9ec5053d4a754d6790e8,
friends_names: { name: 'Bob' },
friends_places: { saved_at: 2020-08-17T13:40:28.334Z }
},
{
_id: 5f2d9ec5053d4a754d6790e8,
friends_names: { name: 'Natalie' },
friends_places: { saved_at: 2020-08-17T13:40:28.334Z }
}
]
Edit Okay, I believe I have reverse engineered your minimal schema correctly:
const mongoose = require("mongoose");
mongoose.connect("mongodb://localhost/test", { useNewUrlParser: true });
mongoose.set("debug", true);
const db = mongoose.connection;
db.on("error", console.error.bind(console, "connection error:"));
db.once("open", async function () {
await mongoose.connection.db.dropDatabase();
// we're connected!
console.log("Connected");
const userSchema = new mongoose.Schema({
friends: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }],
name: String,
});
const placesSchema = new mongoose.Schema({
latitude: String,
creator: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
});
const User = mongoose.model("User", userSchema);
const Place = mongoose.model("Place", placesSchema);
const bob = new User({ name: "Bob", friends: [] });
await bob.save();
const natalie = new User({ name: "Natalie", friends: [bob] });
await natalie.save();
//const chris = new User({ name: "Chris", friends: [] });
//await chris.save();
const john = new User({ name: "John", friends: [natalie, bob] });
await john.save();
const place1 = new Place({ latitude: "Place1", creator: bob });
const place3 = new Place({ latitude: "Place3", creator: bob });
const place2 = new Place({ latitude: "Place2", creator: natalie });
await place1.save();
await place2.save();
await place3.save();
await User.find(function (err, users) {
if (err) return console.error(err);
console.log(users);
});
await Place.find(function (err, places) {
if (err) return console.error(err);
//console.log(places);
});
var cc = await mongoose
.model("User")
.aggregate([
{ $match: { _id: john._id } },
{ $unwind: "$friends" },
{
$lookup: {
from: "places",
localField: "friends",
foreignField: "creator",
as: "friends_places",
}
}, { $lookup: {from: 'users', localField: 'friends', foreignField: '_id', as: 'friend_name'} },//, { $include: 'friends' }
{ $unwind: "$friends_places" }, { $unwind: "$friend_name" }//, { $skip: 1}, {$limit: 1}
])
.exec();
console.log(cc);
});
This is the most relevant part:
var cc = await mongoose
.model("User")
.aggregate([
{ $match: { _id: john._id } },
{ $unwind: "$friends" },
{
$lookup: {
from: "places",
localField: "friends",
foreignField: "creator",
as: "friends_places",
}
}, { $lookup: {from: 'users', localField: 'friends', foreignField: '_id', as: 'friend_name'} },//, { $include: 'friends' }
{ $unwind: "$friends_places" }, { $unwind: "$friend_name" }//, { $skip: 1}, {$limit: 1}
])
.exec();
The first unwind: friends 'flattens' the collections initially. So, basically we've got 'userId | friendId' for each user and friend. Then, for each row, we simply look up places created by him ($lookup). Finally, we unwind friends_places because we don't want them to be rendered as [object] in console output. Additionally, there this $match, because we only want to check one user's friends' places. Considering we want to know friend's name as well, we have to do one more join - this is why there's this second $lookup. After that a simple $unwind to get friend's detail and that's it.
Code yields the following:
[ { _id: 5f3aa3a9c6140e3344c78a45,
friends: 5f3aa3a9c6140e3344c78a44,
name: 'John',
__v: 0,
friends_places:
{ _id: 5f3aa3a9c6140e3344c78a48,
latitude: 'Place2',
creator: 5f3aa3a9c6140e3344c78a44,
__v: 0 },
friend_name:
{ _id: 5f3aa3a9c6140e3344c78a44,
friends: [Array],
name: 'Natalie',
__v: 0 } },
{ _id: 5f3aa3a9c6140e3344c78a45,
friends: 5f3aa3a9c6140e3344c78a43,
name: 'John',
__v: 0,
friends_places:
{ _id: 5f3aa3a9c6140e3344c78a46,
latitude: 'Place1',
creator: 5f3aa3a9c6140e3344c78a43,
__v: 0 },
friend_name:
{ _id: 5f3aa3a9c6140e3344c78a43,
friends: [],
name: 'Bob',
__v: 0 } },
{ _id: 5f3aa3a9c6140e3344c78a45,
friends: 5f3aa3a9c6140e3344c78a43,
name: 'John',
__v: 0,
friends_places:
{ _id: 5f3aa3a9c6140e3344c78a47,
latitude: 'Place3',
creator: 5f3aa3a9c6140e3344c78a43,
__v: 0 },
friend_name:
{ _id: 5f3aa3a9c6140e3344c78a43,
friends: [],
name: 'Bob',
__v: 0 } } ]
So, we've got a flat list of John's friends' places.
Important bit: from: places in $lookup is critical, as we have to use MongoDB's name of the collection, not model name 1.

How to populate in 3 collection in mongoDB with Mongoose

I have three collections such as User, Program, and `Agenda. Those models as follows.
User Model
const mongoose = require('mongoose');
const UserSchema = mongoose.Schema({
name: {type:String},
email: {type:String}
},{timestamps:true
}
);
module.exports = mongoose.model('User', UserSchema);
Program Model
const mongoose = require('mongoose');
const NoteSchema = mongoose.Schema({
name: {type:String},
timefrom: {type:Date},
timeto: {type:Date},
status: {type:String},
venue: {type:String},
timetype: {type:Number},
userid:{type:mongoose.Schema.Types.ObjectId,ref : 'User', required: true},
logo :{type:String,default: 'programe'}
},{timestamps:true
});
module.exports = mongoose.model('Program', NoteSchema);
Agenda Model
const mongoose = require('mongoose');
const AgendaSchema = mongoose.Schema({
name: {type:String},
timefrom: {type:Date},
timeto: {type:Date},
status: {type:String},
proorder: {type:String},
proid:{type:mongoose.Schema.Types.ObjectId,ref : 'Program', required: true}
},
{timestamps:true}
);
module.exports = mongoose.model('Agenda', AgendaSchema);
Now I only get agenda and program data only.
Agenda Controller
// Retrieve and return all agenda from the database.
exports.findAll = (req, res) => {
Agenda.find()
.populate('proid')
//.populate('userid')
.then(agendas => {
res.send(agendas);
}).catch(err => {
res.status(500).send({
message: err.message || "Some error occurred while retrieving agenda."
});
});
};
When I go to this URL and GET method I want to populate agenda document(done), related program document(done) and related user document(this I want)?
The wanted query as like this
SELECT *
FROM users, programs, agendas
WHERE agendas.proid = programs.id AND programs.userid = users.id
You can either use $lookup aggregation
Agenda.aggregate([
{ "$lookup": {
"from": Program.collection.name,
"let": { "proid": "$proid" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$proid" ] } } },
{ "$lookup": {
"from": User.collection.name,
"let": { "userid": "$userid" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$userid" ] } } },
],
"as": "userid"
}},
{ "$unwind": "$userid" }
],
"as": "proid"
}},
{ "$unwind": "$proid" }
])
Or with populate
Agenda.find()
.populate([{ path: 'proid', populate: { path: 'userid' }}])
Both will give you the same result
You can use .populate() and { populate : { path : 'field' } }.
Example:
Agenda.find(). //Collection 1
populate({path:'proid', //Collection 2
populate:{path:'userid' //Collection 3
}})
.exec();
After the 4 collection, you need to identify the model, if it can't be wrong
Example:
Agenda.find(). //Collection 1
populate({path:'proid', //Collection 2
populate:{path:'userid', //Collection 3
populate:{path:'otherFiel', model: Collection //Collection 4 and more
}}})
.exec();
And to finish, if you want get only some fields of some Collection, you can use the propiertie select
Example:
Agenda.find().
populate({path:'proid', select:'name status venue'
populate:{path:'userid'
}})
.exec();

Populate a Nested Mongoose Object w/ a Nested Object

Working on my first project and have been stumped on this for a couple days.
I'm trying to populate an object that contains the brewery info and the single corresponding beer from the Beer model.
models.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var beerSchema = Schema({
breweryName: String,
beer: [{
beerName: String
}]
});
var draftlistSchema = Schema ({
userName: String,
tap: [{
tapNo: Number,
_tapBeer: { type: Schema.Types.ObjectId, ref: 'Beer' },
tapStatus: String
}]
});
var Draftlist = mongoose.model('Draftlist', draftlistSchema);
var Beer = mongoose.model('Beer', beerSchema);
module.exports = {
Draftlist: Draftlist,
Beer: Beer
}
route.js
var getDraftlist = function(user, callback) {
models.Draftlist.findOne({ 'userName': user }).populate( {
path: 'tap._tapBeer',
model: 'Draftlist'
}).exec(function(err, draftlist) {
// console.log(draftlist.tap)
console.log(draftlist);
callback(draftlist);
});
};`
I'm getting a null returned with the current code. Ideally I would like the returned object to look something like--
{
breweryName: Some Brewery,
beer: {// Beer object that was referenced by Id //}
}
Draftlist Object w/ null return
{ _id: 590bd0615a190204fca6d467,
userName: 'A New User1',
__v: 1,
tap:
[ { tapNo: 1,
_tapBeer: null,
tapStatus: 'onTap',
_id: 590bd0615a190204fca6d46d },
{ tapNo: 2,
_tapBeer: null,
tapStatus: 'onTap',
_id: 590bd0615a190204fca6d46c },
{ tapNo: 3,
_tapBeer: null,
tapStatus: 'onTap',
_id: 590bd0615a190204fca6d46b },
{ tapNo: null,
_tapBeer: null,
tapStatus: 'Cellar',
_id: 590bd0615a190204fca6d46a },
{ tapNo: null,
_tapBeer: null,
tapStatus: 'Cellar',
_id: 590bd0615a190204fca6d469 },
{ tapNo: null,
_tapBeer: null,
tapStatus: 'Cellar',
_id: 590bd0615a190204fca6d468 },
{ _tapBeer: null,
tapStatus: 'Cellar',
_id: 590bd0735a190204fca6d470 } ] }
Beer Object
{ breweryName: 'Beachwood',
_id: 590bd3f1ed13510514405cba,
beer: [ { beerName: 'IPA', _id: 590bd3f1ed13510514405cbb } ] }
Use mongoose-deep-populate
const
mongoose = require('mongoose'),
deepPopulate = require('mongoose-deep-populate')(mongoose);
Schema = mongoose.Schema;
const
BeerSchema = Schema({
breweryName: Schema.Types.String,
beer: [{
beerName: Schema.Types.String
}]
}),
DraftlistSchema = Schema ({
userName: Schema.Types.String,
tap: [{
tapNo: Schema.Types.Number,
_tapBeer: {
type: Schema.Types.ObjectId,
ref: 'Beer'
},
tapStatus: Schema.Types.String
}]
});
DraftlistSchema.plugin(deepPopulate);
var Draftlist = mongoose.model('Draftlist', DraftlistSchema);
var Beer = mongoose.model('Beer', BeerSchema);
module.exports = {Draftlist, Beer};
usage:
const getDraftlist = (userName, callback) => {
models
.Draftlist
.findOne({
userName
})
.deepPopulate('tap._tapBeer')
.exec((err, draftlist) => {
console.log(draftlist);
callback(draftlist);
});
};
or:
const Draftlist = models.Draftlist;
const getDraftlist = (userName, callback) => {
Draftlist
.findOne({
userName
})
.exec((err, draftlist) => {
Draftlist
.deepPopulate(
draftlist, 'tap._tapBeer',
(err, draflist) => {
console.log(draftlist);
callback(draftlist);
});
});
};

Resources