Mongoose: How can I access a select:false property in a schema method? - mongodb

Quick code:
var userSchema = new mongoose.Schema({
username: String,
password: {type: String, select: false}
});
userSchema.methods.checkPassword = function(password, done) {
console.log(password); // Password to check
console.log(this.password); // stored password
...
};
I don't want the password to be accessible by default, but I need a method to check against a user inputted password before authenticating the user. I know I can do a query to the DB to include these values, but I'm a bit lost on how I could access the hidden property on the schema method itself. this in the method itself is just the returned query, so it seems like it is inaccessible? Should I be doing the checkPassword() function elsewhere?

You can use select to select password in query. This is an example query.
User.findOne().select('password').exec(callback);
And this must be what you want to check password.
userSchema.methods.checkPassword = function(password, done) {
User.findOne({username: this.username}).select('password').exec(function (err, user) {
if (user.password == password)
return true;
else
return false;
});
}
I hope this might help you.

You can explicitly allow the password field (with {select:"false"}) to be returned in your find call with "+" operator before field e.g.:
User.findOne({}).select("+password") // "+" = allow select hidden field

A right way is writing the fields on method findOne. You can ask the fields that you want to return. In your case, it should be:
await User.findOne({ username: this.username }, 'password').exec();
Documentation:
mongoose.findOne

Above answers only show selection for a single property.
For multiple properties, syntax is this one:
await this.userModel
.findOne({ email }, { status: 1, firstName: 1, religion: 1 })
.exec();
This will return:
{
_id: new ObjectId("62de5a5158b809468b812345"),
status: 'Active',
firstName: 'John',
religion: 'Christian Orthodox'
}

Related

Removing a key value pair from mongoose schema using findOneAndUpdate() method

In the following is the section of code :
Profile is a mongoose shcema object and contains multiple key-value pairs and the function of the code is to update Profile using findOneAndUpdate method passing in profileFields to $set which contains the new key-value pairs.
let profile = await Profile.findOne({ user: req.user.id });
if (profile) {
profile = await Profile.findOneAndUpdate(
{ user: req.user.id },
{ $set: profileFields },
{ new: true }
);
console.log(profile);
return res.json(profile);
}
This code works fine when a value for a key is changed.
eg. Profile.githubusername was abcd and is changed by passing profileFields(in which githubusername is xyz) to $set in the findOneAndUpdate method.
But the code fails when a key-value pair is completely removed.
eg. profileFields object does not contain githubusername key.
In such case the earlier previously stored value of Profile.githubusername persists.
But I need all existing information for profile to be replaced by profileFields such that if a key-value pair is missing from profileFields it is also removed from profile.
Is there a way to achieve this?
TIA
Edit :
I added the following before making the call to findOneAndUpdate(). But this still doesn't work.
let profile = await Profile.findOne({ user: req.user.id });
// for (key in profile)
// console.log(key);
for (key in profile) {
if (Object.keys(profileFields).indexOf(key) === -1) {
console.log('missing key: 'key);
profileFields[key] = '';
}
}
When I console.log(profile) I get:
{
skills: [ 'HTML' ],
_id: 60142f8f9a5bff1f08653478,
user: 60142dc89a5bff1f08653477,
company: 'Self Employed',
bio: 'Useless international',
status: 'Student or Learning',
experience: [],
education: [],
date: 2021-01-29T15:53:51.693Z,
__v: 10,
location: 'Ahemdabad, Gujrat, India',
githubusername: 'k'
}
From what I understand skills, _id, user, company, bio ... githubusername are the only keys in this object.
But on running a for (key in profile) loop I get a lot a other keys as well ,some are :
$__
isNew
errors
$locals
$op
_doc
$init
db
discriminators
schema
collection
$__originalValidate
$__save
$__validate
$__remove
$__deleteOne
$__init
$isMongooseModelPrototype
$__handleSave
save
$__delta
$__version
increment
$__where
remove
delete
How can I loop through only the user defined keys?
You can try:
delete mongooseObject["$init"]
it will delete your key and then u can manipulate other keys

How to update one field from a passed object in mongoose

Incase I have an object that looks like the one below
const auth = {
geohash: args.input.geohash,
offenses: args.input.offenses,
online: args.input.online,
paid: args.input.paid,
profilePic: args.input.profilePic,
username: args.input.username,
}
and I pass it inorder to update a document
const update = { _id: mongoose.Types.ObjectId(args._id) }
const value = await DiscoverUsers.findOneAndUpdate(update, auth, { useFindAndModify: false, new: true })
so incase I only want to update the username and I don't want to keep creating a mutation for updating each field in the document.
lets say my mutation looks like this
mutation{
updateDiscoverUsers(_id:"5dab7c198a83f235c89a964a",input:{username:"peter"}){
username
}
}
but this only updates the username but it makes the rest of the fields null but I only want to find a way to only update the fields I have passed in the mutation and the rest remain the same. so I can update the username and profilePic only and the rest remain unchanged.
I would be grateful for the help and thanks in advance
You should use the atomic operator $set to update only where you want, and you should pass only the fields you want to update, not all of them otherwise all the fields are going to be updated by the new value.
like:
const value = await DiscoverUsers.findOneAndUpdate(update, {$set:{username:"pedro"}}, { useFindAndModify: false, new: true })

How do I query a particular field in loopback 4 through the repository?

I want to enforce uniqueness so I would like to see if there are any other emails and usernames that are similar to the one posted to this route. How do I do that through the repository, it keeps on asking about a filter which I see but cannot get my head around it.
#post('/users', {
responses: {
'200': {
description: 'User model instance',
content: {'application/json': {schema: {'x-ts-type': User}}},
},
},
})
async create(#requestBody() user: User): Promise<User> {
//check : User= await this.userRepository.create(user);
//#param.query.object('filter', getFilterSchemaFor(User)) filter?: Filter;
// var check:any=await this.userRepository.find(filter);
//filter: Filter;
var check: User = await this.userRepository.find({email:user.email});
var isNotPresent: boolean = true;
// check.forEach(function(val){
// });
// if(isNotPresent)
return await this.userRepository.create(user);
}
A Filter object has the following properties that can be used to define a query and it's response:
where: Used to define a query. In your case, you would like to find existing users with the same email and username as provided in the request body.
fields: To specify fields that you would like to include or exclude in the response of your query. Every object in the array returned by find() will have only those fields which are set to true in the fields object.
offset, skip, limit and order: Used for pagination.
So, in your case, assuming a 'user' has an 'email' and an 'username', the filter object would look like the following:
const filter: Filter = {
where: {
'email': user.email,
'username': user.username
},
fields: {
email: true,
username: true
},
offset: 0,
limit: 10,
skip: 0,
order: [],
};
And your call to the repository method would look like the following:
var check: User = await this.userRepository.find(filter);
My first SO answer. Hope this helps.

Clean up dead references with Mongoose populate()

If a user has an array called "tags":
var User = new Schema({
email: {
type: String,
unique: true,
required: true
},
tags: [{
type: mongoose.Schema.Types.ObjectId,
ref:'Tag',
required: true
}],
created: {
type: Date,
default: Date.now
}
});
and I do a populate('tags') on a query:
User.findById(req.params.id)
.populate("tags")
.exec(function(err, user) { ... });
If one of the tags in the list has actually been deleted, is there a way to remove this dead reference in "tags"?
Currently, the returned user object IS returning the desired result -- ie. only tags that actually exist are in the tags array... however, if I look at the underlying document in mongodb, it still contains the dead tag id in the array.
Ideally, I would like to clean these references up lazily. Does anyone know of a good strategy to do this?
I've tried to find some built-in way to do that but seems that mongoose doesn't provide such functionality.
So I did something like this
User.findById(userId)
.populate('tags')
.exec((err, user) => {
user.tags = user.tags.filter(tag => tag != null);
res.send(user); // Return result as soon as you can
user.save(); // Save user without dead refs to database
})
This way every time you fetch user you also delete dead refs from the document. Also, you can create isUpdated boolean variable to not call user.save if there was no deleted refs.
const lengthBeforeFilter = user.tags.length;
let isUpdated = user.tags.length;
user.tags = user.tags.filter(tag => tag != null);
isUpdated = lengthBeforeFilter > user.tags.length;
res.send(user);
if (isUpdated) {
user.save();
}
Assuming you delete these tags via mongoose, you can use the post middleware.
This will be executed after you've deleted a tag.
tagSchema.post('remove', function(doc) {
//find all users with referenced tag
//remove doc._id from array
});
its sample retainNullValues: true
Example:
User.findById(req.params.id)
.populate({
path: "tag",
options: {
retainNullValues: true
}
})

mongoose - how to validate specific fields only?

I have following mongoose model and routing file.
user.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
ObjectId = Schema.ObjectId,
var userSchema = new Schema({
nick_name: {
type: String,
unique: true
},
email: {
type: String,
unique: true
},
first_name: String,
last_name: String,
birth_date: {
type: Date
},
password: {
type: String,
select: true
},
user_type: {
type: Number,
},
is_active: {
type: Number,
default: -1
}
}, { collection: 'user' });
/*
*Validations
*/
userSchema.path('nick_name').required(true, 'nick name is required!');
userSchema.path('email').required(true, 'email is required!');
userSchema.path('password').required(true, 'password is required!');
userSchema.path('user_type').required(true, 'user type is required!');
userSchema.path('is_active').required(true, 'is active is required!');
userSchema.path('is_close').required(true, 'is close is required!');
userSchema.path('first_name').required(true, 'first name is required!');
userSchema.path('last_name').required(true, 'last name is required!');
userSchema.path('birth_date').required(true, 'birth date is required!');
var User = module.exports = mongoose.model("User", userSchema);
router.js
var express = require('express');
var router = express.Router();
var User = require('../models/user');
router
.route('/api/user/register')
.post(
function(req, res, next) {
var user_ = new User(req.body);
/*
*here all validations are required
*/
user_.validate(function(err) {
if (err) {
res.json({ "status": 0, "error": err });
} else {
user_.save(function(err) {
if (err) {
res.json({ "status": 0, "error": { "other": "Oops! something went wrong, please try again later." } });
} else {
res.json({ error: 1, message: 'User registered' });
}
});
}
}
});
}
});
In above routing file I can validate all fields by using validate() method but, I have need validation as following conditions
->When user register, following fields are required
nick_name
email
password
user_type
is_active
->When user edit his profile (after register), all fields are required.
Can anybody help me to solve this issue ?
I just found myself in this situation, want to update a comment model and want a specific field validation for field 'content'.
Im thinking about a hack, pull off that full comment document from the database, then create a new schema object with the same properties from the comment document that i just pulled off from the database and validate this document model copy as if i were to create a new document, but i wont, i wont use the save() method. If there is an error with the 'content' field, which is the only one i care, i would know after validation, if there is no errors then i forget about that new object schema copy that i created by pulling off the comment document from the database, ill forget about it since i already know my 'content' field is valid since no errors where shown, so ill proceed with my flow.
Perhaps instead of pulling off that document from the database i can just create a new object with some fake but valid fields... Then pass the real value i want to test which in my case is 'content', i wouldnt fake that value since i already have it.
NOTE: my comment model has property 'createdAt' so i would replace that for the current date, cause i could have errors at validation saying new comment must be from current date and no from past dates, but since i wont be saving that new date to the database i can add the current date, recall that i will forget about that new object, i wont save it to the database, all i care is the 'content' field validation and see if there is any errors.