I am a very beginner at APIs. I am trying to create a test API for my upcoming website but I am stuck at some point. I used SQL, and I am used to orders when inserting data into the database. While using MongoDB and the mongoose library at insertion the pre-made Schema's order lost after inserting data.
This is my model file for the user.
const mongoose = require("mongoose")
const UserSchema = mongoose.Schema({
username: {
type: String,
require: true,
minlength: 6,
maxlength: 64
},
password: {
type: String,
require: true,
minlength: 6,
maxlength: 255
},
email: {
verified: {type: Boolean, default: false},
type: String,
require: true,
minlength: 11,
maxlength: 64
},
description: {
type: String,
require: true,
minlength: 20,
maxlength: 5000
},
permissions: {
verified: {type: Boolean, default: false, require:true},
admin: {type: Boolean, default: false, require:true},
server: {type: Number, default: 0, require:true}
},
image: {
type: String,
require: true,
default: ""
},
reg_date: {
type: Date,
default: Date.now(),
require: true
}
});
module.exports = mongoose.model("User", UserSchema);
The body of the POST request:
{
"username": "nameblabla",
"email": "email#email.com",
"description": "akjfhajkléfhakaklsfhak-sfn-ak.sfnkl-asfnkléas-f",
"password": "pasfgasgasdg"
}
And the file of the POST request:
const express = require('express')
const router = express.Router();
const userModel = require('../models/User')
//VALIDATION
const joi = require('#hapi/joi');
const schema = joi.object({
username: joi.string().min(6).required(),
email: joi.string().min(6).required().email(),
description: joi.string().min(20).required(),
password: joi.string().min(6).required()
});
router.post('/signup', async (req,res) => {
//VALIDATE
const {error} = schema.validate(req.body, {abortEarly: false});
if(error){
let msg = ""
error.details.forEach(e => {
msg += e.message + "\n";
});
return res.status(400).send(msg)
}else{
const user = new userModel({
username: req.body.username,
email: req.body.email,
description: req.body.description,
password: req.body.password
});
try{
const savedUser = await user.save();
res.send(savedUser)
}catch(err) {
res.status(400).send(err);
}
}
})
module.exports = router
The inserted data looks like:
{
"permissions": {
"verified": false,
"admin": false,
"server": 0
},
"image": "",
"reg_date": "2020-04-06T14:50:02.910Z",
"_id": "5e8b41a0cddf023104955793",
"username": "Miraglia1",
"email": "email#email.com",
"description": "akjfhajkléfhakaklsfhak-sfn-ak.sfnkl-asfnkléas-f",
"password": "pasfgasgasdg",
"__v": 0
}
So when I save the data the default key:value pairs placed above the submitted data. But is there any solution to keep the Schema order? Please help me.
Thanks in advance. :)
Related
I'm creating Social Network app using MERN. I have made it so far that users can create posts and have followers and followings. My task is to make an option on the post so it can be public or private. I don't know how to do that. Has anybody idea or example of code how to do it? Thanks!
This is my post model:
const mongoose = require("mongoose");
const postSchema = new mongoose.Schema({
caption: String,
image: {
public_id: String,
url: String,
},
owner: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
createdAt: {
type: Date,
default: Date.now,
},
likes: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
],
comments: [
{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
comment: {
type: String,
required: true,
},
},
],
visibility: {
type: String,
enum : ["public", "private"],
default: "public"
},
});
module.exports = mongoose.model("Post", postSchema);
This is my user model:
const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const crypto = require("crypto");
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, "Please enter a name"],
},
avatar: {
public_id: String,
url: String,
},
email: {
type: String,
required: [true, "Please enter an email"],
unique: [true, "Email already exists"],
},
password: {
type: String,
required: [true, "Please enter a password"],
minlength: [6, "Password must be at least 6 characters"],
select: false,
},
posts: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Post",
},
],
followers: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
],
following: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
],
resetPasswordToken: String,
resetPasswordExpire: Date,
});
userSchema.pre("save", async function (next) {
if (this.isModified("password")) {
this.password = await bcrypt.hash(this.password, 10);
}
next();
});
userSchema.methods.matchPassword = async function (password) {
return await bcrypt.compare(password, this.password);
};
userSchema.methods.generateToken = function () {
return jwt.sign({ _id: this._id }, process.env.JWT_SECRET);
};
userSchema.methods.getResetPasswordToken = function () {
const resetToken = crypto.randomBytes(20).toString("hex");
this.resetPasswordToken = crypto
.createHash("sha256")
.update(resetToken)
.digest("hex");
this.resetPasswordExpire = Date.now() + 10 * 60 * 1000;
return resetToken;
};
module.exports = mongoose.model("User", userSchema);
and here is the logic how of creating post:
exports.createPost = async (req, res) => {
try {
const myCloud = await cloudinary.v2.uploader.upload(req.body.image, {
folder: "posts",
});
const newPostData = {
caption: req.body.caption,
image: {
public_id: myCloud.public_id,
url: myCloud.secure_url,
},
owner: req.user._id,
};
const post = await Post.create(newPostData);
const user = await User.findById(req.user._id);
user.posts.unshift(post._id);
await user.save();
res.status(201).json({
success: true,
message: "Post created",
});
} catch (error) {
res.status(500).json({
success: false,
message: error.message,
});
}
};
In your post's model, you can add a "visibility" field with private & public as possible values (one of them default, obviously). Then every time you are querying for that post or list of posts, use that visibility column to decide if the user should be served that post or a 403 response page.
Check this for enum validation.
I'm junior software developer which main task is develop a auth nodejs app. When I took the part of reset_password functionality, return me error message that I'm not able to solve.
This is my env file:
TOKEN_SECRET: T0K€N
RESET_PASSWORD_TOKEN: T0K€N
This is my user's model:
../models/user.js
const mongoose = require('mongoose');
const userSchema = mongoose.Schema({
name: {
type: String,
required: true,
min: 6,
max: 255
},
email: {
type: String,
required: true,
min: 6,
max: 1024
},
password: {
type: String,
required: true,
minlength: 6
},
subscribe: {
type: Boolean,
default: false
},
date: {
type: Date,
default: Date.now
},
role: {
type: String,
required: false,
minlength: 4
},
address: {
type: String,
required: false,
minlength: 4,
defaultValue: ""
},
nonce: {
type: String,
required: false,
minlength: 4,
defaultValue: ""
},
})
module.exports = mongoose.model('User', userSchema);
This is my token's model:
../models/token.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema({
token: {
type: String,
required: true,
min: 6
},
id_user:{
type: String,
required: true,
min: 1
}
})
module.exports = mongoose.model('expired_tokens', Schema);
And finally, my user's controller:
const Joi = require('#hapi/joi');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const nodemailer = require("nodemailer");
//models
const User = require('../models/user');
const expired_token = require('../models/expired_token');
//nodemailer
const sendMail = require('../nodemailer');
ResetPassword: async (req, res) => {
const user = await User.findOne({ email: req.body.email });
if (!user) {
return res.status(400).json({
data: "",
message: "User with this email doesn't exist",
status: "error"
})
}
const token = jwt.sign({ id: user._id, email: user.email }, process.env.RESET_PASSWORD_TOKEN );
const data = {
from: 'noreply#hello.com',
to: email,
subject: 'Recuperacion de contraseña',
template: "reset password",
context: {
url: 'http://localhosy:3005/auth/reset_password?token=' + token,
name: user.fullName.split('').split('')[0]
}
}
const link = `${process.env.BASE_URL}/reset_password/${user._id}/${token.token} `;
await sendEmail(user.email, "Password Reset", link);
},
//routes
router.route('/reset_password')
.post(verifyToken, controller.ResetPassword);
Nevertheless, when I tested in postman, return me the error message:
Probably, the error has to do with the enviroment variable in env. file. So, I'd be really grateful if someone could bring me his help or advice in order to solve this situation.
Thanks and have a nice day!
Hi everyone I am trying to add the user's name in the post that is being created by that user but I'm running into trouble.
This is the part of the post where the user's name should be displayed
<Link style={{ textDecoration: "none", color: "black" }}>
<h4
onClick={() => this.handleShowUserProfile(event.userId)}
className="host-name"
>
{getUser(event.userId).name}
</h4>
</Link>
This is where the user is being grabbed from the database
import http from "./httpService";
const apiEndPoint = "http://localhost:3100/api/users";
export function getUsers() {
return http.get(apiEndPoint);
}
export function getUser(userId) {
return http.get(apiEndPoint + "/" + userId);
}
in the backend this is how the user schema looks like
const jwt = require("jsonwebtoken");
const config = require("config");
const Joi = require("joi");
const mongoose = require("mongoose");
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
minlength: 5,
maxlength: 50
},
bio: {
type: String,
required: true,
minlength: 200,
maxlength: 400
},
interests: {
type: Array
},
email: {
type: String,
required: true,
minlength: 5,
maxlength: 255,
unique: true
},
password: {
type: String,
required: true,
minlength: 5,
maxlength: 1024
},
isAdmin: Boolean
});
userSchema.methods.generateAuthToken = function() {
const token = jwt.sign(
{ _id: this._id, isAdmin: this.isAdmin },
config.get("jwtPrivateKey")
);
return token;
};
const User = mongoose.model("User", userSchema);
function validateUser(user) {
const schema = {
name: Joi.string()
.min(5)
.max(50)
.required(),
bio: Joi.string()
.required()
.min(200)
.max(400),
interests: Joi.array().required(),
email: Joi.string()
.min(5)
.max(255)
.required()
.email(),
password: Joi.string()
.min(5)
.max(255)
.required()
};
return Joi.validate(user, schema);
}
module.exports.User = User;
module.exports.validate = validateUser;
this is the event schema...
const Joi = require("joi");
const mongoose = require("mongoose");
const { categorySchema } = require("./category");
const { userSchema } = require("./user");
const Event = mongoose.model(
"Events",
new mongoose.Schema({
image: {
type: String
},
title: {
type: String,
required: true,
minlength: 5,
maxlength: 50,
trim: true
},
user: {
type: userSchema,
required: true
},
details: {
type: String,
required: true,
minlength: 200,
maxlength: 300,
trim: true
},
category: {
type: categorySchema,
required: true
},
numOfAttendies: {
type: Number,
required: true,
min: 3,
max: 10000
}
})
);
this is the handleShowUserProfile
handleShowUserProfile = id => {
this.setState({
showUserProfile: true,
shownUserID: id,
user: getUser(id)
});
};
handleHideUserProfile = () => {
this.setState({
showUserProfile: false
});
};
If I had to guess, getUser() likely returns a promise, not a value. You'll need to look into reading the name from props(state) and updating state instead of trying to do it all right there.
Try logging out that get request. See if the results it returns match up with what you're trying to get from it.
export function getUser(userId) {
const results = http.get(apiEndPoint + "/" + userId);
console.log("results of getUser ::: ", results)
return results
}
The getUser function is an asynchronous function so it will return a promise in most cases you need to use state and the lifecycle methods to fetch and set the username.
Can you update your handleShowUserProfile method like the following?
handleShowUserProfile = async (id) => {
const user = await getUser(id);
this.setState({
showUserProfile: true,
shownUserID: id,
user: user
});
};
I assume your api returns data for http://localhost:3100/api/users/:userId endpoint.
After you check your api and sure that it returns data, you need to check if you send a valid userId in this line.
onClick={() => this.handleShowUserProfile(event.userId)}
Lastly you should also change the following line since the user data is in the state.
{getUser(event.userId).name} => {this.state.user && this.state.user.name }
I am new to programming in NodeJS and was looking for solutions around how to populate response attributes in json Object which are of type Object Array.
Below is the sample response from my service -
{
"Roles": [
"5b7fd72537651320a03494f8",
"5b7fdc06e3fcb037d016e26a"
],
"_id": "5b8e530be2d4630fd4f4b34a",
"Username": "ctucker",
"FirstName": "Chris",
"LastName": "Tucker",
"Email": "ctucker#innovate.com",
"createdAt": "2018-09-04T09:40:27.091Z",
"updatedAt": "2018-09-04T09:40:27.091Z",
"__v": 0
}
I would like to populate the ObjectId's in the Roles array attribute with the actual name instead of the Ids.
How should I be doing this.
Following is the way, I have defined the User and Roles schema
User Model
const userSchema = new mongoose.Schema({
Username: {
type: String,
required: true,
minlength: 5,
maxlength: 255,
unique: true
},
FirstName: {
type: String
},
LastName: {
type: String
},
Email: {
type: String,
required: true,
minlength: 5,
maxlength: 255
},
Password: {
type: String,
required: true,
minlength: 5,
maxlength: 1024
},
Roles: [{type: mongoose.Schema.Types.ObjectId, ref: Roles}],
Active: {type: Boolean, default: true},
SuperUser: {type: Boolean, default: false}
},{
timestamps: true
});
Roles Model
const rolesSchema = new mongoose.Schema({
RoleName: {
type: String,
required: true,
minlength: 5,
maxlength: 255,
unique: true
},
Description: {
type: String
},
Active: {type: Boolean, default: true}
}, {
timestamps: true
});
Create User
router.post('/', [auth, admin], async (req, res) => {
const { error } = validate(req.body);
if (error) return res.status(400).send(error.details[0].message);
let user = await User.findOne({ Username: req.body.Username });
if (user) return res.status(400).send('User with username: ', req.body.Username, 'already exists. Please try with any other username');
let roleInput = [];
if(req.body.Roles !== null) {
req.body.Roles.forEach( async (element) => {
objectId = mongoose.Types.ObjectId(element);
roleInput.push(objectId);
});
}
console.log('Role info ', roleInput);
user = new User(_.pick(req.body, ['Username', 'FirstName', 'LastName' ,'Email', 'Password', 'Active','SuperUser']));
console.log('User Input => ', user);
const salt = await bcrypt.genSalt(10);
user.Password = await bcrypt.hash(user.Password, salt);
roleInput.forEach( async (objectId) => {
user.Roles.push(objectId);
});
console.log('User Input after role addition => ', user);
await user.save();
const token = user.generateAuthToken();
res.header('x-auth-token', token).send(_.pick(user, ['_id', 'FirstName', 'LastName' ,'Email', 'Roles']));
});
I would like to get the RoleName instead of the ObjectId in response.
Any help in this regard would be appreciated.
Forgot to mention that I already tried using the populate method, but I see the following exception.
How I am using the populate method -
router.get('/', auth, (req, res, next) => {
var request_params = url.parse(req.url,true).query;
var searchQuery = (request_params.mode === 'active') ? {'Active': true} : {};
User.find(searchQuery)
.populate('Roles')
.select(['-Password', '-Active', '-SuperUser'])
.then((users) => {
res.send(users);
}, (error) => {
next(error);
});
});
Exception -
<!DOCTYPE html>
<html>
<head>
<title></title>
<link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body>
<h1>Schema hasn't been registered for model "[object Object]".
Use mongoose.model(name, schema)</h1>
<h2></h2>
<pre>MissingSchemaError: Schema hasn't been registered for model "[object Object]".
Use mongoose.model(name, schema)
at new MissingSchemaError (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\lib\error\missingSchema.js:20:11)
at NativeConnection.Connection.model (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\lib\connection.js:791:11)
at getModelsMapForPopulate (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\lib\model.js:4016:20)
at populate (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\lib\model.js:3505:21)
at _populate (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\lib\model.js:3475:5)
at utils.promiseOrCallback.cb (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\lib\model.js:3448:5)
at Object.promiseOrCallback (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\lib\utils.js:232:14)
at Function.Model.populate (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\lib\model.js:3447:16)
at cb (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\lib\query.js:1678:17)
at result (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\node_modules\mongodb\lib\utils.js:414:17)
at executeCallback (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\node_modules\mongodb\lib\utils.js:406:9)
at handleCallback (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\node_modules\mongodb\lib\utils.js:128:55)
at cursor.close (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\node_modules\mongodb\lib\operations\cursor_ops.js:211:62)
at handleCallback (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\node_modules\mongodb\lib\utils.js:128:55)
at completeClose (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\node_modules\mongodb\lib\cursor.js:887:14)
at Cursor.close (C:\Vijay-Docs\Personal\vuejs-ws\AgileCenter\agilecenterservices\node_modules\mongoose\node_modules\mongodb\lib\cursor.js:906:10)</pre>
</body>
</html>
Understood the problem.
Within the User schema definition, I had to define the Roles Schema as well.
const rolesSchema = new mongoose.Schema({
RoleName: {
type: String,
required: true,
minlength: 5,
maxlength: 255,
unique: true
},
Description: {
type: String
},
Active: {type: Boolean, default: true}
}, {
timestamps: true
});
const Roles = mongoose.model('Roles', rolesSchema);
const userSchema = new mongoose.Schema({
Username: {
type: String,
required: true,
minlength: 5,
maxlength: 255,
unique: true
},
FirstName: {
type: String
},
LastName: {
type: String
},
Email: {
type: String,
required: true,
minlength: 5,
maxlength: 255
},
Password: {
type: String,
required: true,
minlength: 5,
maxlength: 1024
},
Roles: [{type: mongoose.Schema.Types.ObjectId, ref: 'Roles'}],
Active: {type: Boolean, default: true},
SuperUser: {type: Boolean, default: false}
},{
timestamps: true
});
After adding the roles schema definition within the user schema, the response from postman does populate the RoleName in response.
{
"Roles": [
{
"RoleName": "AC-User"
},
{
"RoleName": "AC-PMLead"
}
],
"_id": "5b8e48e2f3372a098c6e5346",
"Username": "mcmillan",
"FirstName": "McMillan",
"LastName": "McMillan",
"Email": "mcmillan#innovate.com",
"createdAt": "2018-09-04T08:57:07.029Z",
"updatedAt": "2018-09-04T08:57:07.029Z",
"__v": 0
}
Simple using mongoose populate() function
const user = await User.findOne().populate('Roles');
// output => user.Roles // will be array of mongo documents
Cheers
You can use Mongoose's populate to populate RoleName instead of ObjectId refs
Mongoose has a more powerful alternative called populate(), which lets you reference documents in other collections.
'use strict';
let user = await User.findOne({
Username: req.body.Username
}).populate('Roles');
if (user && user.Roles.length > 0) {
for (let role of user.Roles) {
console.log(role.RoleName);// logs `RoleName`
}
}
So I am working on building a web app and I have a lot of it working. However I am trying to link my user._id to my items database. But when I try to post an item using postman the app crashes saying it cant read property '_id' of null. I know I am missing something but I honestly can't figure out what other code I need to implement. Any help would be great. Thanks
Here is the code for the UserSchema:
const mongoose = require('mongoose');
const passportLocalMongoose = require("passport-local-mongoose");
const UserSchema = new mongoose.Schema({
username: {
type: String,
trim: true,
unique: true,
required: true,
minlength: 3,
maxlength: 15
},
firstName: {
type: String,
required: true,
minlength: 3,
maxlength: 15
},
lastName: {
type: String,
required: true,
minlength: 3,
maxlength: 15
},
email: {
type: String,
unique: true,
required: true
},
resetPasswordToken: String,
resetPasswordExpires: Date,
isAdmin: {
type: Boolean,
default: false
}
});
UserSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("user", UserSchema);
Here is the code for the ItemSchema:
const mongoose = require('mongoose');
const User = require('./user');
const ItemSchema = new mongoose.Schema({
name: {
type: String,
required: true,
minlength: 3,
maxlength: 20
},
description: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
createdBy: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
},
createdAt: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model("items", ItemSchema);
And here is the code for the route thats throwing the error:
const express = require("express");
const router = express.Router();
const User = require("../models/user");
router.route("/item/add")
.post(function(req, res) {
User.findById(req.user._id, function(user, err) {
if (err) {
console.log(err);
}
var item = new Item();
item.name = req.body.name;
item.description = req.body.description;
item.price = req.body.price;
item.createdBy = { id: req.user._id, username: req.user.username };
item.save(function(err) {
if (err) {
res.send(err);
}
res.json({ message: "Item was successfully saved" });
console.log(item);
});
});
});
You need to send your data in json format in postman for example:
{'id':1, 'name':'jhon doe', 'email':'jhondoe#example.com'}
in your backend file you need to call
req.body.id not req.user._id