After building an app using the MERN Stack and finished a simple CRUD API for the items of the users I wanted to add a 'categories' property to the user model which he then can add to his items...
To explain the app I planned to attach some default data i.e categories to each user while posting his data to MongoDB. Unfortunately, I am failing to 'post' those categories to the database.
Here are different request and schema combinations I have tried out
As its own schema
user route
const User = require("../models/User")
// #route POST api/users
// #desc Regiter a user
// #access Public
router.post(
"/",
[
check("name", "Please add name")
.not()
.isEmpty(),
check("email", "Please include a valid email").isEmail(),
check(
"password",
"Please enter a password with 6 or more characters"
).isLength({ min: 6 })
],
async (req, res) => {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() })
}
const { name, email, password } = req.body
console.log(categories)
try {
let user = await User.findOne({ email })
if (user) {
return res.status(400).json({ msg: "User already exists" })
}
user = new User({
name,
email,
password,
})
const salt = await bcrypt.genSalt(10)
user.password = await bcrypt.hash(password, salt)
await user.save()
const payload = {
user: {
id: user.id
}
}
jwt.sign(
payload,
config.get("jwtSecret"),
{
expiresIn: 360000
},
(err, token) => {
if (err) throw err
res.json({ token })
}
)
} catch (err) {
console.error(err.message)
res.status(500).send("Server Error")
}
}
)
module.exports = router
request in AuthState
// Register User
const register = async formData => {
console.log(formData)
const expandedFormData = {
...formData,
categories: [
{ name: "Testcategory1", test: 1 },
{ name: "Testcategory2", test: 2 }
]
}
const config = {
headers: {
"Content-Type": "application/json"
}
}
try {
const res = await axios.post("/api/users", expandedFormData, config)
console.log(expandedFormData)
dispatch({
type: REGISTER_SUCCESS,
payload: res.data
})
loadUser()
} catch (err) {
dispatch({
type: REGISTER_FAIL,
payload: err.response.data.msg
})
}
}
schema
const mongoose = require("mongoose")
const categorieSchema = mongoose.Schema({
label: String,
test: Number
})
const UserSchema = mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
},
categories: [categorieSchema]
})
module.exports = mongoose.model("user", UserSchema)
2.
request in AuthState
....
const expandedFormData = {
...formData,
categories: [{ name: "Testcategory1" }, { name: "Testcategory2" }]
}
....
schema
....
categories: [
{
name: String
}
]
....
3.
request in AuthState
schema
same as 2.
....
categories: [
{
name: {
type: String
}
}
]
....
4.
request
schema
same as 2.
....
categories: [String]
....
I've also read these threads but they did not provide new information:
- Mongoose schema array of objects
- Save arrays in Mongoose schema
The full application can be viewed at https://github.com/mortizw/Repio-2.0
Next to some ideas on how to make this model work, I would be happy about some tips on how to iteratively test /approach such a 'schema-problem' as you can with console logging something.
When you are creating new User, you are not passing Categories that's why its not being saved.
First of all this Schema will work perfectly fine
categories: [
{
name: {
type: String
}
}
]
Then you need to update your user route to this
const { name, email, password,categories } = req.body
user = new User({
name,
email,
password,
categories
})
Also make sure you only pass name in categories from frontend because your schema only have name.
Your frontend should be like this
const expandedFormData = {
...formData,
categories: [
{ name: "Testcategory1"},
{ name: "Testcategory2"}
]
}
Related
This is My User Model
const mongoose = require('mongoose')
const bcrypt = require('bcrypt');
const userSchema = mongoose.Schema({
contact: {
type: Number,
required: true,
},
email: {
type: String,
required: true,
unique: true
},
score: {
type: Number,
default:0,
},
password: {
type: String,
required: true,
},
role: {
type: String
},
blocked: {
type: Boolean, default: false
}
}, { timestamp: true }
)
userSchema.statics.hashPassword = function hashPassword(password){
return bcrypt.hashSync(password,10);
}
userSchema.methods.isValid = function(hashedpassword){
return bcrypt.compareSync(hashedpassword, this.password);
}
module.exports = mongoose.model('user',userSchema)
This is my Controller
const User = require('../models/user')
const Otp = require('../models/otp')
const jwt = require('jsonwebtoken')
const sendMail = require('../mail/mail')
const bcrypt = require('bcryptjs')
exports.getCheck = (req, res, next) => {
res.json({ msg: "All ok" })
}
exports.registerStudent = async (req, res) => {
// const x = await check(req,res,req.body.email);
const user = new User({
contact: req.body.phone,
email: req.body.email,
role: "student",
password: User.hashPassword(req.body.p1),
});
User.find({ email: req.body.email }, (err, users) => {
if (err) {
console.log("err in finding email ");
res.json({ msg: "some baler error!" });
}
if (users.length != 0) {
console.log("already user with this email");
res.json({ msg: "already user exist with this email!" });
}
else {
user.save((error, registeredUser) => {
if (error) {
console.log(error);
res.json({ msg: "some error!" });
}
else {
let payload = { subject: registeredUser._id }
let token = jwt.sign(payload, 'secretkey')
res.status(200).json({ token: token })
}
})
}
})
}
PLeasee HELP me out i'm getting confused
IGNORE
I ran into the same problem. 1. I saved my ATLAS_URI ID to a file called .env 2. My .env file was in the wrong directory, that's how the problem cause 3. Solution: I used "ls -a" command to make sure my .env file is in the same location as my server
IGNORE
PLeasee HELP me out i'm getting confused
I am trying to create a followers/following function in my project. However I cannot seem to update the DB correctly. I'm able to send the ids as they both print when I console.log but nothing in my DB updates and I do not get any response in my frontend.
route
app.put('/api/follow', async function (req, res, next){
const { id } = req.query;
const userFrom = req.body.data
console.log('OTHER USER ID',id)
console.log('CURRENT ID', userFrom)
User.findByIdAndUpdate(id), {
$push:{followers:req.body.data}
},{new:true},
(err,result)=>{
if(err) {
if(err) return res.status(400).send(err)
}
User.findByIdAndUpdate(req.body.data), {
$push:{following:id}
},{new:true}.then(result=> {
res.json(result)
}).catch(err=>{
return res.status(422).json({error:err})
})
}
})
user model
const mongoose = require("mongoose");
const User = mongoose.model(
"User",
new mongoose.Schema({
username: String,
email: String,
password: String,
phoneNo: String,
bio: String,
filePath: String,
following: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
],
followers: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
})
);
module.exports = User;
my route end function
const clickHandler = () => {
const currentID = currentUser.id;
const id = this.props.id;
console.log('CURRENT ID',currentID)
console.log('otherUserID',id)
Axios.put(`http://localhost:8080/api/follow/?id=${id}`, { data: currentID }, { headers: authHeader() })
.then(response => {
if (response.data.success) {
console.log('FOLLOWED', response.data)
// this.setState({ userDetails: response.data.details })
} else {
alert('Error')
}
})
}
This should be
User.findByIdAndUpdate(id, {
You should not close the bracket after id but after new: true})
I have created 2 table Token and users and i want to associate it. After associating i want to set the token value to my user and associate my userId with the token table. for this i have used setUser & addToken but my setUser is throwing error while addToken is working fine.
const { nanoid } = require('nanoid/async')
const argon2 = require('argon2')
const jwt = require('../lib/jwt')
module.exports = (sequelize, Sequelize, Token, Task) => {
const User = sequelize.define("users", {
age: {
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
email: {
type: Sequelize.STRING
},
password: {
type: Sequelize.STRING
}
}, {
defaultScope: {
attributes: { exclude: ['password'] },
},
scopes: {
withPassword: {
attributes: {}
}
}
});
User.beforeCreate(async (user, options) => {
const hashedPassword = await argon2.hash(user.password)
user.password = hashedPassword
})
User.prototype.generateToken = async function generateToken() {
const jwtid = await nanoid()
const token = jwt.sign({ sub: this.id }, { jwtid })
const userToken = await Token.create({ jti: jwtid })
await this.setUser(token)
await this.addToken(userToken)
return token
}
User.prototype.verifyPassword = async function verifyPassword(password) {
console.log('verify Password instance method', { password, hash: this.password })
return argon2.verify(this.password, password)
}
User.hasMany(Token)
Token.belongsTo(User)
User.hasMany(Task)
Task.belongsTo(User)
return User;
};
Please help me fix this so that my token table associate with user table
I am writing a multi-user online dictionary. I want to implement a leadership board, e.i. "score" attribute increases, as soon as a user adds a word. I have a rough idea on how to do it, and tried one solution, however it does not work. Could you please guide me?
Word API route
const express = require('express');
const router = express.Router();
const Word = require('../../models/Word');
const User = require('../../models/User');
const validateWordInput = require('../../validation/word');
const passport = require('passport');
// #route POST api/words
// #desc Add words to profile
// #access Private
router.post(
'/',
passport.authenticate('jwt', { session: false }),
(req, res) => {
const { errors, isValid } = validateWordInput(req.body);
// Check validation
if (!isValid) {
// Return any errors
return res.status(400).json(errors);
}
Word.find({}).then(word => {
if (
word.filter(
wrd =>
wrd.ugrWordCyr.toString().toLowerCase() ===
req.body.ugrWordCyr.toLowerCase()
).length !== 0
) {
return res
.status(404)
.json({ wordalreadyexists: 'Word already exists' });
} else {
const newWord = new Word({
user: req.user.id,
ugrWordCyr: req.body.ugrWordCyr,
rusTranslation: req.body.rusTranslation,
example: req.body.example,
exampleTranslation: req.body.exampleTranslation,
origin: req.body.origin,
sphere: req.body.sphere,
lexis: req.body.lexis,
grammar: req.body.grammar,
partOfSpeech: req.body.partOfSpeech,
style: req.body.style
});
newWord.save().then(word => res.json(word));
User.update(
{ _id: '5cf0cb78b3105d1ba8e30331' },
{ $inc: { score: 1 } }
);
}
});
}
);
User model
This is where a score attribute is located
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Create schema
const userSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
score: {
type: Number,
default: 0
},
avatar: {
type: String
},
date: {
type: Date,
default: Date.now
}
});
module.exports = User = mongoose.model('users', userSchema);
After successfully saving the word, we should update the user count
To update the respective user's score you can do the following:
newWord.save().then((word) => {
//now update user model
User.findOne({ _id: req.user.id }) <-- or an id you would like
.then((foundUser) => {
foundUser.score = foundUser.score + 1
foundUser.save()
.then((savedUser) => {
res.json({ word, savedUser })
})
.catch((err) => {
return res.status(400).json({ error: "could not add score"})
})
})
.catch((err) => {
return res.status(400).json({ error: "could not find user"})
})
})
I have a route for creating users in Node/Express. I am getting a weird error about a method on the model not existing.
Here is the model for users:
'use strict';
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
mongoose.Promsie = global.Promise;
const UserSchema = mongoose.Schema({
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
email: { type: String, required: true },
firstName: { type: String },
lastName: { type: String },
families: [
{
family_key: { type: String, required: true },
family_name: { type: String }
}
]
});
UserSchema.methods.apiRepr = function() {
return {
id: this._id,
firstName: this.firstName,
lastName: this.lastName,
username: this.username,
email: this.email,
families: this.families
};
};
UserSchema.methods.hashPassword = function(password) {
return bcrypt.hash(password, 10);
}
UserSchema.methods.validatePassword = function(password) {
return bcrypt.compare(password, this.password);
}
const User = mongoose.models.User || mongoose.model('User', UserSchema);
module.exports = { User };
Not particularly complicated. BUT, my the route is having trouble with the "hashPassword" method. When I try to use this route, I get an error that says "TypeError: User.hashPassword is not a function"
Here is the route (the issue is close to the bottom):
router.post('/', jsonParser, (req, res) => {
// checking that required fields are present
const requiredFields = ['username', 'password', 'email'];
const missingField = requiredFields.find(field => !(field in req.body));
if(missingField) {
return res.status(422).json({
code: 422,
reason: 'Validation Error',
message: 'Missing field',
location: missingField
});
}
// checking the format of string fields
const stringFields = ['username', 'password', 'email', 'lastname', 'firstname'];
const nonStringField = stringFields.find(
field => field in req.body && typeof req.body[field] !== 'string'
);
if (nonStringField) {
return res.status(422).json({
code: 422,
reason: 'Validation Error',
message: 'Incorrect field type: expected string',
location: nonStringField
});
}
// checking the trimming on fields
const trimmedFields = ['username', 'password', 'email'];
const nonTrimmedField = trimmedFields.find(
field => req.body[field].trim() !== req.body[field]
);
if (nonTrimmedField) {
return res.status(422).json({
code: 422,
reason: 'Validation Error',
message: 'Cannot start or end with whitespace',
location: nonTrimmedField
});
}
// checking length of fields with required length
const sizedFields = {
username: { min: 1 },
password: { min: 10, max: 72 }
};
const tooSmallField = Object.keys(sizedFields).find(field =>
'min' in sizedFields[field] &&
req.body[field].trim().length < sizedFields[field].min
);
const tooLargeField = Object.keys(sizedFields).find(field =>
'max' in sizedFields[field] &&
req.body[field].trim().length > sizedFields[field].max
);
if (tooSmallField || tooLargeField) {
return res.status(422).json({
code: 422,
reason: 'Validation Error',
message: tooSmallField
? `Must be at least ${sizedFields[tooSmallField].min} characters long`
: `Must be at most ${sizedFields[tooLargeField].max} characters long`,
location: tooSmallField || tooLargeField
});
}
// creating the user
let { username, firstname, lastname, families, email, password } = req.body;
return User.find({ username })
.count()
.then(count => {
if(count > 0) {
return Promise.reject({
code: 422,
reason: 'Validation Error',
message: 'Username already taken',
location: 'username'
});
}
return User.hashPassword(password);
})
.then(hash => {
return User.create({ username, firstname, lastname, families, email, password: hash })
})
.then(user => {
return res.status(201).json(user.apiRepr());
})
.catch(err => {
console.error(err)
res.status(500).json({ code: 500, message: 'Internal server error'})
})
})
It does not like the return User.hashPassword(password) part. Any thoughts about what is causing this? I'm copying from a working app. Not sure what I'm doing wrong here.
The methods in node.js can not be used directly using the SchemaName you need to create an object of the schema name and then use the methods of the schema.
Ex:
var AnimalSchema = new Schema({
name: String
, type: String
});
AnimalSchema.methods.findSimilarType = function findSimilarType (cb) {
return this.model('Animal').find({ type: this.type }, cb);
};
var Animal = mongoose.model('Animal', AnimalSchema);
var dog = new Animal({ name: 'Rover', type: 'dog' });
dog.findSimilarType(function (err, dogs) {
if (err) return ...
dogs.forEach(..);
})
Source: http://mongoosejs.com/docs/2.7.x/docs/methods-statics.html
In your code you are trying to access the methods from the model.
Instantiate the model then use the methods.
If need use like the way you are using in the code try using function instead of methods.
module.exports.funtionName = function(/*function params*/){
//function body here
};