Checking if a user has a certain role - javascript

So I use mongoDb in my express server and store the user information like this:
var UserSchema = new Schema({
username: { type: String, required: true, index: { unique: true } },
password: { type: String, required: true },
email: {type: String, required: true , index: { unique: true } },
isActive: {type:Boolean, default:false},
signUpDate: {type: Date, default: Date.now()},
roles: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Role"
}
]
});
With Role having a simple single field : name.
To find out if a user is an admin for example I do:
User.findById(req.SecureUserId).populate('roles').exec((err, user) => {
if (err) {
res.status(500).send({ message: err });
return;
}
for(let roles of user.roles){
if(roles.name === 'admin'){
next();
return;
}
}
However, this seems like something I can better query to my database ?
How would I do this in mongoDB?

You can use .aggregate() to "join" single user with his role and then run $match to verify if there's any role named admin. If particular user doesn't have such role then aggregation will return an empty array:
User.aggregate([
{ $match: { _id: req.SecureUserId } },
{
$lookup: {
from: "roles",
localField: "roles",
foreignField: "_id",
as: "roles"
}
},
{ $match: { "roles.name": "admin" } }
]).exec((err, user) => {
if(user.length === 1){
next();
}
})

Related

Populating in Mongodb Aggregating

I just asked a related question here:
Mongoose/Mongodb Aggregate - group and average multiple fields
I'm trying to use Model.aggregate() to find the average rating of all posts by date and then by some author's subdocument like country.name or gender. Having trouble with this though. I know for the first stage I just need to use $match for the date and I think I need to use $lookup to "populate" the author field but not sure how to implement this.
This works for finding an average rating for all posts by date:
Post.aggregate([
{ $group: { _id: "$date", avgRating: { $avg: '$rating' }}}
]).
then(function (res) {
console.log(res);
})
And this is basically what I want to do but it doesn't work:
Post.aggregate([
{$match: {"date": today}},
{$group: {_id: {"country": "$author.country.name"}, avgRating: {$avg: "$rating"}}}
]).then(function(res) {
console.log(res)
})
User model:
const userSchema = new Schema({
email: {
type: String,
required: true,
unique: true
},
birthday: {
type: Date,
required: true,
},
gender:{
type: String,
required: true
},
country:{
name: {
type: String,
required: true
},
flag: {
type: String,
// default: "/images/flags/US.png"
}
},
avatar: AvatarSchema,
displayName: String,
bio: String,
coverColor: {
type: String,
default: "#343a40"
},
posts: [
{
type: Schema.Types.ObjectId,
ref: "Post"
}
],
comments: [
{
type: Schema.Types.ObjectId,
ref: "Comment"
}
],
postedToday: {
type: Boolean,
default: false
},
todaysPost: {
type: String
}
})
You can populate an aggregation after you fetched the data from the MongoDB. Your `Query will look a bit like this:
modelName.aggregate([{
$unwind: ''//if Needed
}, {
$group: {
_id: {"country":"$author.country.name"},
avgRating: {
$avg: '$rating'
}
}])
.exec(function(err, transactions) {
// ERRORHANDLING
// CallsBacks
modelName.populate(columnName, {path: '_id'}, function(err, populatedModel) {
// Your populated columnName inside TaleName
});
});

findByIdAndUpdate pull from array objects equal to a specific value

I want to remove one or more objects of type tweet from the timeline list within the user model. The tweet objects that I want to remove are those whose author id matches a specific id user._id.
I have tried this:
router.get("/follow/:userId", isLoggedIn, catchAsync(async (req, res) => {
try {
const currentUser = await User.findById(req.user._id).populate("timeline")
const user = await User.findById(req.params.userId).populate("followers tweets")
for (let tweet of currentUser.timeline) {
if (tweet.author._id.equals(user._id)) {
currentUser.timeline.pull(tweet._id)
}
}
req.flash("error", `Unfollowed to ${user.username}`)
user.save();
currentUser.save()
res.redirect(`/${user._id}`)
} catch (err) {
req.flash("error", err.message);
res.redirect("back")
}
}));
and this:
await User.findbyIdAndUpdate(currentuser._id, { $pull: { timeline: { author : user._id } } }
but none of them are working.
My user model:
const userSchema = new Schema({
name: {
type: String,
required: true
},
biography: { type: String, maxlength: 160 },
location: {type: String, maxlength: 30 },
email: {
type: String,
unique: true,
required: true
},
image: {
url: String,
filename: String,
},
followers: [{ type: Schema.Types.ObjectId, ref: "User" }],
following: [{ type: Schema.Types.ObjectId, ref: "User" }],
tweets: [{ type: Schema.Types.ObjectId, ref: "Tweet"}],
timeline: [{ type: Schema.Types.ObjectId, ref: "Tweet"}]
});
My tweet model :
const tweetSchema = new Schema({
images: [{
url: String,
filename : String
}],
text: { type: String, maxlength: 260},
date: { type: Date, default: Date.now },
author: { type: Schema.Types.ObjectId, ref: "User" },
parent: { type: Schema.Types.ObjectId, ref: "Tweet", default:null },
replies: [{ type: Schema.Types.ObjectId, ref: "Tweet" }],
likes: [{ type: Schema.Types.ObjectId, ref: "User" }],
retweets: [{ type: Schema.Types.ObjectId, ref: "Tweet" }],
retweetStatus: {type: Schema.Types.ObjectId, ref: "Tweet", default: null}
});
If your collection looks like this:
[
{
"_id" : ObjectId("60254276259a60228cbe5707"),
"name" : "Mary",
"timeline" : [
ObjectId("60254276259a60228cbe5703"),
ObjectId("60254276259a60228cbe5704"),
ObjectId("60254276259a60228cbe5705")
]
},
{
"_id" : ObjectId("60254276259a60228cbe5706"),
"name" : "Dheemanth",
"timeline" : [
ObjectId("60254276259a60228cbe5700"),
ObjectId("60254276259a60228cbe5701"),
ObjectId("60254276259a60228cbe5702")
]
}
]
then the solution is:
usersSchema.updateOne(
{
"_id": ObjectId("60254276259a60228cbe5706"),
"timeline": ObjectId("60254276259a60228cbe5700"),
},
{
$pull: {
"timeline": ObjectId("60254276259a60228cbe5700")
}
}
)
.then()
.catch()
// or
usersSchema.findOneAndUpdate(
{
"_id": ObjectId("60254276259a60228cbe5706"),
"timeline": ObjectId("60254276259a60228cbe5700"),
},
{
$pull: {
"timeline": ObjectId("60254276259a60228cbe5700")
}
},
{
new: true
}
)
.then()
.catch()
I finally found the issue! The problem I was having is that I was trying to remove items from a list of objects while looping through that list. The solution is easy: you can just create an auxiliar empty array and push the items that you want to remove, then loop through that auxiliar array and pull the items from the original array.
In my case, I've already had an array with the tweets that I wanted to remove, user.tweets. The solution is:
router.get("/follow/:userId", isLoggedIn, catchAsync(async (req, res) => {
try {
const currentUser = await User.findById(req.user._id).populate("timeline")
const user = await User.findById(req.params.userId).populate("followers tweets")
for (let tweet of user.tweets) {
currentUser.timeline.pull(tweet._id)
}
req.flash("error", `Unfollowed to ${user.username}`)
user.save();
currentUser.save()
res.redirect(`/${user._id}`)
} catch (err) {
req.flash("error", err.message);
res.redirect("back")
}
}));

aggregate then update to other collection

I want to insert the returned data of my aggregate (controller function) to another collection called User. Below is my schema for the User:
const userSchema = new mongoose.Schema({
firstName: {
type: String,
required: [true, 'First name is required']
},
lastName: {
type: String,
required: [true, 'Last name is required']
},
email: {
type: String,
required: [true, 'Email is required']
},
loginType: {
type: String,
required: [true, 'Login type is required']
},
password: {
type: String
},
transactions: [{transactionId: String}, date:{type:Date, default:new Date()}],
totalIncome : [{id:String},{totalIncome:Number}]
})
module.exports = mongoose.model('User', userSchema);
I want to insert the result of this controller function into User collection as another variable
return Transaction.aggregate([ { $match : { userId : userId, type:"Income",isActive:true} }, {
$group: {
_id: "$type",
totalIncome: {
$sum: "$amount"
}
}
}
]).then((totalIncome,err)=> {
return (err) ? false : totalIncome
})
The result of my aggregate above is this:
[
{
"_id": "Income",
"totalIncome": 18000
}
]
Please help :( I tried adding $out but it replaces the whole user collection.
I just want it to add to a specific user and update its content every time I use the controller function above. Thank you!
return Transaction.aggregate([ { $match : { userId : userId, type:"Income",isActive:true} }, {
$group: {
_id: "$type",
amount: {
$sum: "$amount"
}
}
}
]).then((totalIncome,err)=> {
// return (err) ? false : totalIncome
console.log(totalIncome)
if (err) {
return false
} else {
User.findByIdAndUpdate(userId, {"$set" : {"totalIncome": totalIncome[0].amount }}, { "new": true })
.then((transaction, err ) => {
return (err) ? false : true
})
}
})
I was able to add totalIncome to my User collection by using findByIdAndUpdate() and $set operator. Feel free to provide different solution for my question above.

How can I limit the returned items from a Mongoose array populate?

I have a model that we use for a conversational chat, that uses nested arrays. Each time a user sends a message, it gets pushed into the messages array. This is my model:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const ConversationSchema = new Schema({
sender: {
type: Schema.Types.ObjectId,
ref: 'User'
},
messages: [
{
message: String,
meta: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
delivered: Boolean,
read: Boolean
}
]
}
],
is_group_message: { type: Boolean, default: false },
participants: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
delivered: Boolean,
read: Boolean,
last_seen: Date
}
]
}, { timestamps: true });
module.exports = mongoose.model('Conversation', ConversationSchema);
I'm trying to find all conversations, that a user is a part of, but I'm unable to return only the most recent chat message, and it seems to ignore the options. Is this a model or populate issue on my end?
Conversation.find({ 'participants.user': { $all: [req.user._id] } })
.sort('-createdAt')
.populate({
path: 'sender',
select: '-_id -password -email -lastLogin -createdAt -updatedAt -__v',
})
.populate({
path: 'messages.meta.user',
select: 'username avatar',
options: {
limit: 1
}
})
.populate({
path: 'participants',
path: 'participants.user',
select: 'username avatar'
})
.then(conversation => {
if (!conversation) {
errors.noconversation = 'No conversation could be found';
return res.status(404).json(errors);
}
res.json(conversation)
})
.catch(err => res.status(404).json({ conversation: 'There are no conversations or an error occurred' }));

Mongoose node return multiple arrays of referenced objects from document

I am working on a node backend API with mongoose. I have 2 schemas one User schema and one Follow schema(saved as users and follows in mongo). The follow schema fields followers and following hold an array of ObjectIds that refer to User objects. I am trying to return the Referenced objects in in the arrays so that I can respond with an object to the client that contains an array of userFollowing and userFollowers containing user objects, a but I am unable to populate the output.
I will also need to be able to filter the returned objects to only return 'username bio image email first_name surname join_date'.
My current incorrect output is below. I am not sure if its an error in my query or if I am using the correct approach.
[ { _id: 5c7dc1b92f3f1dd8ad9df993,
user: 5c7d93b57a29ce05a096c492,
userFollowing: [],
userFollowers: [] } ]
var mongoose = require('mongoose');
let Schema = mongoose.Schema;
var FollowSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
followers: [{
type: Schema.Types.ObjectId,
ref: 'User'
}],
following: [{
type: Schema.Types.ObjectId,
ref: 'User'
}]
}, { toJSON: { virtuals: true } }
);
module.exports = mongoose.model('Follow', FollowSchema);
// username must be unique and is required
var UserSchema = new mongoose.Schema({
username: {
type: String,
unique: true,
required: true
},
email: {
type: String,
unique: true,
},
first_name: {
type: String,
required: true
},
surname: {
type: String,
required: true
},
join_date: {
type: Date,
default: Date.now
},
bio: {
type: String,
default: 'Tell me about yourself'
},
image:{
type: String,
default: 'profile.jpg'
},
password: {
type: String,
required: true
}
});
User.findOne({
'username': username
}, function (err, user) {
if (!user) {
return res.json({
'state': false,
'msg': `No user found with username ${username}`
})
} else {
const user_id = user._id;
Follow.aggregate([{
$match: {
"user": mongoose.Types.ObjectId(user_id)
}
},
{
$lookup: {
"from": "follows",
"localField": "following",
"foreignField": "_id",
"as": "userFollowing"
}
},
{
$lookup: {
"from": "follows",
"localField": "followers",
"foreignField": "_id",
"as": "userFollowers"
}
}, {
$project: {
"user": 1,
"userFollowers": 1,
"userFollowing": 1
}
}
]).exec(function (err, doc) {
console.log(doc);
res.json({
'state': true,
'msg': 'Follow list',
'doc': doc
})
})
}
})

Categories

Resources