I am making nodejs API and I have a user model. Some fields are required. When trigger post it will tell me that some fields required so no save will be done, but when I do it with put it will replace it even if validation is wrong, or even if there is a required field and is missing, but duplicates run good.
this is the model of user
const mongoose = require('mongoose');
const validator = require('validator');
const userSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
firstName: {
type: String,
required: [true, 'the firstName is missing'],
validate: [(val) => validator.isAlpha(val, ['fr-FR']), 'not valid first name'],
},
lastName: {
type: String,
required: [true, 'the lastName is missing'],
validate: [(val) => validator.isAlpha(val, ['fr-FR']), 'not valid last name'],
},
phoneNumber: {
type: String,
required: [true, 'the phoneNumber is missing'],
unique: [true, 'phoneNumber already in use'],
validate: [(val) => validator.isMobilePhone(val,['ar-DZ']), 'not valid phone number'],
},
email : {
type: String,
required: [true, 'the email is missing'],
unique: [true, 'email already in use'],
validate: [validator.isEmail, 'not valid email'],
},
role: {
type : String,
"enum" : ['teacher', 'student'],
required : [true, 'the user `s role is missing'],
}
});
module.exports = mongoose.model('User', userSchema);
this is where I handle put
const express = require('express');
const router = express.Router();
const mongoose = require('mongoose');
const User = require('../../../../models/user');
router.put('/', (req, res) => {
//get the new user object
const userId = req.body.userId;
User.replaceOne({
_id: userId
},
{
_id: userId,
firstName: req.body.firstName,
lastName: req.body.lastName,
phoneNumber: req.body.phoneNumber,
email: req.body.email,
role: req.body.role
})
.exec()
.then(response => {
res.status(200).json(response);
})
.catch(err => console.log(err));
});
module.exports = router;
so I tried to test those, by postman, I wanted from mongoose to do that automatically, I thought about splitting it and redirect it to delete then post, but i will need to do the checking first, or just do the checking manually, and because am using api, I don't want to use the patch method so I don't track the user for what changes he did in the front end.
You can, instead of replaceOne() use updateOne() or findOneAndUpdate() with turned on validators (as they are of by default), like so:
User.updateOne({_id: userId},
{
_id: userId,
firstName: req.body.firstName,
lastName: req.body.lastName,
phoneNumber: req.body.phoneNumber,
email: req.body.email,
role: req.body.role
},
{runValidators: true})
.then(response => {
res.status(200).json(response);
})
.catch(err => console.log(err));
Or you can call the validate() on the new instance of the model and if it is valid continue with update logic, e.g.
let user = new User({_id: userId,
firstName: req.body.firstName,
lastName: req.body.lastName,
phoneNumber: req.body.phoneNumber,
email: req.body.email,
role: req.body.role});
user.validate()
.then(() => {
// update logic
})
.catch((err) => {
// handle error
})
Look for more information on Mongoose validation with update.
Related
I'm consuming an API REST with NodeJs with POST, GET, DELETE and PUT verbs.
This is my mongoose Schema
const mongoose = require('mongoose')
const userSchema = mongoose.Schema({
"id": {
type: Number,
require: true
},
"email": {
type: String,
require: true
},
"first_name":{
type: String,
require: true
},
"last_name":{
type: String,
require: true
},
"company":{
type: String,
require: true
},
"url":{
type: String,
require: true
},
"text":{
type: String,
require: true
}
});
module.exports = mongoose.model('User', userSchema);
What I'm trying to do is that when I do a GET, I want to search by user ID and not by the ID that MongoDB gives the user.
The endpoint I want to use:
localhost:9000/api/user/[id]
The endpoint I don't want to use:
localhost:9000/api/user/[MongoDb id]
Also I'm trying that when I GET the user, I only receive the parameters that I have in the Mongoose Schema and not the ID that MongoDB gives me
Expecting:
If I search a user, I expect this resources:
{
id: 1,
email: "John#gmail.com",
first_name: "Smith",
last_name: "Smith",
company: "google",
url: "Smith.com",
text: "John Smith"
}
But not this
{
_id: 1nf19tg427gnvn9v
id: 1,
email: "John#gmail.com",
first_name: "Smith",
last_name: "Smith",
company: "google",
url: "Smith.com",
text: "John Smith"
_v: 1
}
This is how I GET the user:
function GetUser(req, res){
const { id } = req.params;
userSchema
.findById(id)
.then((data) => res.json(data))
.catch((error) => res.json({ msg: error }))
};
EDIT 1:
As #Tromgy said, to find by user ID, I changed the GET to this:
function GetUser(req, res){
const { id } = req.params;
userSchema
.find({id})
.then((data) => res.json(data))
.catch((error) => res.json({ msg: error }))
};
Now, to finde only the objects I need, I tried only searching first_name and id with .then((data) => res.json(data.id, data.first_name)) but Postman stays loading and never show anything...
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
I'm having this issue. I made a login system which works like that:
Login with Discord
If exists - if user changed their Discord information, update and return user information. If they didn't change any of their Discord account information, just return user information
If doesn't exist - create user and return user information
That's my user.js schema:
import mongoose from 'mongoose';
const Schema = mongoose.Schema
const userSchema = new Schema({
userId: {type: String, required: true, unique: true},
username: {type: String, required: true, unique: false},
type: {type: String, required: true},
discrim: {type: String, required: true},
email: {type: String, required: true},
avatar: {type: String, required: true},
banned: {type: Boolean, required: true}
})
const User = mongoose.model('User', userSchema)
export default User
And there's login system login (just a prototype to show the problem):
app.post("/login-test", (req, res) => {
User.findOne({"userId": req.body.uid}).then((result) => {
console.log(result)
if(result == null){
const newUser = new User({
userId: req.body.uid,
username: "sharek1234",
discrim: "1236",
email: "sharekxass#gmail.com",
avatar: "bereknone",
banned: false,
type: "user"
})
newUser.save().then((resp) => {
res.json(resp)
})
}else{
User.updateOne({"userId": req.body.uid}, {
discrim: "1236",
email: "sharekxass#gmail.com",
avatar: "bereknone",
banned: false,
type: "user"
}).then((resp) => {
res.json(resp)
}).catch((pog) => {
console.log(pog)
res.json(pog)
})
}
})
})
But when I try to log in with existing user after Discord account information change, where other user with name "MyName123" already exists, for example:
Before change:
UserID: "12345"
Username: "MyName"
Discrim: "1000"
After change:
UserID: "12345"
Username: "MyName123"
Discrim: "9999"
it returns E11000 error.
E11000 duplicate key error collection: ShardDB.users index: username_1 dup key: { username: "MyName123" }. Please note that username field in userSchema is not unique.
I have no idea how to fix this.
I solved this, I don't know why but there was "username_1" index in my database, and it was set to unique. I deleted it and now everything works fine.
I'm making a POST request like this:
router.post('/register2', (req, res) => {
const newUser = new User({
name: req.body.name,
email: req.body.email,
password: req.body.password
});
newUser.save()
.then(res.json(newUser));
})
But, in Postman, no matter what I enter for my name/email/password values, it just returns the first set of values I ever tried. The id and date update each time, but name, email, and password ignore the input and give me the same things every time.
Here's the "User" that newUser refers to:
const UserSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
module.exports = User = mongoose.model('users', UserSchema);
I want to create a DB with Users which also have a reference to another DB called "Library" which has "favourites" and "likes". I will show the idea here:
User Model
const userSchema = Schema({
username: {type: String, minlength: 4, maxlength: 10, required: true, unique: true},
email: {type: String, required: true, unique: true},
password: {type: String, required: true},
isVerified: { type: Boolean, default: false },
library: {type: Schema.Types.ObjectId, ref: 'Library'}
}, { timestamps: true});
Library Model
const librarySchema = new Schema({
likes: [{
likeId: {type: String},
mediaType: {type: String}
}],
favourites: [{
favId: {type: String},
mediaType: {type: String}
}],
user: {type: Schema.Types.ObjectId, ref: 'User'}
});
Can you please tell me if this is the right way to implement these models or if there is a better way?
At the moment if I try to call
User.findOne({email: 'xxx#xxx.com'}).populate('library').exec(function (err, library)
it doesn't find anything...
Library POST request
router.post('/favourites', passport.authenticate('jwt', {session: false}), function (req, res) {
const favouritesFields = {};
if (req.body.favId) favouritesFields.favId = req.body.favId;
if (req.body.mediaType) favouritesFields.mediaType = req.body.mediaType;
Library.findOne({user: req.user._id}).then(library => {
if (library) {
Library.update({user: req.user._id}, {$push: {favourites: favouritesFields}})
.then(library => res.json(library));
} else {
new Library({user: req.user._id, favourites: favouritesFields}).save().then(library => res.json(library));
}
});
});
User POST request
router.post('/signup', function (req, res) {
const {errors, isValid} = validateSignupInput(req.body);
if (!isValid) {
return res.status(400).json(errors);
}
// Check if email already exists
User.findOne({email: req.body.email}, function (user) {
if (user) {
return res.status(400).json({
title: 'Email already exists'
});
}
});
// Create and save the new user
let user = new User({
username: req.body.username.toLowerCase(),
email: req.body.email.toLowerCase(),
password: bcrypt.hashSync(req.body.password, 10)
});
user.save(function (err, result) {
if (err) {
return res.status(500).json({
title: 'An error occurred during the signup',
error: err
});
}
res.status(201).json({
title: 'User created',
obj: result
});
Your problem is not with the query you're making. there is no foundUser.library because one was never added.
You're adding users to libraries, but you're not adding libraries to your users. if you run the following code in your app:
Library.find({}).populate("user").exec(function(err, foundLibraries){
if (err){
console.log(err);
} else {
console.log(foundLibraries);
}
});
You would see that the libraries have their "user" properties, that when populated contain the entire user document as an object. But, the reason that isn't working for foundUser.library when you query for users is that foundUser.library was never assigned. you know how you're assigning the email, username and password when creating users, you have to do the same for the library property. Or, in your case, since a library is only created after the user, you can just set the value of user.library in the callback of creating/saving the library.