I am trying to do a check to see if a logged-in user's id req.user.id. is in an array of followers of the user being checked in req.params.id, bit for some reason it doesn't work.
router.get('/api/:id/isfollowing', auth, async (req, res) => {
if (req.params.id==req.user._id) {
return res.status(200).send({ "isfollowing": "Myself" })
}
try {
const followers = await Follow.find({
user: req.params.id
})
let followersArr = followers.map(follower=>{
return follower.followedBy
})
const yes = followersArr.includes(req.user._id)
// const yes = followersArr.filter((objId) => objId==req.user._id)
console.log(yes, followersArr, req.user._id)
if (yes===false) {
return res.status(200).send({ "isfollowing": false })
}
return res.status(200).send({ "isfollowing": true })
} catch (e) {
res.status(500).send()
}
})
for some reason the check doesn't work and even when using the filter, it still returns nothing. But when I console.log the values, it is right there.
[] [ 5fa4f0af4a7bf5471c41e225, 5f9dc1777a695570e878424d ] 5f9dc1777a695570e878424d
EDIT
schemas below
User schema
const userSchema = new mongoose.Schema({
fullname: {
type: String,
required: true,
trim: true,
lowercase: true
},
username: {
type: String,
unique: true,
required: true,
trim: true,
lowercase: true
},
email: {
type: String,
unique: true,
required: true,
trim: true,
lowercase: true,
validate(value) {
if (!validator.isEmail(value)) {
throw new Error('Email is invalid')
}
}
},
password: {
type: String,
required: true,
minlength: 7,
trim: true,
validate(value) {
if (value.toLowerCase().includes('password')) {
throw new Error('Passwoed cannot contain "password"')
}
}
}
})
follow schema
const followSchema = new mongoose.Schema({
// the logged in user who will be trying to follow someone will be added to "followedBy"
// the user who is getting followed will be added to "user"
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User'
},
followedBy: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'Showcase'
}
}, {
timestamps: true
})
I gave follow its own schema so I can record other info like time and other info whenever a user follows another.
If I've understand well you only need a simple query.
Since you only want to know if the id is into an array, you can check that directly with mongo. You don't need load every document into memory and use JS functions like filter or something similar.
You only need a query similar to this:
db.collection.find({
"user": ObjectId("user_id"),
"followedBy": ObjectId("follower_id")
})
This will return a document that match both values.
Check here it works and tell me if is the behaviour and output you expect.
Also I will code a mongoose query and I'll update the answer.
You can use also this query in mongoose to get how many documents find the query:
var find = await model.find({"user":mongoose.Types.ObjectId(user_id),"followedBy":mongoose.Types.ObjectId(follower_id)}).countDocuments()
Includes cannot be used in this case since you are trying to find ObjectId in an array.
To find if req.user._id is present in followersArr, use Array.some() function as below
const yes = followersArr.some(followerId=>{followerId.equals(req.user._id)})
The some call will iterate over the followersArr array, calling equals on each one to see if it matches req.user._id and stop as soon as it finds a match. If it finds a match it returns true, otherwise false.
You can't use something simpler like indexOf because you want to compare the ObjectIDs by value, not by reference.
Related
Can anyone help me solve this problem, I've been stuck days on it and haven't found any solution to.
I have this user schema in my mongodb / node js:
const userSchema = new Schema({
username:{
type: String,
required: true,
unique: true,
},
name:{
type: String,
required: true,
},
password:{
type: String,
required: true,
},
phoneNumber:{
type: String,
required: true,
},
age:{
type: Number,
required: true,
},
helpSent: [Help.schema],
});
The helpSent is basically an array that stores every data the user has requested in the application.
Now by using my update function, which is the following:
exports.createHelp = async (req,res) => {
try{
const decoded = jwt.verify(req.headers.authorization.split(' ')[1], config.secret);
const help = new Help({
type: req.body.type,
description: req.body.description,
timeToRespond: req.body.timeToRespond,
emergencyLevel: req.body.emergencyLevel,
acceptance: req.body.acceptance,
state: req.body.state,
dateIssued: req.body.dateIssued
});
const user = await User.findOne({username: decoded.data});
user.helpSent.push(help);
const savedUser = await user.save();
res.json(savedUser)
}catch(e){
console.log({message: e});
}
}
I'm getting this error as if the function is storing a new data entry in the database and telling me that the username is already taken since I have it as a unique value
{
message: Error: user validation failed: _id: Username already in use.
at ValidationError.inspect (D:\Patrick\Flutter\Fyp\draft-v1\backend\node_modules\mongoose\lib\error\validation.js:48:26)
at formatValue (node:internal/util/inspect:763:19)
at formatProperty (node:internal/util/inspect:1681:11)
at formatRaw (node:internal/util/inspect:1006:9)
at formatValue (node:internal/util/inspect:793:10)
at inspect (node:internal/util/inspect:340:10)
at formatWithOptionsInternal (node:internal/util/inspect:2006:40)
at formatWithOptions (node:internal/util/inspect:1888:10)
at console.value (node:internal/console/constructor:323:14)
at console.log (node:internal/console/constructor:359:61) {
errors: { _id: [ValidatorError] },
_message: 'user validation failed'
}
}
Anyone help can help, and thank you!
Why dont you use updateOne
await User.updateOne({ username: decoded.data }, {
$push: {
helpSent: help
}
})
The first argument is the query condition, in your case you search for an username with decoded.data, and the second argument is your update logic
I created a node.js application (Bus-ticket-booking app). MongoDB is the database system I'm using. I haven't yet finished the front end. I'm doing API queries with Postman.
For authentication, I'm using JWT. Now I want to add roles and rules for users such as the app's administrator, supervisor, and normal user.
1 -> A user can have many roles assigned to them (admin, supervisor).
2 -> Permissions can be assigned to a role ( Create, Update, delete etc...).
As a result, a user can have one or more roles, and each role can have one or more permissions. A user can use APIs for which he has rights, such as creating data, deleting data, updating data, and so on.
Here is the user schema:
const userSchema = new mongoose.Schema({
firstname: {
type: String,
required: true,
},
lastname: {
type: String,
required: true,
},
email: {
type: String,
unique: true,
required: true,
validate(value) {
if (!validator.isEmail(value)) {
throw new Error("Please provide the valid email address");
}
},
},
password: {
type: String,
required: true,
trim: true,
minLength: 8,
},
phone: {
type: Number,
required: true,
unique: true
},
tokens:[{
token: {
type: String,
required:true
}
}]
},{
timestamps:true
});
I'm new to it and have very little knowledge about it.
Is there anyone who can assist me?
If you just need a couple different roles,
I suggest you go with Sajawal Hassan's concept of simply adding a boolean field to determine user's access level.
However, if you are planning to create where there are multitude of roles to be added, and do not want field to be added for each role:
Add permissions array field to data model and create a permission list (to better organize) to the user data model
set up a middleware to add to your routers
set up groups to allow the user of your routers, and pass them through routers
1a. I suggest you create a list of roles within the user model file. Possibly a dictionary.
.../models/user.js
const ROLES = {
ADMIN: "ADMIN",
SUPERVISOR: "SUPERVISOR"
}
...
module.exports = mongoose.model('User', UserSchema);
module.exports.ROLES = ROLES;
1b. add a array field to Users models which will have the roles as permissions
.../user.js
const userSchema = new mongoose.Schema({
...,
permissions: [String],
...
});
Set up a middleware or in this case you already have auth; add functional capabilities to the function in which it will check its parameter or attach it to options (if you are checking for token, or other auth params)
.../auth.js
module.exports = function (options) {
...
// get user, validate token b4 here
user.permissions.forEach(permission => {
if (options.allowedGroup.indexOf(permission)){
// if authenticated
return next();
}
}
// could not authenticate at this point
return next(errorHandler) // throw a error or handle it way you want
}
3a. set up groups to determine which roles will have access to each router or a set of routers
.../routes/api_123.js
const mongoose = require('mongoose');
const User = mongoose.model('User');
const readGroup = [User.ROLES.SUPERVISOR, User.ROLES.ADMIN];
const writeGroup = [User.ROLES.ADMIN];
3b. pass the group you made as allowedGroup in param of middleware and set it up with a asyncHandler
...
const asyncHandler = require('express-async-handler');
...
router.get('/user/:id', auth({allowedGroup: readGroup}), asyncHandler(async(req, res, next) => {
... // your stuff here
res.status(200).json(data);
}))
router.post('/user/:id', auth({allowedGroup: writeGroup}), asyncHandler(async(req, res, next) => {
... // your stuff here
res.status(200).json(data);
}))
You should try to watch a full course on express and mongodb but you would have to add fields in the user schema that specifies if the user has permissions i.e admin: { type: booleen, default: false } then set the booleen to true if you want the user to be admin then create a route for something only admin sould be able to do lets say to delete a user so then in there check if the admin field in user schema is true. If so then user can delete otherwise throw err. it would be really helpful if you were to provide code snippets and other helpful links learn more here: https://stackoverflow.com/help/how-to-ask
edit:
Do keep in mind im using mongodb atlas for the code
Add an admin field (or any role that you want im gonna go with admin here)
so change
const userSchema = new mongoose.Schema({
firstname: {
type: String,
required: true,
},
lastname: {
type: String,
required: true,
},
email: {
type: String,
unique: true,
required: true,
validate(value) {
if (!validator.isEmail(value)) {
throw new Error("Please provide the valid email address");
}
},
},
password: {
type: String,
required: true,
trim: true,
minLength: 8,
},
phone: {
type: Number,
required: true,
unique: true
},
tokens:[{
token: {
type: String,
required:true
}
}]
},{
timestamps:true
});
to this
const userSchema = new mongoose.Schema({
admin: {
type: Booleen,
default: false,
},
firstname: {
type: String,
required: true,
},
lastname: {
type: String,
required: true,
},
email: {
type: String,
unique: true,
required: true,
validate(value) {
if (!validator.isEmail(value)) {
throw new Error("Please provide the valid email address");
}
},
},
password: {
type: String,
required: true,
trim: true,
minLength: 8,
},
phone: {
type: Number,
required: true,
unique: true
},
tokens:[{
token: {
type: String,
required:true
}
}]
},{
timestamps:true
});
I just added the admin field in the user schema
Then lets say you only want the admin to be able to delete users
for that you would have to create a route like this
router.delete("/delete/:id", async (req, res) => {
try {
// First find the user admin wants to delete
const user = await User.findById(req.params.id) // getting id from the id you put in url
// Make sure the user who wants to delete another user is an admin
if (user.admin) {
await user.deleteOne() // This deletes the user
} else {
res.status(403).json("You are not allowed to do this action!")
}
} catch (error) {
res.sendStatus(500);
}
});
In create, the validation works fine and it will throw validation error if missing required or wrong type.
However, when I try to update or findOneAndUpdate it only validates wether any required is missing, but it doesn´t validate the type. At the moment I can update the name property to a number and no validation error happens. Any idea on what to do?
mongoose.set('runValidators', true);
const Post = mongoose.model('Post', {
nome: {
type: String,
required: true,
trim: true
},
email: {
type: String,
required: true,
trim: true
},
morada: {
type: String,
required: true,
trim: true
}
})
module.exports = Post
const update = async (req, res) => {
try {
let post = await Post.findOneAndUpdate(req.params, req.body, {new: true});
res.json(post)
} catch (e) {
res.status(500).json(e)
}
}
You need to explicitly define the Post model using a mongoose schema. Something like the following:
const PostSchema = {
nome: { type: String, required: true, trim: true},
email: { type: String, required: true, trim: true},
morada: { type: String, required: true, trim: true}
};
const Post = mongoose.model('Post', PostSchema);
If that doesn't work, you could use a pre function on the schema. The pre function allows you to run code before certain actions (e.g. save) where you can do things like more granular data validation.
For example:
Post.pre("save", function(next, done) {
let self = this;
if (invalid) { // Replace 'invalid' with whatever checking needs to be done
// Throw an Error
self.invalidate("nome", "name must be a string");
next(new Error("nome must be a string"));
}
next();
});
I am trying to determine how to do asynchronous validation for a Mongoose schema - specifically in this case the username. TMK, to ensure that the username is unique, we have to manually query the database to see if the same username already exists. This is an asynchronous query. However the methodology of having a 'validate:' property for each schema item, seems to ask for a synchronous validation function. In other words, this line:
validate: [validation.usernameValidator, 'not a valid username']
seems to require that usernameValidator be synchronous, and the problem is I need it to be async, for the reason aforementioned.
So, I have a Mongoose schema for a User like so:
var validation = {
usernameValidator: function (candidate) {
return true;
},
passwordValidator: function (candidate) {
return true;
}
};
userSchema = mongoose.Schema({
username: {
type: String,
isUnique: true,
required: true,
validate: [validation.usernameValidator, 'not a valid username']
},
passwordHash: {
type: String,
required: true,
validate: [validation.passwordValidator, 'not a valid password']
},
email: {
type: String,
isUnique: true,
required: true,
validate: [validation.emailValidator, 'not a valid email address']
}
});
userSchema.pre('save', function (next) {
var self = this;
if (!self.isModified('passwordHash')) {
return next();
}
bcrypt.hash(self.passwordPreHash, SALT_WORK_FACTOR, function (err, hash) {
if (err) {
return next(err);
}
else if(hash == null){
return next(new Error('null/undefined hash'));
}
else {
self.passwordHash = hash;
next();
}
});
});
//is the following function my best bet?
userSchema.path('username').validate(function (value, respond){
this.findOne({ username: value }, function (err, user){
if(user) respond(false);
});
}, 'This username has been already registered');
is my only option to leave out the validation.usernameValidator methodology, and validate username with userSchema.path('username').validate..?
Mongoose should handle this provided that you specify unique: true on that field.
For example
userSchema = mongoose.Schema({
username: {
type: String,
unique: true,
required: true
},
passwordHash: {
type: String,
required: true
},
email: {
type: String,
unique: true,
required: true
}
});
ADDITION:
Mongoose will declare a unique index provided that you specify such in your schema(as done in example above). This prevents having to query into mongodb to see if another document has a field of the same value. You can read about it here.
You can read more about Unique Indexes for mongodb here, if you'd like to learn more about their behaviour.
Note: A validation error will not be throw if a non-unique value is provided. See the mongoose docs for more info on this.
Hello i have this Schema(called schema.js):
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var RoomSchema = new Schema({
name: { type: String, required: true, index: { unique: true } },
people: { type: Number, required: true },
childrens: {type: Number, required: true},
total: {type: Number, required: true}
});
var Room = mongoose.model('Room', RoomSchema);
var AvSchema = new Schema({
roomId: {type: Schema.Types.ObjectId, ref: 'Room'},
people: { type: Number, required: true },
childrens: {type: Number, required: true},
total: {type: Number, required: true}
});
var Av = mongoose.model('Av', AvSchema);
module.exports = {
Room: Room,
Av: Av
};
in my Route file :
module.exports = function(app) {
var model = require('../models/Schema');
app.get('/api/rooms', function(req, res) {
model.Room.find(function(err, rooms) {
if (err)
res.send(err);
res.json(rooms);
});
});
app.get('/api/av', function(req, res) {
model.Av.find().populate('roomId').exec(function(err, av) {
if (err)
res.send(err);
res.json(av);
});
});
};
A pic of the db :
GET /api/rooms - response:
[{
"_id": "5444d0dd9a31437167eea816",
"name": "Single",
"people": 1,
"childrens": 1,
"total": 4
}, {
"_id": "5444d1009a31437167eea817",
"name": "Double",
"people": 2,
"childrens": 2,
"total": 10
}]
When i call api/rooms looks fine but when i call api/av i got an empty array [] .... Any idea what i do wrong? I should mention that i have inserted records in av collection for both roomsID
Thank you in advance.
By default, Mongoose pluralizes the model name to come up with the name of the collection, so Mongoose is looking in the avs collection instead of av.
You can explicitly set the collection name by passing that as the third parameter to model:
var Av = mongoose.model('Av', AvSchema, 'av');
I had the same issue but none of the answers worked for me.
I wanted to populate a document after it was queried.
This didn't work:
// IIFE for async/await
( async() => {
var user = await User.findOne( { _id } );
await user.populate( 'comments' ); // Doesn't work
} );
The Mongoose Documentation explains that when calling .populate() without a callback it won't be executed. Instead you need to use .populate().execPopulate():
// IIFE for async/await
( async() => {
var user = await User.findOne( { _id } );
await user.populate( 'comments' ).execPopulate(); // Works as expected
} );
Similar to CodyBugstein's answer, I'm posting why it wasn't working in my case, even though it's not the same case as OP's.
I was trying to populate the "pro" field of my schema in a .post('save') hook, as so:
mySchema.post('save', function(doc, next) {
console.log(doc.pro); // Expected to log ObjectID
doc.populate("pro"); // Populate field
console.log(doc.pro); // Expected to log actual pro document
}
However, the 2nd console.log was also logging the ObjectID instead of the doc.
After struggling with this for a solid hour and trying different approaches, I found out that all I had to do was use promises and call execPopulate() so that it returned a fully-fledged promise. I used async/await but you could use .then too:
mySchema.post('save', async function(doc, next) {
console.log(doc.pro); // Expected to log ObjectID
await doc.populate("pro").execPopulate(); // Populate field
console.log(doc.pro); // Expected to log actual pro document
}
This way, the 2nd console.log did log the entire pro doc as expected :)
Since this is the most popular result for the query
mongoose populate not working
I'll include the reason it wasn't working for me, even though it's not a direct answer to this already solved question, in the hopes it will help someone
The problem for me was that I had specified fields in select({..} but not the field I was trying to populate.
Don't forget to add the ref property to the schema for the property you are trying to populate. E.g.
// ...
const orderSchema = new mongoose.Schema({
userId: {
type: Types.ObjectId,
required: true
},
reservationId: {
type: Types.ObjectId,
required: true,
ref: 'Reservation' // <-- don't forget the ref
}
}, {
timestamps: true
})
// ...
See Mongoose Populate
Also check that your schema doesn't have depopulate in toJSON or toObject option set to true. (facepalm myself)
See all schema options
For me, it is was due to incorrect data. The Ids which I want to populate got deleted from the main table.
So when I do populate it didn't filled the populated data because the Ids were not in the table.
add this in your model
const productSchema = mongoose.Schema(
{
name: {
type: String,
required: [true, "product name must be provide"],
minlength: [3, "Name length minimum is 3"],
maxlength: [50, "Name length maximum is 50"],
trim: true,
},
price: {
type: Number,
required: [true, "price must be provide for product"],
default: 0,
},
description: {
type: String,
required: [true, "product description is required"],
trim: true,
},
{
timestamps: true,
toJSON: {
virtuals: true,
},
toObject: {
virtuals: true,
},
}
);
Example:
productSchema.virtual("reviews", {
ref: "Review",
localField: "_id",
foreignField: "product",
justOne: false,
});
How to use:
products = await productModel.find({}).populate("reviews");
For me, the issue was that I did not require the model to be populated at the beginning of the file.