Mongoose model unique - mongodb

I´m rather new to this..
If I dont want the user to be able to add duplicated countries to visitedCountry, shoulden unique true work?
Or are there any easy way to block that in the patch?
const User = mongoose.model('User', {
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
accessToken: {
type: String,
default: () => crypto.randomBytes(128).toString('hex')
},
visitedCountries:[ {
country: {
type: Object,
ref: "Country",
unique: true
},
comments: String
}]
})
app.patch('/countries', authenticateUser)
app.patch('/countries', async (req, res) => {
const { username, visitedCountry } = req.body
try {
const countryByAlphaCode = await Country.findOne({ alphaCode: visitedCountry }).lean()
const updatedUser = await User.findOneAndUpdate({ username: username, }, {
$push: {
visitedCountries: { country: countryByAlphaCode, comments: "no comments yet"}
},
}, { new: true })
res.json({ success: true, updatedUser })
} catch (error) {
res.status(400).json({ success: false, message: "Invalid request", error })
}
})

The options unique works for all documents. It prevents two (or more) documents from having the same value for your indexed field. It's often used for the email or username.
For your case, I recommend you to perform a check on the user data before you call findOneAndUpdate.

Related

How to update a user profile which has a property which is a ref in MongooseJS?

I have a User schema which has reference to a profile schema.
const UserSchema = new Schema(
{
_id: mongoose.Schema.Types.ObjectId,
email: {
....email props...
},
password: {
...password props...
},
profile: [{
type: mongoose.Schema.Types.ObjectId,
ref: "Profile",
}],
},
);
const Profile = new Schema({
_user: {
type: Schema.Types.ObjectId, ref: 'User'
},
'displayName': {
type: String,
default: ''
},
'interestedActivities': ['Ping-pong'], <---- This bad boy/girl is an array
'memberSince': { type: Date, default: Date.now }
}
)
I'd like to create a route which can update the User properties AND the Profile properties in one shot—with a caveat one of the properties on the Profile model is an array!!!
I tried this....
handler
.use(auth)
.put((req, res, next) => {
emailValidator(req, res, next, 'email');
},
async (req, res, next) => {
await connectDB()
const {
profileDisplayName,
profileEmail,
interestedActivities } = req.body;
const update = {
email: profileEmail,
'profile.$.displayName': profileDisplayName,
'profile.$.interestedActivities': interestedActivities
}
const filter = { _id: req.user.id };
const updatedUser = await User.findOneAndUpdate(filter, update, { new: true })
try {
console.log("updatedUser ", updatedUser);
if (updatedUser) {
return res.status(200).send({
updatedUser,
msg: `You have updated your profile, good job!`
});
}
} catch (error) {
errorHandler(error, res)
}
})
export default handler;
My response is:
Status Code: 500 Internal Server Error
Cast to ObjectId failed for value "[
{
id: 'ae925393-0935-45da-93cb-7db509aedf20',
name: 'interestedActivities',
value: []
}
]" (type Array) at path "profile.$"
Does anyone know how I could also afford for the property which is an array?
Thank you in advance!

Mongoose text index search returns empty array

I'm trying to query indexes, but I receive an empty array. I can't find what's wrong with my code. I used two methods to create the index: 1) VideoSchema.index() and 2) in the schema itself, both of them don't work. I checked the mongodb and it seems that indexes are created correctly, so I don't know what I do wrong.
const mongoose = require("mongoose");
const VideoSchema = mongoose.Schema(
{
user: {
type: mongoose.ObjectId,
required: true,
ref: "user",
},
title: {
type: String,
maxLength: 100,
text: true,
},
description: {
type: String,
text: true,
},
publishDate: {
type: Date,
},
views: {
type: Number,
default: 0,
},
likes: {
type: Number,
default: 0,
},
dislikes: {
type: Number,
default: 0,
},
comments: [
{
type: mongoose.ObjectId,
ref: "comment",
},
],
urls: {
video_url: {
type: String,
required: true,
},
thumbnail_url: {
type: String,
},
preview_url: {
type: String,
required: true,
},
},
private: {
type: Boolean,
default: 0,
},
category: {
type: String,
default: "",
},
duration: {
type: Number,
required: true,
},
},
{ timestamps: true }
);
// VideoSchema.index({ title: "text", description: "text" });
// export model user with UserSchema
module.exports = mongoose.model("video", VideoSchema);
The query:
const express = require("express");
const router = express.Router();
const Video = require("../model/Video");
router.post("/", (req, res) => {
const query = req.body.query;
Video.find({ $text: { $search: query } }, { score: { $meta: "textScore" } })
.sort({ score: { $meta: "textScore" } })
.exec(function (error, results) {
if (error) return res.status(400).send(error);
res.status(200).json({ results });
});
});
module.exports = router;
As you are fetching data from your Database it´s a good practice and makes the code clearer if you use the 'GET' method. If you do so, there is no need to add the score option to the query since V.4.4
const express = require("express");
const router = express.Router();
const Video = require("../model/Video");
router.get("/", (req, res) => {
const query = req.query.YOUR_QUERY_PARAMETER;
Video.find({ $text: { $search: query }})
.sort({ score: { $meta: "textScore" } })
.exec(function (error, results) {
if (error) return res.status(400).send(error);
res.status(200).json({ results });
});
});
module.exports = router;
If the problem persists:
Try to add the wild card text indexing to see if the problem is within it as follows:
VideoSchema.index({'$**': 'text'});
If so, then drop the collection for a fresh start on the indexing and then append your text indexes like this:
VideoSchema.index({ title: "text", description: "text" });
Create new dummy items and then check again.
Make sure you read the exceptions shown in the MongoDB documentation:
https://docs.mongodb.com/manual/reference/operator/query/text/
It seems that I resolved the problem. I noticed that in the express js the 'query' keyword is used for 'get' request params, so I decided to change this variable to 'search', so now it is like underneath and it is working!
router.get("/", (req, res) => {
const { search } = req.query;
Video.find(
{ $text: { $search: search } },
{ score: { $meta: "textScore" } }
)
.sort({ score: { $meta: "textScore" } })
.exec(function (error, results) {
if (error) return res.status(400).send(error);
res.status(200).json({ results });
});
});
But I've noticed that I'm getting only one video instead of two that contains the 'obs' in the title, so now I will need to deal with that.
Thank you so much for your time and effort!

updating a referenced field in mongodb mongoose

I am trying to "clear" my referenced field in my mongo db.
I am using mongoose, so i have a modelSchema like this.
const usersSchema = new Schema({
userName: { type: String, required: true },
password: { type: String, required: true },
isAdmin: { type: Boolean },
email: { type: String },
profile: {
type: mongoose.Schema.Types.ObjectId,
ref: 'profiles'
}
})
So my question is... if i already have an objectId added in my profile field...
how can i clear that field?
I am receiving from my client an update request with a json like this.
{ userName: 'admin',
password: '123',
email: 'admin#admin.com',
profile: '',
isAdmin: true }
and my controller do this.
usersCtrl.updateUser = (req, res) => {
userModel.findByIdAndUpdate(req.params.id, { $set: req.body }).then(() => {
res.json('updated')
}).catch((err) => {
console.log(err)
res.json(err)
})
}
but i get this error
CastError: Cast to ObjectId failed for value "" at path "profile"
thanks in advance
I could solve my problem, adding this code in my controller
usersCtrl.updateUser = (req, res) => {
if(req.body.profile == ''){
req.body.profile = null
}
userModel.findByIdAndUpdate(req.params.id, { $set: req.body }).then(() => {
res.json('updated')
}).catch((err) => {
console.log(err)
res.json(err)
})
}

MongoDB & Mongoose: unable to populate a user's posts with .populate()

I've searched this site for days looking through the many different but similar questions on this topic to no avail.
Here's what I'd like to happen. A user signs in and their posts are automatically linked to the users collection. Eventually I'd like to link posts to the profile it was posted to, but i"m not quite there yet. Here's what I've tried so far.
In the User Schema:
const UserSchema = new Schema({
posts: [{
type: Schema.Types.ObjectId,
ref: 'posts'
}],
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
...
});
module.exports = User = mongoose.model('users', UserSchema);
In the Post Schema:
const PostSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
text: {
type: String,
required: true
},
name: {
type: String
},
...
});
module.exports = Post = mongoose.model('posts', PostSchema);
In my users api, here's how I'm signing the user in and attempting to populate the user's posts:
const User = require('../../models/User');
router.post('/login', (req, res) => {
const { errors, isValid } = validateLoginInput(req.body);
// Check Validation
if (! isValid) {
return res.status(400).json(errors);
}
const email = req.body.email;
const password = req.body.password;
// Find user by email
User.findOne({ email })
.populate('posts')
.then(user => {
if (! user) {
errors.email = 'User not found';
return res.status(400).json(errors);
}
// Check password
bcrypt.compare(password, user.password).then(isMatch => {
if (isMatch) {
// User Matched
// Create JWT Payload
const payload = {
id: user.id,
firstName: user.firstName,
lastName: user.lastName,
name: user.firstName + ' ' + user.lastName,
avatar: user.avatar,
posts: user.posts
};
jwt.sign(
payload,
keys.secretOrKey,
{ expiresIn: 3600 }, (err, token) => {
res.json({
success: true,
token: 'Bearer ' + token,
payload
});
});
} else {
errors.password = 'Password is incorrect';
return res.status(400).json(errors);
}
});
});
});
In the posts api, here's how the post is being submitted:
router.post('/', passport.authenticate('jwt', { session: false }), (req, res) => {
const { errors, isValid } = validatePostInput(req.body);
if (! isValid) {
// Return errors with 400 status
return res.status(400).json(errors)
}
const newPost = new Post({
text: req.body.text,
name: req.body.name,
avatar: req.body.avatar,
user: req.user.id
});
newPost.save().then(post => res.json(post));
});
Currently, all I'm seeing is an empty array and no errors. I've been spinning my wheels on this one for a couple days now so any help would be appreciated. Thanks!
I think you forgot to save the _id of your new post to the User model so that the populate() can lookup the posts to populate:
newPost.save().then(post => {
User.update({ _id: req.user.id }, { $push: { posts: post._id }}, (err) => {
res.json(post));
});
});

Create new subdocument in mongoose

What am I missing here? I want to add a sub document to the User schema I already have the schema predefined else where.
User.findById(req.body.id, function(err, user) {
if (err) return console.log(err);
reivews: [{
reviewer: req.body.name,
content: req.body.content
}]
user.save(function(err) {
if (err) console.log(err);
res.send('saved')
})
})
It's saying its saved but I don't see the review in the for the user with the id I tried to save to.
Schema
const Review = new Schema({
reviewer: String,
date : { type: Date, default: Date.now },
content : String,
isLive : { type: Boolean, default: false }
});
const User = new Schema({
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
createdAt: { type: Date, default: Date.now },
reviews: [Review]
});
User.plugin(passportLocalMongoose);
module.exports = mongoose.model('Review', Review);
module.exports = mongoose.model('User', User);
Please try this
User.findById(req.body.id, function(err, user) {
if (err) return console.log(err);
if (user.reviews === undefined || user.reviews.length == 0) {
user.reviews = [];
}
user.reviews.push({
reviewer: req.body.name,
content: req.body.content
});
user.save(function(err) {
if (err) console.log(err);
res.send('saved')
})
})
And ensure that data in user as per the defined Schema