How to update some but not all fields in Mongoose - node.js

here is the UserSchema:
var UserSchema = new Schema({
username: { type: String, required: true, index:{unique: true} },
firstName: { type: String, required: true },
lastName: { type: String, required: true },
email: { type: String, required: true, index:{unique: true} },
password: { type: String, required: true, select: false }
});
Here is the http PUT request:
// update user information
api.put('/users/:username', function(req, res) {
User.findOne({username: req.params.username}, function(err, user) {
if (err){
res.send(err);
return;
}
if (!user){
res.status(404).send({
success: false,
message: "user not found"
});
} else {
user.username = req.body.username;
user.email = req.body.email;
user.password = req.body.password;
user.firstName = req.body.firstName;
user.lastName = req.body.lastName;
user.save(function(err) {
if (err){
res.send(err);
return;
}
res.json({
success: true,
message: "user information updated."
});
});
}
});
});
The question is, if the user only want to update limited fields, for example, only update username, then the above code does not work, the error looks like this:
{
"message": "User validation failed",
"name": "ValidationError",
"errors": {
"lastName": {
"properties": {
"type": "required",
"message": "Path `{PATH}` is required.",
"path": "lastName"
},
"message": "Path `lastName` is required.",
"name": "ValidatorError",
"kind": "required",
"path": "lastName"
},
"firstName": {
"properties": {
"type": "required",
"message": "Path `{PATH}` is required.",
"path": "firstName"
},
.........
so how can I implemement to allow user updates some but not all fields?
Any comments and suggestions are appreciated!

Using findOneAndUpdate with the operator $set in the update object:
User.findOneAndUpdate({username: req.params.username}, { $set: req.body }, { new: true }, callback);
$set will allow you to modify only the supplied fields in the req.body object.

My solution is:
const dot = require('dot-object'); // this package works like magic
const updateData = { some: true, fields: true };
User.updateOne(
{ _id: req.user._id },
{ $set: dot.dot(updateData) },
(err, results) => {
if (err) res.json({ err: true });
else res.json({ success: true });
}
);
I found this tip (dot package) on: https://github.com/Automattic/mongoose/issues/5285

This is a good compromise:
Specify the fields that the user can update
let fieldToUpdate = {
name: req.body.name,
email: req.body.email,
};
Then delete all the keys that contains falsy value
for (const [key, value] of Object.entries(fieldToUpdate)) {
if (!value) {
delete fieldToUpdate[key];
}
}
Then Update the value using the $set operator
const user = await User.findByIdAndUpdate(
req.user.id,
{ $set: { ...fieldToUpdate } },
{
runValidators: true,
new: true,
}
);

You can use the 'findOneAndUpdate' method.
User.findOneAndUpdate({username: req.params.username}, {username: req.body.username}, function(err, user) {
//...
});

From what I understand is that you want to be able to update any amount of fields. The code below is from a past project.
Model
const ingredientSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type:String, required: true },
quantity: { type: Number, default: 0}
});
HTTP PUT
router.put('/:ingredientId', (req, res, next) => {
// extracting ingredient id from url parameters
const id = req.params.ingredientId;
//creating a map from the passed array
const updateOps = {};
for(const ops of req.body){
updateOps[ops.propName] = ops.value;
}
//updating the found ingredient with the new map
Ingredient.update({_id: id}, { $set: updateOps})
.exec()
.then(result =>{
console.log(result);
//returning successful operation information
res.status(200).json(result);
})
//catching any errors that might have occured from above operation
.catch(err => {
console.log(err);
//returning server error
res.status(500).json({
error: err
});
});
});
PUT Request (json)
[
{"propName": "name", "value": "Some other name"},
{"propName": "quantity", "value": "15"},
]
or if you one want to update one field
[
{"propName": "name", "value": "Some other name"}
]
basically you have an array of these property/field names and their new values. you can update just one or all of them this way if you would like. Or none of them I believe.
Hopefully, this helps! if you have any questions just ask!

Related

Adding json response to a Map field Mongoose Node

I'm just starting out whit node and mongoose and I'm trying to create a user, then create a stripe customer with a stripe generated id and save the response in a user Map field stripeDetails.
Here is the schema:
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true },
photoUrl: { type: String, required: true },
phoneNumber: { type: String, required: false },
address: { type: String, required: false },
zipCode: { type: String, required: false },
city: { type: String, required: true },
region: { type: String, required: true },
country: { type: String, required: true },
isVerified: { type: Boolean, required: false, default: false },
lastLogin: { type: Number, required: false, default: Date.now },
stripeDetails: {type: Map, required: false}
},
{ timestamps: true });
module.exports = mongoose.model('User', userSchema, 'Users');
I tried setting the stripeDetails field like
.then(stripeCustomer => {
console.log('Stripe.customer.create', stripeCustomer);
result.set('stripeDetails', stripeCustomer);
result.save();
...
but is not working.. I settle to update the record's field but is a bit messy..
exports.createUser = async (req, res) => {
const user = req.body;
console.log('User is :', user);
/// Creat use in DB
User.create(
user,
function (err, result) {
if (err) {
console.log('Mongoose createUser error: ', err);
res.statut(503).send({ error: "Internal error" });
return;
}
console.log('Mongoose createUser: ', result);
res.status(200).send({
message: "User created successfully!",
data: result
});
/// Create stripe customer
stripe.customers.create({
"address": {
"city": user.city,
"state": user.region,
"country": user.country,
"postal_code": user.zipCode
},
"balance": 0,
"created": Date.now,
"email": user.email,
"name": user.name,
"phone": user.phoneNumber,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none"
})
.then(stripeCustomer => {
console.log('Stripe.customer.create', stripeCustomer);
// save stripe details to db
//not working..
// result.set('stripeDetails', stripeCustomer);
// result.save();
// working
User.findByIdAndUpdate(
result.id,
{stripeDetails: stripeCustomer},
{ new: true },
function(err, result) {
if (err) {
console.log('Stripe customer not updated to db: ', err);
}
if (result != null){
console.log('Stripe customer updated to DB', result);
} else {
console.log('Stripe customer to update not found in db ');
}
}
);
})
.catch(error => {
console.log('Stripe.customer.create error: ', error);
});
}
);
};
also I can't access the stripeDetails.id value for when I need to delete the user..
exports.deleteUserById = async (req, res) => {
const id = req.params.id;
User.findByIdAndDelete(
id,
function (err, result) {
if (err) {
console.log('Mongoose deleteUserById error: ', err);
res.statur(505).send({ errro: "Internal error" });
}
if (result != null) {
console.log('Mongoose deleteUserById: ', result);
res.status(200).send({
message: "User found!",
data: result
});
console.log('stripe id is: ', result.stripeDetails['id']);
stripe.customers.del(`${result.stripeDetails['id']}`)
.then(stripeCustomer => {
console.log('Stripe.customer.delete', stripeCustomer);
})
.catch(error => {
console.log('Stripe.customer.delete error: ', error);
});
} else {
console.log("Mongoose deleteUserById: user not found");
res.status(404).send({ message: "User not found" });
}
});
}
I could use the mongoose _id as the stripe id but I rather use their own separate id generators and keep the ids separate, and get used to work with maps in mongoose. Can you see what I'm doing wrong with in writing and reading stripeDetails?
Try to change stripeDetails in Schema to be of type Object:
stripeDetails: {type: Object, required: false}
Now you can do this:
.then(stripeCustomer => {
result.stripeDetails.details = stripeCustomer;
User.findByIdAndUpdate(result.id, result).then((result)=>{
console.log('User updated.');
})
})
Note that when you are using Map as a type, you should access the value of a key with .get(). So try to access stripe_id like this:
let stripe_id = result.stripeDetails.get("id");
stripe.customers.del(stripe_id)
.then(stripeCustomer => {
console.log('Stripe.customer.delete', stripeCustomer);
})
.catch(error => {
console.log('Stripe.customer.delete error: ', error);
});
Check it here.

updating document inside array in mongoose

I want to update my answer object inside answers array. I am using following schema
const mongoose = require("mongoose");
const { ObjectId } = mongoose.Schema;
const questionSchema = new mongoose.Schema(
{
postedBy: {
type: ObjectId,
required: true,
ref: "User",
},
question: {
type: String,
required: true,
},
photo: {
data: String,
required: false,
},
answers: [
{
userId: { type: ObjectId, ref: "User" },
answer: String,
},
],
questionType: {
data: String,
required: false,
},
},
{ timeStamps: true }
);
module.exports = mongoose.model("Question", questionSchema);
I am using updateOne method to update my answer in my db. Can anyone explain what is missing here. I am been trying to solve this since hours
exports.updateAnswer = (req, res) => {
const questionId = req.body.questionId;
const answerId = req.body.answerId;
Question.findOne({ _id: questionId }).exec((err, question) => {
if (err) {
res.status(400).json({
error: errorHandler(err),
});
return;
}
if (!question) {
res.status(400).json({
error: "question not found",
});
return;
}
});
Question.updateOne(
{ _id: answerId },
{
$set: {
"answers.$.answer": "This is update answer. My name is Ravi Dubey",
},
},
{ new: true },
(err, success) => {
if (err) {
res.status(400).json({
error: errorHandler(err),
});
}
res.json({
msg: "answer updated successfully",
success,
});
}
);
};
My result is coming successful but answer is not updating in db.
I am confused on Question.updateOne method.
Any help appreciated.
If you trying to query based on id of one of the documents in the answers array then instead of {_id: answerId} you need to provide {'answers._id': answerId}. And also if you need the updated document as result then you should use the findOneAndUpdate method.
Question.findOneAndUpdate(
{ "answers._id": answerId },
{ $set: { "answers.$.answer": "some answer" } },
{ new: true },
(err, data) => {
// handle response
}
);

Catch all errors in Sails while submitting form

Hey I am new to Sails I am having an issue while submitting form when in my model username and email is set to unique in my input field if I put username and email which already exist in database I am just getting error for unique username not for email if I fix username then I will get error for email but I want both errors to be shown at one go
Here is the model code:
username: {
type: "string",
required: true,
unique: true,
},
name: {
type: "string",
required: true,
},
title: {
type: "string",
},
email: {
type: "string",
isEmail: true,
required: true,
unique: true,
},
password: {
type: "string",
minLength: 6
},
This is controller code
var result=await Users.create(req.allParams(),function(err,data){
if(err)
{
console.log(err);
}
else
{
console.log(data)
}
});```
your controller should be like
module.exports = {
create: async (req, res) => {
//get your params and body using req.params or req.body
try {
//controller's logic here
let user = await Users.create(data).fetch();
res.status(200)
.send({ user })
} catch (error) {
//catch all errors here
res.status(500)
.send({ err:error })
}
},

Many to one relationships in mongoose. How to access and output the referred to object ID's?

I'm trying to create a "wishlist" feature for users on my node / mongo application. I've assumed the best way to model the scheme would be to reference the items they like. So far my reading has brought me to this point (I'm not very familiar with the Types.ObjectID):
Schema Model
var UserSchema = new mongoose.Schema({
email: {
type: String,
unique: true,
required: true,
trim: true
},
password: {
type: String,
required: true
},
wishlist: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Wishlist",
required: true
}]
});
I've managed to write some code which pushes the relevant _id into the "Likes" array:
Product.findById(productID).exec(function (err, user) {
User.updateOne({ _id: req.session.userId }, { "$push": { "wishlist": productID } }, function (err, user) {
if (err) {
console.log("Failed to add")
} else {
console.log(productID + " has been added")
}
});
});
This outputs in the database like so:
{
"_id" : ObjectId("5c3f7e1f1268203b1f31cb17"),
"email" : "email",
"password" : "password",
"__v" : 0,
"wishlist" : [
ObjectId("5c41f4b42f82b14798d5c7fc"),
ObjectId("5c41f4b42f82b14798d5c7ff")
]
}
I'm stuck on how I'd output these wishlist items in my template. My assumption was to get the data like this:
router.get('/wishlist', middleware.requiresLogin, function(req, res, next) {
User.findOne({ _id: req.session.userId }, function(err, user) {
res.render('wishlist', {
title: 'Wishlist',
template: 'wishlist',
saved: user.wishlist,
header: true,
footer: true
});
});
});
And the loop through the items like this:
{{#each saved }} Code goes here {{/each }}
Am I approaching this correctly?
you'll need to populate the wishlist field, try this,
User.findOne({ _id: req.session.userId }).
populate('wishlist').
exec(function (err, user) {
res.render('wishlist', {
title: 'Wishlist',
template: 'wishlist',
saved: user.wishlist,
header: true,
footer: true
});
});
You can refer to the Populate (mongoose documentation).
//User_controller.js
exports.getUser = (req, res) => {
User.findOne({ _id: req.session.userId })
.populate('wishlist')
.then((user) => { res.json(user) })
.catch((error) => { res.status(500).json({ error })
});
};
// UserRoute.js
const express = require("express");
const router = express.Router();
const userCtrl = require('./user_controller');
router.get('/:id', userCtrl.getUser);
module.exports = router;
//server.js
//...
const userRoute = require("./UserRoute");
app.use("/user", userRoute);
//...

Mongoose API get nested items in array by _id

I am building an api with Express and Mongoose (Backbone on the front). I have a Mongoose User model that contains an array called "orders". I need to set up a create method that will READ a Single Order by ID.
When I navigate to:
http://localhost:3000/test/
I get the following for a logged in user:
{
"__v": 0,
"_id": "537d09a1fe47a00000c54514",
"kittenType": "Grumpy",
"local": {
"petname": "Smeagol",
"password": "$2a$08$X4sF5UmYZ3/2cxfRzpPcq.pphYFRKcb.6xBGupdUyUMgWJlFSr/uq",
"email": "julie#gmail.com"
},
"orders": [
{
"title": "Big Max Fountain",
"description": "Large capacity drinking fountain",
"quantity": "2",
"price": 500,
"_id": "53837e9e681808e6ea9f9ca4",
"modified": "2014-05-28T23:49:10.232Z"
},
{
"title": "Lotus Fountain",
"description": "Tranquil pools of water",
"quantity": "1",
"price": 1000,
"_id": "53867762ff514df026b608fa",
"modified": "2014-05-28T23:55:16.263Z"
}
]
}
When I navigate to:
http://localhost:3000/test/orders
I send the list of orders for the logged in user (this gives me the array of orders:
app.get('/test/orders', function(req, res) {
User.findOne({'_id': req.user.id }, function(err, user) {
if (err)
return done(err);
if (user) {
res.send(user.orders);
}
});
});
How do I then send each order by id?
app.get('/test/orders/:id', function(req, res) {
User.findOne({'_id': req.user.id }, function(err, user) {
if (err)
return done(err);
if (user) {
//send the order by id here thru the url
}
});
});
ADDED AFTER RESPONSE:
var userSchema = mongoose.Schema({
user : {
type: mongoose.Schema.ObjectId,
ref: 'User'
},
orders: [{
title: String,
description: String,
quantity : String,
price : Number,
modified: { type: Date, default: Date.now }
}],
signup: [{
name: String,
courseDay: String,
time: String,
location: String,
modified: { type: Date, default: Date.now }
}],
kittenType : String,
profilePhoto : String,
profilePage : String,
local : {
email : String,
password : String,
petname : String,
path : String,
}
routes:
app.get('/test', function(req,res) {
res.send(res.locals.user);
});
app.get('/test/orders', function(req, res) {
User.findOne({'_id': req.user.id }, function(err, user) {
if (err)
return done(err);
if (user) {
res.send(user.orders);
}
});
});
app.post('/api/orders', isLoggedIn, function (req, res){
User.findOne({'_id': req.user.id }, function(err, user) {
if (err)
return done(err);
if (user) {
user.orders.quantity = req.body.quantity;
user.orders.description = req.body.description;
user.orders.title = req.body.title;
user.orders.price = req.body.price;
user.orders.modified = req.body.modified;
user.update({$push: { "orders" :
{ title: user.orders.title,
description: user.orders.description,
quantity: user.orders.quantity,
price: user.orders.price,
modified: user.orders.modified
}
}},{safe:true, upsert:true},function(err){
if(err){
console.log(err);
} else{
console.log("Successfully added" + user.orders);
}
});
console.log('located a user');
}
});
});
WORKING GET METHOD:
app.get('/test/orders/:id', function(req, res) {
User.findOne({'_id': req.user.id }, function(err, user) {
if (err)
return done(err);
if (user) {
console.log(user.orders);
var order = user.orders.filter(function(e){ return e._id == req.params.id })[0]
console.log(order);
res.send(order);
}
});
});
I think you don't need to find a user in this case. Enough to find Order with proper condition:
app.get('/test/orders/:id', function(req, res) {
Order.findOne({'_id': req.params.id, 'user_id': req.user.id }, function(err, order) {
if (err)
return done(err);
if (order) {
res.send(order);
}
});
});
But you should log req to be sure that you use proper ids. It depends also on your routes, that you didn't public.
Or if you need to find User model, you can simply use filter method. Code will be almost the same as in first method:
app.get('/test/orders/:id', function(req, res) {
User.findOne({'_id': req.user.id }, function(err, user) {
if (err)
return done(err);
if (user) {
console.log(user.orders); // returns an array
// console.log(req.id); // to be sure that it returns proper order id
// perhaps it could be next
console.log(req.params.id);
var order_id = user.orders.filter(function(e){ return e == req.params.id })[0]
// then find this order
Order.findOne({'_id': order_id }, function(err1, order) {
if (err1)
return done(err1);
if (order) {
res.send(order);
}
});
}
});
});

Resources