nodejs/mongoose: What is wrong with my chained .then() calls - node.js

My code below is attempting to:
Create an instance of the User model
Find the instance in the Subscriber model with the same email address as the newly created user
Associate the new user's subscribedAccount property to the Subscriber instance found by the findOne query on the user.email
Code:
// Check that I have a subscriber with email 'test#test.com'
Subscriber.findOne({email:'test#test.com'})
.then(d => console.log(`\nResult of check for a subscriber with email test#test.com:\n ${d}`));
User.create({name: {first: 'test first', last: 'test last'}, email: 'test#test.com', password: 'pass123'})
.then(u => {
user = u;
// Check that user.email contains 'test#test.com'
console.log(`\nCreated user's email address: ${user.email}\n`);
Subscriber.findOne({email: user.email});
})
.then(s => {
console.log(`\nAnything found by findOne and passed to this .then()?: ${s}`);
user.subscribedAccount = s;
user.save();
})
.catch(e => console.log(e.message));
Console results:
Server running at http://localhost:3000 Successfully connected with
Mongoose!
Result of check for a subscriber with email test#test.com:
{ groups:
[], _id: 5aa422736518f30fbc0f77e2, name: 'test name', email:
'test#test.com', zipCode: 11111, __v: 0 }
Created user's email address: test#test.com
Anything found by findOne and passed to this .then()?: undefined
Why is Subscriber.findOne returning undefined? Is that what is actually happening or is it something else I'm missing?
Here are my model definitions for User and Subscriber. Let me know if you need to see anything else from the application to tell what is going on.
User:
const mongoose = require('mongoose');
const {Schema} = require('mongoose');
var userSchema = new Schema( {
name: {
first: {
type: String,
trim: true
},
last: {
type: String,
trim: true
}
},
email: {
type: String,
required: true,
lowercase: true,
unique: true
},
zipCode: {
type: Number,
min: [ 10000, 'Zip code too short' ],
max: 99999
},
password: {
type: String,
required: true
},
courses: [ {
type: Schema.Types.ObjectId,
ref: 'Course'
} ],
subscribedAccount: {
type: Schema.Types.ObjectId,
ref: 'Subscriber'
}
}, {
timestamps: true
} );
userSchema.virtual('fullName').get(function() {
return `${this.name.first} ${this.name.last}`;
});
module.exports = mongoose.model('User', userSchema);
Subscriber:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
let subscriberSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
lowercase: true,
unique: true
},
zipCode: {
type: Number,
min: [10000, 'Zip Code too short'],
max: 99999
},
groups: [{type: Schema.Types.ObjectId, ref: 'Group'}]
});
subscriberSchema.methods.getInfo = function() {
return `Name: ${this.name} Email: ${this.email} Zip Code: ${this.zipCode}`;
}
subscriberSchema.methods.findLocalSubscribers = function() {
return this.model('Subscriber')
.find({zipCode: this.zipCode})
.exec();
}
//model.exports = mongoose.model('Subcriber', subscriberSchema);
var Subscriber = exports.Subscriber = mongoose.model('Subscriber', subscriberSchema);

You should have done like this
// Check that I have a subscriber with email 'test#test.com'
Subscriber.findOne({email:'test#test.com'})
.then(d => console.log(`\nResult of check for a subscriber with email test#test.com:\n ${d}`));
User.create({name: {first: 'test first', last: 'test last'}, email: 'test#test.com', password: 'pass123'})
.then(u => {
user = u;
// Check that user.email contains 'test#test.com'
console.log(`\nCreated user's email address: ${user.email}\n`);
Subscriber.findOne({email: user.email});
console.log(`\nAnything found by findOne and passed to this .then()?: ${s}`);
user.subscribedAccount = s;
user.save()
.then(s => {
//user has been updated
})
.catch(err => {
res.status(err).json(err);
})
})
})
. catch(e => console.log(e.message));

Related

Log a user in and get their profile

I am attempting to log a user in to my DB. When I log the user in, it returns the first userId in the DB and not the user who logged in. I have been struggling with this for a while and really am at a dead end.
This is my POST route to log the user in:
// login
router.post("/login", async (req, res) => {
const user = await User.findOne({
email: req.body.email,
});
const secret = process.env.SECRET;
if (!user) {
return res.status(400).send("the user not found!");
}
if (user && bcrypt.compareSync(req.body.password, user.passwordHash)) {
const token = jwt.sign(
{
userId: user.id,
isAdmin: user.isAdmin,
},
secret,
{ expiresIn: "1d" }
);
res.status(200).send({ user: user.email, token: token });
} else {
res.status(400).send("password is wrong!");
}
});
The const user = await User.findOne({ email: req.body.email, }); this returns the wrong user.
When I query the endpoint get a users profile with the userId it gets the right information. So its got nothing to do with the DB.
This is the call in the app.
const handleSubmit = () => {
axios
.post(`${baseURL}users/login`, {
email: email,
passwordHash: password,
})
.then(res => {
console.log('USER ID TOKEN', res.data.token);
setbearerToken(res.data.token);
AsyncStorage.setItem('bearerToken', res.data.token);
const decoded = decode(res.data.token);
setTokenID(decoded.userId);
dispatch(setUser(res.data));
});
};
user.js model
const userSchema = mongoose.Schema({
contactName: {
type: String,
required: true,
minlength: 5,
maxlength: 50
},
phone: {
type: String,
required: true,
minlength: 5,
maxlength: 50
},
passwordHash: {
type: String,
required: true,
minlength: 5,
maxlength: 1024
},
token: {
type: String,
},
isAdmin: {
type: Boolean,
default: false
},
clubName: {
type: String,
required: true,
},
clubAddress: {
type: String,
required: true,
},
clubEmail: {
type: String,
required: true,
},
clubPhone: {
type: String,
required: true,
},
clubWebsite: {
type: String,
required: true,
},
clubContact: {
type: String,
required: true,
},
})
Your schema doesn't have a field email to filter on.
const user = await User.findOne({
email: req.body.email,
});
Maybe you try clubEmail field. I reproduced the behavior and it looks like that mongoose ignores the filter if the field does not exist in the Schema an just returns the first document in the collection.
E.g.
const userSchema = new Schema(
{
name: String,
age: Number
}
)
const User = mongoose.model('User', userSchema);
User.findOne({name: "Superman"}, ...
Returns the user with name "Superman".
const userSchema = new Schema(
{
name: String,
age: Number
}
)
const User = mongoose.model('User', userSchema);
User.findOne({xname: "Superman"}, ...
But when using xname in the filter document which does not exist in my schema neither in the collection as field the query returns the first document in my test collection (its not Superman).
Also look here similar issue: Model.find Mongoose 6.012 always return all documents even though having filter
Issue reported: https://github.com/Automattic/mongoose/issues/10763
Migration Guide to Mongoose 6:
https://mongoosejs.com/docs/migrating_to_6.html#strictquery-is-removed-and-replaced-by-strict

Mongoose pre hook findOneAndUpdate to modify document before saving

I am using the mongoose pre hook for findOneAndUpdate. I went through the documentation to understand better it's usage. I would like to update the password field before it saves to DB. However, I am not getting the disired result - nothing gets changed. What would be the right approach for using the findOneAndUpdate pre hook to modify a certain field in the doc?
Actual Document
{
_id: new ObjectId("622457f5555562da89b7a1dd"),
id: '5982ca552aeb2b12344eb6cd',
name: 'Test User',
configuration: [
{
email: 'test2#gmail.com',
password: 'p#ssw0rd',
_id: new ObjectId("9473l58f2ad34efb816963dd"),
},
{
email: 'test3#gmail.com',
password: 'trUstN0oNe',
_id: new ObjectId("8674884cec1877c59c8838e0")
}
],
__v: 0
}
Desired Document
{
_id: new ObjectId("622457f5555562da89b7a1dd"),
id: '5982ca552aeb2b12344eb6cd',
name: 'Test User',
configuration: [
{
email: 'test2#gmail.com',
password: '0f359740bd1cda994f8b55330c86d845',
_id: new ObjectId("9473l58f2ad34efb816963dd"),
},
{
email: 'test3#gmail.com',
password: '3dba7872281dfe3900672545356943ce',
_id: new ObjectId("8674884cec1877c59c8838e0")
}
],
__v: 0
}
Code:
const UserSchema = new Schema({
id: {
type: String,
required: [true, "'id' value is required"]
},
name: {
type: String,
required: [true, "'name' value is required"]
},
configuration: [ConfigModel.schema]
});
const ConfigSchema = new Schema({
email: {
type: String,
required: [true, "Email is required"]
},
password: {
type: String,
required: [true, "Password is required"]
}
});
UserSchema.pre('findOneAndUpdate', async function(next) {
const docToUpdate = await this.model.findOne(this.getQuery());
docToUpdate.configuration.forEach((item,i) => {
docToUpdate.configuration[i].password = md5(item.password);
});
return next();
});
You are missing the .save() document command after changing the information inside the document, because you are only using findOne
const docToUpdate = await this.model.findOne(this.getQuery());
docToUpdate.botconfiguration.forEach((item,i) => {
docToUpdate.configuration[i].password = md5(item.password);
});
await docToUpdate.save() // <---- this line
You dont need the updateMany() here because the ConfigSchema is nested inside the user collection
in userModel you read configuration from ConfigModel so you have to modify the config model not user model it just read and populate the data from config model.

Static method for filtering all documents based on property of enum type

I have in my userSchema for role a user and admin but I'm not sure how I can create a static method to return all documents based on if the user is an admin or user.
I did try the following...
userSchema.statics.findByRole = function (role) {
return this.find({ role: role });
};
but that didn't work.
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
firstName: {
type: String,
},
lastName: {
type: String,
},
email: {
type: String,
required: [true, 'Please provide a valid email address!'],
unique: true,
lowercase: true,
},
password: {
type: String,
required: [true, 'Password is required!'],
},
role: {
type: String,
enum: ['user', 'admin'],
default: 'user',
},
createdAt: {
type: Date,
default: Date.now,
},
});
userSchema.virtual('fullName').get(function () {
return this.firstName + ' ' + this.lastName;
});
userSchema.statics.findByRole = function (role) {
return this.find({ role: role });
};
const User = mongoose.model('User', userSchema);
module.exports = User;
for find, findOneAndUpdate, etc you should use async/await; Because getting connected to the database can be time consuming.
userSchema.statics.findByRole = async function (role) {
await this.find({ role: role })
}

Mongoose - Get and Delete a subrecord

I have a model defined as so:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const feedbackSchema = new Schema({
Name: {
type: String,
required: true,
},
Email: {
type: String,
required: true,
},
Project: {
type: String,
required: true,
},
Wonder: {
type: String,
required: true,
},
Share: {
type: String,
required: true,
},
Delight: {
type: String,
required: true,
},
Suggestions: {
type: String,
required: true,
},
Rating: {
type: String,
required: true,
},
dateCreated: {
type: Date,
default: Date.now(),
},
user: {
type: Schema.Types.ObjectId,
ref: 'User'
}
});
const UserSchema = new Schema({
googleId: {
type: String
},
displayName: {
type: String
},
firstName: {
type: String
},
lastName: {
type: String
},
image: {
type: String
},
createdAt: {
type: Date,
default: Date.now(),
},
feedback: [feedbackSchema],
})
module.exports = mongoose.model("User", UserSchema);
An example document:
{
_id: ObjectId('60b9dc728a516a4669b40dbc'),
createdAt: ISODate('2021-06-04T07:42:01.992Z'),
googleId: '2342987239823908423492837',
displayName: 'User Name',
firstName: 'User',
lastName: 'Name',
image: 'https://lh3.googleusercontent.com/a-/89wf323wefiuhh3f9hwerfiu23f29h34f',
feedback: [
{
dateCreated: ISODate('2021-06-04T07:42:01.988Z'),
_id: ObjectId('60b9dc858a516a4669b40dbd'),
Name: 'Joe Bloggs',
Email: 'joe#bloggs.com',
Project: 'Some Project',
Suggestions: 'Here are some suggestions',
Rating: '10'
},
{
dateCreated: ISODate('2021-06-04T08:06:44.625Z'),
_id: ObjectId('60b9df29641ab05db7aa2264'),
Name: 'Mr Bungle',
Email: 'mr#bungle',
Project: 'The Bungle Project',
Suggestions: 'Wharghable',
Rating: '8'
},
{
dateCreated: ISODate('2021-06-04T08:08:30.958Z'),
_id: ObjectId('60b9df917e85eb6066049eed'),
Name: 'Mike Patton',
Email: 'mike#patton.com',
Project: 'No More Faith',
Suggestions: 'Find the faith',
Rating: '10'
},
],
__v: 0
}
I have two routes defined, the first one is called when the user clicked a button on a feedback item on the UI which takes the user to a "are you sure you want to delete this record"-type page displaying some of the information from the selected feedback record.
A second route which, when the user clicks 'confirm' the subrecord is deleted from the document.
The problem I'm having is I can't seem to pull the feedback from the user in order to select the document by id, here's what I have so far for the confirmation route:
router.get('/delete', ensureAuth, async (req, res) => {
try {
var url = require('url');
var url_parts = url.parse(req.url, true);
var feedbackId = url_parts.query.id;
const allFeedback = await User.feedback;
const feedbackToDelete = await allFeedback.find({ _id: feedbackId });
console.log(feedbackToDelete);
res.render('delete', {
imgSrc: user.image,
displayName: user.firstName,
feedbackToDelete
});
} catch (error) {
console.log(error);
}
})
Help much appreciated
Update
You should be able to do just this:
const feedbackToDelete = await User.feedback.find({ _id: feedbackId });
Or if feedbackId is just a string, which is appears to be, you may have to do something like:
// Create an actual _id object
// That is why in your sample doc you see ObjectId('foobarbaz')
const feedbackId = new mongoose.Types.ObjectId(url_parts.query.id);
const feedbackToDelete = await User.feedback.find({ _id: feedbackId });
Original
Shouldn't this:
const allFeedback = await User.feedback; (a field)
be this:
const allFeedback = await User.feedback(); (a method/function)
?

How to add user's name as well as Id to users field in a chat?

I'm creating a web application that has chats, and users can join the chat. Once the user joins the chat, I want to add the user's ID as well as their name to the users field in the Chat schema. So far, I'm able to add their ID, but I am finding it difficult to add their name. Below, I have attached my Chat mongoose model, as well as my route to add a user to a chat. Also, I have attached my User mongoose model. Any help is greatly appreciated. Thank you!
Chat model:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const ChatSchema = new Schema({
title: {
type: String,
required: true
},
password: {
type: String,
required: true
},
creator: {
type: Schema.Types.ObjectId,
ref: 'user'
},
users: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'user'
},
name: {
type: String,
required: true
}
}
],
code: {
type: String,
required: true
},
posts: [
{
text: {
type: String,
required: true
},
title: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
}
],
date: {
type: Date,
default: Date.now
}
});
module.exports = Chat = mongoose.model('chat', ChatSchema);
route to add user to chat:
// #route Put api/chats
// #desc Add a user to a chat
// #access Private
router.put('/', [auth,
[
check(
'code',
'Please include the code for the chat')
.not()
.isEmpty(),
check(
'password',
'Please include the password for the chat'
).not()
.isEmpty()
]
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
try {
const chat = await Chat.findOne({ code: req.body.code });
//const user = await User.findOne({ user: req.user.id });
if (!chat) {
return res.status(400).json({ msg: 'Invalid Credentials' });
}
// Check if the chat has already been joined by the user
if (chat.users.filter(member => member.user.toString() === req.user.id).length > 0) {
return res.status(400).json({ msg: 'Chat already joined' });
}
//console.log(chat.password);
const isMatch = await bcrypt.compare(req.body.password, chat.password);
if (!isMatch) {
return res.status(400).json({ errors: [{ msg: 'Invalid Credentials' }] });
}
const newUser = {
user: req.user.id,
text: req.user.name
}
chat.users.unshift(newUser);
await chat.save();
res.json(chat.users);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
User model:
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
module.exports = User = mongoose.model('user', UserSchema);
in this part, it seems you are assigning the user's name to a text property, which I think it should be name not text.
const newUser = {
user: req.user.id,
text: req.user.name
}
The code should be:
const newUser = {
user: req.user.id,
name: req.user.name //Property should be name
}
I hope this works!

Resources