How do I update a subdocument with mongoose? - javascript

I'm trying to update a subdocument using mongoose. The document that the subdocument exists on is structured like so :
const UserSchema = new mongoose.Schema({
email: {
type: String,
unique: true,
lowercase: true,
required: true,
match: [/.+#.+\..+/, "Please enter a valid email."]
},
password: {
type: String,
required: true
},
characters: [characterSchema]
})
The actual subdocument looks like this:
const mongoose = require("mongoose")
const CharacterSchema = new mongoose.Schema({
playerName: {
type: String,
unique: true,
trim: true,
lowercase: true
},
playerLevel: Number,
highestSlot: Number,
numberOfSlots: [{
id: Number,
slots: Number
}]
})
module.exports = CharacterSchema
I've been trying to update the subdocument like so:
updateCharacter: async function (req, res) {
try {
const user = await db.Users.findOneAndUpdate(
{ _id: req.user._id, "characters.name": req.params.characterName },
{ $set: { "characters.$": req.body } }
)
res.json(user)
} catch (error) {
console.log(error)
}
}
I get back a successful response but nothing happens to the subdocument and I have no idea what I need to change.

Related

Multiple async queries in nodejs (mongoose)

I am a nodejs newbie. I have two simple models, User and Story. Here is what I want to do:
I want to retrieve all stories that have {status:"public"} and store it in an array called retrievedStories.
Then for each story I want to use its "user" field (which contains the object id of the user) to lookup the name of the user from User
Then add a new key in each element of retrievedStories called authorName with the name of the user.
Here are the models:
const UserSchema = new mongoose.Schema({
googleId: {
type: String,
required: true
},
displayName: {
type: String,
required: true
},
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
image: {
type: String,
},
createdAt: {
type:Date,
default: Date.now()
}
})
const StorySchema = new mongoose.Schema({
title: {
type: String,
required: true,
trim: true
},
body: {
type: String,
required: true
},
status: {
type: String,
default: 'public',
enum: ['public', 'private']
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
createdAt: {
type:Date,
default: Date.now()
}
})
And here is what I tried, but doesn't work. The stories are retrieved but the authorName is not added. Any help (possibly a better way to do this?) will be highly appreciated!
router.get('/',async (req,res)=>{
try {
const retrievedStories = await Story.find(
{status: "public"}
)
await Promise.all(retrievedStories.map(async (story) =>{
const author = await User.findById(story.user)
story.authorName = author.displayName
}))
return res.json(retrievedStories)
} catch (error) {
console.log(error)
}
})
You can simplify your query by using populate to retrieve User's data:
router.get('/', async (req, res) => {
try {
const retrievedStories = await Story.find({ status: 'public' })
.populate('user')
.exec();
return res.json(retrievedStories);
} catch (error) {
console.log(error);
}
});
You can then access User's displayName data on each Story by accessing story.user.displayName.
For more information on query population see the official docs.

How to find documents based on the result of a function mongoose

So I have two schemas user and driver they both have latitude and longitude attributes.
At some point I want to query the database for nearby drivers, I will be sending the user's current location (latitude and longitude) and I have a function to calculate the distance between two points.
I am trying to do something like this:
find all drivers with distance less than 2 KM using my function ( the function is called calculateDistance).
In code this will be like this:
const drivers = await Driver.find();
const driversToReturn = drivers.filter(
driver => calculateDistance(userLat, userLong, driver.latitude, driver.longitude) <= 2
);
res.status(200).json({
drivers: driversToReturn
});
but I don't think this is the best way to do it, I've checked the mongoose virtuals but we can't pass params (userLat and userLong) to the get method of a virtual and I don't think instance methods are the solution.
so how should I do this?
Thanks
EDIT
Driver Model
const mongoose = require("mongoose");
const { Schema } = mongoose;
const driverSchema = new Schema(
{
/** Required Attributes */
name: { type: String, required: true },
carBrand: { type: String, required: true },
plateNumber: { type: String, required: true },
password: { type: String, required: true },
phoneNumber: { type: Number, required: true },
price: { type: Number, required: true },
latitude: { type: Number, required: true },
longitude: { type: Number, required: true },
/** Not Required Attributes */
rating: { type: Number, required: false },
},
{
timestamps: true,
}
);
const Driver = mongoose.model("Driver", driverSchema);
module.exports = Driver;
User Model
const mongoose = require("mongoose");
const { Schema } = mongoose;
const userSchema = new Schema(
{
/** Required Attributes */
name: { type: String, required: true },
password: { type: String, required: true },
phoneNumber: { type: Number, required: true },
latitude: { type: Number, required: true },
longitude: { type: Number, required: true },
},
{ timestamps: true }
);
const User = mongoose.model("User", userSchema);
module.exports = User;
Users Controller
const Driver = require("../models/driver");
const Order = require("../models/order");
const calculateDistance = require("../utils/calculateDistance");
const CIRCLE_RADIUS_IN_KM = 2;
exports.getNearbyDrivers = async (req, res, next) => {
try {
const userLat = req.body.userLat,
userLong = req.body.userLong,
drivers = await Driver.find();
const driversToReturn = drivers.filter(
(driver) =>
calculateDistance(
userLat,
userLong,
driver.latitude,
driver.longitude
) <= CIRCLE_RADIUS_IN_KM
);
res.status(200).json({
drivers: driversToReturn,
});
} catch (err) {
if (!err.statusCode) {
err.statusCode = 500;
}
next(err);
}
};
Here is some code from the documentation of Mongoose that can help you with that I think :
const denver = { type: 'Point', coordinates: [-104.9903, 39.7392] };
return City.create({ name: 'Denver', location: denver }).
then(() => City.findOne().where('location').within(colorado)).
then(doc => assert.equal(doc.name, 'Denver'));
and here is the link : https://mongoosejs.com/docs/geojson.html

CastError: Cast to [undefined] failed for value "[]" (type string) at path "comments.undefined"

I'm quite new to node and mongoose. I'm trying to do a project using them, but i'm running into an error while trying to populate. The comment is saved to the Comment schema perfectly, but throws an error when i reference it Organization Schema.Please advise me on what i'm doing wrong. Any form of assistance will be appreciated.
// Post route for comment(on the Organization's profile page)
router.post('/comment/:id', ensureAuthenticated,(req, res) =>{
let id = req.params.id;
console.log(mongoose.Types.ObjectId.isValid(id))
const commentObject = new Comment({
sender: 'Fred kimani',
commentBody: req.body.commentBody
})
console.log(commentObject);
commentObject.save((err, result) =>{
if(err){console.log(err)}
else{
Organization.findByIdAndUpdate(id, {$push: {comments: result}}, {upsert: true}, (err, organization) =>{
if(err){console.log(err)}
else{
console.log('======Comments====')
}
})
res.redirect('/users/organizationprofilepage/:id')
}
})
});
//Organization Schema
const mongoose = require('mongoose');
const OrganizationSchema = new mongoose.Schema({
organization_name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
},
category: {
type: String,
required: true
},
isApproved: {
type: Boolean,
default: false
},
image:{
type:String,
required:true
},
description: {
type: String,
required: true,
},
comments: [{
type: mongoose.Types.ObjectId,
ref: 'Comment'
}],
},
//{ typeKey: '$type' }
);
OrganizationSchema.statics.getOrganizations = async function () {
try {
const organizations = await this.find();
return organizations;
} catch (error) {
throw error;
}
}
//defines the layout of the db schema
const Organization = mongoose.model('0rganization', OrganizationSchema);
module.exports = Organization;
//Comment schema
const mongoose = require('mongoose');
const CommentSchema = mongoose.Schema({
sender: {
type: String,
},
commentBody: {
type: String,
required: false,
},
date: {
type: Date,
default: Date.now
},
})
CommentSchema.statics.getComments= async function () {
try {
const comments = await this.find();
return comments ;
} catch (error) {
throw error;
}
}
const Comment= mongoose.model('Comment', CommentSchema);
module.exports = Comment;
Try to change the comments type to mongoose.Schema.Types.ObjectId:
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment',
},
],
Try to push the new commend _id into the Organization object after its creation, not the whole object:
commentObject.save((err, result) => {
if (err) {
console.log(err);
} else {
Organization.findByIdAndUpdate(
id,
{ $push: { comments: result._id } }, // <- Change this line
{ upsert: true },
(err, organization) => { }
);
...
}
});
If you just updated the schema you will need to make sure all of the comments are following the new form you created, when you save it will attempt to validate them, that is why an updateOne will work but not await save()

Why when I register ,the password value ignored in registration function to mongoDB?

When I made Post request via Postman you can see it in attachment, all data will be send to database(mongoDB) except password value .. Why ? is it hidden or I just miss something?
Update :
I added user model above .
registration function :
router.post('/register',async(req,res)=>{
//validation joi
registerSchemaValidation(req.body , res);
//check if the user is validated ...
const emailexist = await User.findOne({Email : req.body.Email});
if(emailexist) return res.send('email is exit , please log in or forget password :)')
//hash the password
const salt = await bcrypt.genSalt(10);
const haspassword = await bcrypt.hash(req.body.Password , salt);
const user = new User({
Name : req.body.Name,
Description : 'the best',
Email : req.body.Email,
Password : haspassword
});
console.log('hash password : '+ haspassword + " original : "+req.body.password);
try{
const saveUserData = await user.save();
console.log(saveUserData);
res.json({message : saveUserData})
}catch(err){
res.status(400).send(err)
}
});
registerSchemaValidation function :
const joi = require('#hapi/joi');
//validation register
const registerSchema = joi.object({
Name: joi.string().min(6).required(),
Description:joi.string().min(6).required(),
Email: joi.string().min(6).required().email(),
Password: joi.string().min(6).required(),
});
const loginSchema = joi.object({
Email: joi.string().min(6).required().email(),
Password: joi.string().min(6).required(),
});
function registerSchemaValidation(data, res) {
const {
error
} = registerSchema.validate(data);
if (error) return res.send(error.details[0].message);
}
User model :
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
Name: { type: String,
require:true,
min:6,
max:255,
},
description: { type: String,
require:true,
min:6,
max:255,
},
Email:{
type: String,
require:true,
min:5,
max:255,
},
Passowrd:{
type: String,
require:true,
min:6,
bcrypt: true
},
Date:{
type : Date,
default:Date.now
}
})
module.exports = mongoose.model('users',userSchema);
Postman :
mongoDB :
Your schema isn't quite a valid Mongoose schema.
You can't use min/max with Strings; use minLength/maxLength.
It's required, not require.
You've misspelled Password as Passowrd; that's why a property Password isn't getting saved.
There is no property called bcrypt I know of.
Try with
const mongoose = require("mongoose");
const userSchema = new mongoose.Schema({
Name: {
type: String,
required: true,
minLength: 6,
maxLength: 255,
},
description: {
type: String,
required: true,
minLength: 6,
maxLength: 255,
},
Email: {
type: String,
required: true,
minLength: 5,
maxLength: 255,
},
Password: {
type: String,
required: true,
minLength: 6,
},
Date: {
type: Date,
default: Date.now,
},
});

How do I assign a created contact to the current user in Mongoose?

I am trying to create a contact which is pushed to the current user's array of contacts.
My controller currently only creates a contact generically and isn't specific to the user.
Controller:
function contactsCreate(req, res) {
Contact
.create(req.body)
.then(contact => res.status(201).json(contact))
.catch(() => res.status(500).json({ message: 'Something went wrong'}));
}
Contact Model:
const contactSchema = new Schema({
firstName: String,
lastName: String,
email: String,
job: String,
address: String,
number: Number
});
User model:
const userSchema = new mongoose.Schema({
username: { type: String, unique: true, required: true },
email: { type: String, unique: true, required: true },
passwordHash: { type: String, required: true },
contacts: [{ type: mongoose.Schema.ObjectId, ref: 'Contact' }]
});
Assuming you have access to the username on the request object, something like this should work:
async function contactsCreate(req, res) {
const username = request.User.username
try {
const newContact = await Contact.create(req.body)
const user = await User.findOne({username})
user.contacts.push(newContact)
await user.save()
return res.status(201).json(contact)
} catch ( err ) {
return res.status(500).json({ message: 'Something went wrong'})
}
}
Thank you to LazyElephant above. The solution (tweaked) was:
async function contactsCreate(req, res) {
const userId = req.user.id;
try {
const newContact = await Contact.create(req.body);
const user = await User.findById(userId);
user.contacts.push(newContact);
await user.save();
return res.status(201).json(newContact);
} catch ( err ) {
return res.status(500).json({ message: 'Something went wrong'});
}
}

Categories