In the following code, the user isn't getting saved.
router.patch('/onboard', auth.requireLoggedIn, function (req, res) {
if (req.user.settings.onboarding[req.body.page]) {
res.status(409).json({
status: 'Error: Trying to onboard for a page that has already been onboarded.',
});
}
console.log(req.body.page);
req.user.settings.onboarding[req.body.page] = true;
console.log(req.user.settings.onboarding);
req.user
.save()
.then(function (res) {
console.log(res);
res.status(200).json({
status: 'Successful',
});
})
.catch(function () {
res.status(500).json({
status: 'Internal server error.',
});
})
;
});
req.user.settings.onboarding.equityCalculator starts off as false and I want to set it as true. All of the console logs indicate that this is happening. However, when I check my database, it isn't updated.
By "check my database" I mean "look in Robo 3T". But I also mean querying the database and looking at the user I get back.
Furthermore, the following code works perfectly. I don't see how this code works but the code above does not work.
router.patch('/subscribe-to-email', auth.requireLoggedIn, function (req, res) {
if (req.user.emailOptIn) {
res.status(409).json({
status: 'You can\'t subscribe if you are already currently subscribed.',
});
}
req.user.emailOptIn = true;
req.user
.save()
.then(function () {
res.status(200).json({
status: 'Successful',
});
})
.catch(function () {
res.status(500).json({
status: 'Internal server error.',
});
})
;
});
Here is the relevant part of my User schema:
UserSchema = new mongoose.Schema({
emailOptIn: {
type: Boolean,
default: false,
required: true,
},
settings: {
type: mongoose.Schema.Types.Mixed,
required: true,
default: defaultSettings,
},
...
});
Try the following, it worked for me in a similar situation:
req.user.settings.onboarding[req.body.page] = true;
req.user.markModified('settings.onboarding');
req.user.save()
The lack of saving seems to occur when setting array indices, like in your example. Marking the modified array allows the saving to occur properly.
Related
I'm learning node.js and it's amazing, especially with mongo, but sometimes I struggle to solve a simple problem, like patching only 1 attribute in my user database.
It's easier to patch something that cannot be unique, but I want to patch an username attribute and I defined it as "unique" in my schema. I don't know why, but MongoDB doesn't care other db entry has the same user, it let me save.
My schema:
/** #format */
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema(
{
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
email: { type: String, required: true, unique: true },
userNumber: { type: Number, required: true },
description: { type: String },
verified: { type: Boolean, default: false },
isAdmin: { type: Boolean, default: false },
isSubscriber: { type: Boolean, default: false },
isDisabled: { type: Boolean, default: false },
acceptedTerms: { type: Number, required: true },
},
{ timestamps: true }
);
module.exports = mongoose.model('User', userSchema);
On my user controllers in node, I want to updateOne({ _id: userId}, { username: myNewUsername} but it always happens, it doesn't take into consideration another db entry can have the username, so I tried a different strategy but it doesn't work:
exports.changeUsername = (req, res, next) => {
// Requirements
const userId = req.params.userId;
const newUsername = req.body.username;
console.log('userId: ' + userId);
console.log('newUsername: ' + req.body.username);
User.findOne({ username: req.body.username })
.then(result => {
console.log(result);
if (result.username) {
const error = new Error('Could not find this sport');
error.code = 'DUPLICATED';
throw error;
}
return;
})
.catch(err => next(err));
// if no username was in use then updateOne
User.updateOne({ _id: userId }, { username: newUsername })
.then(result => {
res.status(200).json({
message: 'username has been updated',
username: result.username,
});
})
.catch(err => next(err));
};
I don't know if I can updateOne at the same time add some find validation. What I am doing wrong? Users cannot have the same username.
On the console, it seems it works, but it throws an extra error I don't understand:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at new NodeError (node:internal/errors:371:5)
at ServerResponse.setHeader (node:_http_outgoing:576:11)
at ServerResponse.header (/Users/username/Sites/pipi-api/node_modules/express/lib/response.js:776:10)
I tried this other approach and it works, but doesn't trigger an error if the record is not unique as I stated in the schema.
// GET ONLY ONE SPORT BY ID
exports.changeUsername = async (req, res, next) => {
// Requirements
const userId = req.params.userId;
const newUsername = req.body.username;
console.log('userId: ' + userId);
console.log('newUsername: ' + req.body.username);
try {
const oldUsername = await User.findOne({ username: newUsername });
if (oldUsername.username) {
throw new Error('Error: its duplicated');
}
const user = await User.findOneAndUpdate(
{ _id: userId },
{ username: newUsername },
{ new: true }
);
console.log('User successfully updated.');
return res.status(200).json({ success: true, user });
} catch (err) {
console.log('ERROR: ', err);
return res.status(400).json({ success: false });
}
};
If I uncomment the code above, it triggers an error if I find a record on the database that matches but it doesn't allow me to continue to my next line of codes I the username is not found on the db.
I get a new error:
userId: 6231bdef334afbde85ed9f43
newUsername: tetete
ERROR: TypeError: Cannot read properties of null (reading 'username')
at exports.changeUsername (/Users/user/Sites/pipi-api/v1/controllers/users/index.js:43:21)
That error is not related to Mongo. It means that you are trying to send a response and the response is already sent.
The issue is because you called both User.findOne and User.updateOne and both of them has .then handler. So the first one of these that finishes will send the actual response. In the moment the second one finished, the response is already send and the error is thrown because you are trying to send response again.
Mongo will throw the error if you try to change username property that some other user already have. You should check if the req.params.userId and req.body.username sent correctly to the backend. Try to console.log() them and check if they are maybe null.
Consider refactoring your handler to use async/await instead of then/catch. You can do it like this:
exports.changeUsername = async (req, res, next) => {
try {
const userId = req.params.userId;
const newUsername = req.body.username;
const user = await User.findOneAndUpdate({ _id: userId }, { username: newUsername }, { new: true });
console.log('User successfully updated.');
return res.status(200).json({ success: true, user });
} catch (error) {
console.log('ERROR: ', error);
return res.status(400).json({ success: false });
}
}
I have a chat application that I'm trying to add seen functionality on it.
I'm trying to execute a query that could update all chat messages (seen) columns.
here's the schema:
const messageSchema = new mongoose.Schema({
senderId: {
type: String,
required: true
},
message: {
type: String,
required: true
},
seen: { // I'm trying to update this in all the documents !!
type: Boolean,
default: false
}
}, {timestamps: true});
const chatSchema = new mongoose.Schema({
messages: [messageSchema]
});
As you can see here, I have a seen property in the message schema (which is nested inside the chat schema)
so I just want to get a single chat, update all messages inside it and update seen column to be true
I tried that:
const messagesSeen = async (req, res) => {
const chatId = req.params.id;
await Chat.findByIdAndUpdate(
chatId,
{
$set: { 'messages.seen': true },
},
{ new: true }
)
.then((chat) => {
return res
.status(200)
.json({ chat });
})
.catch((error) => {
return res.status(500).json({ message: error.message });
});
};
but unfortunately, it didn't work
so, hope to find a solution.
thank you
You should use positional operator - $[].
await Chat.findByIdAndUpdate(
chatId,
{
$set: { "messages.$[].seen": true },
},
{
new: true
})
Working example
I'm trying to add a value obtained from a calculator to a user's document in the database, however it's not showing any error or updating the document, the page just loads forever and I'm not sure why. Below is the code where I'm trying to update:
router.post('/tdeecalculator', function(req, res){
User.updateOne({email: "testtesttest#test.com"}, {$set: {tdeenumber: req.body.tdee}})
});
And below is my user schema:
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
tdeenumber: {
type: Number,
required: false
},
ormnumber: {
type: Number,
required: false
}
})
const User = mongoose.model('User', UserSchema);
module.exports = User;
The document in MongoDB shows as this:
_id: 60c217c26c83903ee454c2c5
name: "testtesttest"
email: "testtesttest#test.com"
password: "$2a$10$zQ4Jk3KKCjjNTyY0Z48/X.JkPO0J5lfV6j4gqTR1sLmqxKqvSq8mW"
__v: 0
Could it be because the tdeenumber is not shown in the actual document? Any help would be much appreciated
You should've used async/await before db query also you're not returning a response to the call you receive.
Convert your controller like this and see the error correctly.
// Used async before the function to use await before the db query
router.post('/tdeecalculator', async (req, res) => {
//Wrapped your operation with try/catch block to catch error message and show it in console and response
try {
await User.updateOne({ email: "testtesttest#test.com" }, { $set: { tdeenumber: req.body.tdee } })
res.send({ success: 1 });
return;
} catch (error) {
console.log(error.message);
res.send({
success: 0,
error: error.message
});
}
});
Why this is not displaying all channels. How can I display all channels.
new channelModel()
.fetch()
.then(function (channel) {
console.log(channel.attributes.name);
if (channel) {
res.json({error: false, status: 200, data: channel.attributes.name});
} else {
res.json({error: true, status: 404, data: 'channel does not exist'});
}
})
.otherwise(function (err) {
res.status(500).json({error: true, data: {message: err.message}});
});
Any idea?
Use fetchAll:
new channelModel()
.fetchAll()
.then(function (channels) {
channels.forEach(function(channel) {
// do something with each channel
});
// or just respond with JSON array assuming this is an Express app:
res.send(channels.toJSON());
})
I haven't actually tried the forEach function, but according to bookshelf.js documentation it should work. If you're doing a REST service, you probably want to just serialize the collection (array) as JSON.
I'm making an application where a form has to be validated with AJAX. Nothing too fancy. When a form submit is triggered I'm posting to a URL on a Node.js server and routing with Express. If the data does not pass all of the validation requirements, I'm sending a status code of 400, like so:
app.post('/create', checkAuth, function (req,res)
{
var errors = new Errors();
if (req.body['game-name'].length < 3 || req.body['game-name'].length > 15)
{
res.send({
msg: 'Game name must be between 3 and 15 characters.'
}).status(500).end();
}
else
{
GameModel.find({id: req.body.roomname}, function (err,result)
{
if (result.length !== 0)
{
errors.add('A game with that name already exists.');
}
//Some more validation
if (errors.get().length > 0)
{
res.status(400).send({
msg: errors.get()[0]
}).end();
return;
}
else
{
var data = new GameModel({
roomname: req.body['roomname'],
owner: req.session.user,
id: req.body['roomname'].toLowerCase(),
config: {
rounds: req.body['rounds'],
timeLimit: req.body['time-limit'],
password: req.body['password'],
maxplayers: req.body['players'],
words: words
},
finished: false,
members:
[
req.session.user
]
});
data.save(function (err, game)
{
if (err) {
console.log(err);
res.send({
msg: 'Something funky happened with our servers.'
}).status(500).end();
}
else
{
res.send({
msg: 'All good!'
}).status(200).end();
}
});
}
});
}
});
On the client side, I have the following code:
$.ajax({
type: "POST",
url: "/someURL",
data: $("form").serialize(),
statusCode:
{
200: function (data)
{
//All good.
},
400: function (data)
{
//Uh oh, an error.
}
}
});
Strangely, jQuery is calling the 200 function whenever I send a 400 error. I believe this is because I'm sending an object along with the status code. How can I resolve this?
First guess is you need a return; statement in your express code inside that if block. I bet the error code is running then the success code and the last values for the statusCode/body are being sent to the browser.