How should I access/modify nested mogoose schema? - node.js

I'm not sure how to approach accessing/modifying nested schemas in mongoDB. I have a User schema which has standard info about the user, this also contains a schema for Pages which is a way to organize a user's Bookmarks. I'm developing a backend API that would allow the user to delete a specific bookmark on the page their currently on.
I have a set of mongoose Schema as follows:
user.model.js
const mongoose = require('mongoose');
const Bookmark = new mongoose.Schema({
url: {type: String, required: true, unique: true},
image: {type: String},
title: {type: String},
description: {type: String},
tags: [String],
});
const Page = new mongoose.Schema({
name: {type: String, required: true, unique: true},
image: {type: String},
bookmarks: [Bookmark],
});
const User = new mongoose.Schema({
email: {type: String, required: true, unique: true },
password: {type: String, required: true},
pages: [Page],
},
{collection: 'user-data'}
);
const model = mongoose.model('Userdata', User);
module.exports = model;
I need to build a backend that would remove a specified Bookmark from the current User. Can I simply use findbyIDandRemove()?
index.js
const express = require("express");
const mongoose = require("mongoose");
const jwt = require("jsonwebtoken");
const User = require("./models/user.model");
const app = express();
app.post("/api/remove_bookmark", async (res, req) => {
const token = req.headers["x-access-token"];
try{
const decoded = jwt.verify(token, secret);
const email = decoded.email;
const bookmark = req.body.bookmark;
const user = await User.findOne({email: email});
if(user)
{
await User.findByIdAndRemove(bookmark._id);
}
} catch (err) {
return res.json({status: "error", error: "invalid token"});
}
});
Any help or articles that would pertain to understanding nested schemas would be greatly appreciated.

Related

What is wrong with my React.js code for blog comment feature

I created a comment feature in my node.js blog application. I am using Mongodb data base. Everything works fine in postman. I can create new comments. However, I am trying to replicate the same in my client side via React.js. I think I have exhausted all my ideas. Here is the error message I keep getting each time I try to create a new comment from client side: This error is console.log from the catch(err) in backend.
undefined
CastError: Cast to ObjectId failed for value ":id" (type string) at path "_id" for model "Post"
Post Model
//creating the user models for the database
const mongoose = require("mongoose"); //import mongoose
const Schema = mongoose.Schema;
const PostSchema = new mongoose.Schema(
{
title:{
type: String,
required: true,
unique: true,
},
description:{
type: String,
required: true,
},
postPhoto:{
type: String,
required:false,
},
username:{
type: Schema.Types.ObjectId,
ref: 'User'
},
categories:{
type: Array,
},
comments: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment',
unique: true,
}]
}, {timestamps: true},
);
//exporting this schema
module.exports = mongoose.model("Post", PostSchema); //the module name is "Post"
Comment Model
const mongoose = require("mongoose"); //import mongoose to be used
const Schema = mongoose.Schema;
const CommentSchema = new mongoose.Schema(
{
commentdescription:{
type: String,
required: true,
},
author:{
type: Schema.Types.ObjectId,
ref: 'User',
},
}, {timestamps: true}
);
//exporting this schema
module.exports = mongoose.model("Comment", CommentSchema); //the module name is "Post"
create new comment and push to post codes
//creating catergory logic
router.post("/posts/:id/comment", async (req, res) =>{
const newComment = new Comment(req.body);//we create a new comment for the database
try{
//we need to try and catch the new comment and save it
const currentPost = await Post.findByIdAndUpdate(req.params.id)//we need to find the post that has the comment via the id
currentPost.comments.push(newComment)//we need to push the comment into the post
await newComment.save();
await currentPost.save()//we saved the new post with the comment
res.status(200).json(currentPost)
}catch(err){
console.log(err)
res.status(500).json(err)
}
})
What I have written in React
const [commentdescription, setCommentDescription] = useState('')
const [postId, setPostId] = useState()
//function to create comment
const handleComment = async ()=>{
const newComment = {
_id: postId,
author: user._id,
commentdescription,
};
try{
await axios.post("/posts/:id/comment", newComment);
}catch(err){
console.log(err)
}
}
Everything works fine in postman. I know I am missing something in react.
You need to pass actual ID to that POST request url.
await axios.post(`/posts/${postId}/comment`, newComment);
When POSTing to /posts/:id/comment, your parsed value (req.params.id) will literally be :id, which cannot be casted as ObjectId.

how to reference a model in another model using mongoose?

I'm pretty new to node and mongoose, still learning a lot. Basically I am trying to create a forum page. I have a forumpost schema and I have recently added in a new field that I would like to show which user posted it. I have read other questions on this online and I was able to follow the code on there however mine is still not working. When i check my data in atlas it is still missing the new 'submitted by' field that I added. I have already deleted the 'collection' and have started over but it is still missing. Any help would be appreciated. Heres my models below as well as a screencap of how the data is being posted to the db.
**Post Form Schema**
const mongoose = require('mongoose');
const PostSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
body: {
type: String,
required: true,
},
date: {
type: Date,
default: Date.now,
required: true,
},
submittedBy: { *(this is where I would like to get the user who submitted the form)*
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
extraInfo: {
type: String,
default: 'Other info goes here',
}
})
const Post = mongoose.model('Post', PostSchema);
module.exports = Post;
**Users Form Schema**
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
const User = mongoose.model('User', UserSchema);
module.exports = User;
EDIT: heres my newpost route
const express = require('express');
const Post = require('../models/post');
const router = express.Router();
const {ensureAuthenticated} = require("../config/auth.js");
router.get('/', ensureAuthenticated, (req, res) => {
res.render('newPost')
})
router.post('/', ensureAuthenticated, (req, res) => {
const post = new Post(req.body);
console.log(req.body)
post.save()
.then((result) => {
res.redirect('/dashboard')
})
.catch((err) => {
console.log(err)
})
})
module.exports = router;
If I'm not mistaken, you validate if is authenticated with the "ensureAuthenticated" middleware (the user ID should be there) but when creating the "Post" you only do it with the body data.
It is something like this ( you should replace "userId" with your property name):
const post = new Post({ ...req.body, submittedBy: userId })

how to return the user email

I have created post route to store posts in the database. It's a protected route so user can store post only after entering the login details. When I post in postman, I've seen that the user email is not returned in the object. Even in the mongodb collection, I don't see the email associated with the post. How do I include the email as well with the post object. I don't want the user to enter the email again and again when posting because they have already logged in. So I kinda want to store the email automatically with the post. Hope I make sense. Can someone help me with this?
Right now the object is kinda stored like this in the posts collection in mongodb
_id: ObjectId("5f1a99d3ea3ac2afe5"),
text: "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. ",
user:ObjectId("5f1a99d3eac2c82afe5"),
age:20,
country:"India",
gender:"male",
date:2020-07-24T08:23:35.349+00:00,
__v:0
I want the email too in the above object.
Post model
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const PostSchema = new Schema ({
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
text: {
type: String,
required: true
},
name: {
type: String
},
email: {
type: String
}
,
age: {
type: Number,
required: true
},
gender: {
type: String,
required: true
},
country: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
})
module.exports = Post = mongoose.model('post', PostSchema)
post route
const express = require('express');
const router = express.Router();
const auth = require('../../middleware/auth')
const { check, validationResult} = require('express-validator');
const User = require('../../models/User')
const Post = require('../../models/Post')
router.post('/', [auth, [
check('text', 'Text is required').not().isEmpty()
]], async (req,res)=>{
const errors = validationResult(req);
if(!errors.isEmpty()){
return res.status(400).json({errors: errors.array()})
}
try {
const user = await (await User.findById(req.user.id)).isSelected('-password')
const newPost = new Post({
text: req.body.text,
name: user.name,
user: req.user.id,
age: req.body.age,
country: req.body.country,
gender: req.body.gender,
email: req.user.email // this email is not stored with the post and I want this to be automatically posted in the collection without the user having to type it again to save the post
})
const post = await newPost.save();
res.json(post);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error')
}
})
module.exports = router;
User model
const mongoose = require('mongoose');
const UserSchema = new 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
}
})
module.exports = User = mongoose.model('user', UserSchema);
Change isSelected to select
const user = await (await User.findById(req.user.id)).isSelected(password')
What I potentially see the problem here is, once you have grabed the object of user, you're still referring to req.user.email instead of user.email.
If that does not solve your problem, try to console.log the user returned from after User.findById
Update:
You can see here that isSelected returns boolean. So you're essentialy getting true for having password field in user. Also instead of req.user.email use user.email

Validation with MongooseJS causing NodeJS app to crash

I'm trying to find a better way of performing this validation. I have the user schmea setup and I'm trying to get the age validation working properly as to not cause the app to crash. You'll have to forgive me as I'm still relatively new to the language, so I may not be explaining it 100%. However, here is the User schema I created.
const mongoose = require('mongoose')
const validator = require('validator')
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
age: {
type: Number,
default: 0,
validate(value) {
if(value < 13){
throw new Error('You must be over the age of 13 to register for this site!')
}
}
},
email: {
type: String,
unique: true,
required: true,
trim: true,
lowercase: true,
validate(value){
if (!validator.isEmail(value)) {
throw new Error('Email is invalid')
}
}
},
password: {
type: String,
required: true,
trim: true,
minlength: 7,
validate(value){
if (value.toLowerCase().includes('password')) {
throw new Error('Password cannot contain "password"')
}
}
},
tokens: [{
token: {
type: String,
required: true
}
}]
})
userSchema.virtual('tasks', {
ref: 'Task',
localField: '_id',
foreignField: 'owner'
})
userSchema.methods.generateAuthToken = async function () {
const user = this
const token = jwt.sign({ _id: user._id.toString() }, 'thisismynewcourse')
user.tokens = user.tokens.concat({ token })
await user.save()
return token
}
userSchema.statics.findByCredentials = async (email, password) => {
const user = await User.findOne({ email })
if (!user) {
throw new Error('Unable to login')
}
const isMatch = await bcrypt.compare(password, user.password)
if (!isMatch) {
throw new Error('Unable to login')
}
return user
}
//Hash the plain text password before saving
userSchema.pre('save', async function(next) {
const user = this
if (user.isModified('password')) {
user.password = await bcrypt.hash(user.password, 8)
}
next()
})
userSchema.methods.toJSON = function () {
const user = this
const userObject = user.toObject()
delete userObject.password
delete userObject.tokens
return userObject
}
const User = mongoose.model('User', userSchema)
module.exports = User
The exact area that I'm trying to hone in on is in the age section, I'm trying to validate ages 13 or older, and when I run a test user creation through post man it performs the validation correctly, but it stops the application with the following:
UnhandledPromiseRejectionWarning: ValidationError: User validation failed: age: You must be over the age of 13 to register
Is there a way that I can prevent the application from crashing or should I perform the validation else where? Thanks in advance.
Normally the validation is performed in another file. This can be considered to be a service. But it should pass through a controller first if you want to do it properly. Here is an example of a simple blog post schema I made. You can see the function at the bottom runs every time before I send it to the database.
This is how it looks like in my schema file looks like which is located in folder called models.
// Requiring modules
const mongoose = require('mongoose');
// Initializing Schema
var Schema = mongoose.Schema;
// Creating a data model
const schema = new Schema({
shopname : {type: String, required:true},
address : {type: String, required:true},
review : {type: String, required:false},
image : {type: String, required:false},
originalname: {type: String, required:false},
filename: {type: String, required:false},
mimetype: {type: String, required:false},
size : {type: String, required:false},
updatedAt: {type: Date, required:false},
createdAt: {type: Date, required:false}
})
// Settings up the process before the data is sent to mongoDB.
// This process is call everytime 'save' is called.
// it sets the data for createdAt and updatedAt.
schema.pre('save', function(next){
if (!this.createdAt){
this.createdAt = new Date();
}else{
this.updatedAt = new Date();
}
next();
})
// Exports module
module.exports = mongoose.model("Blog", schema);

User.findByID is not a function

I am having an issue with node + express routing. I have a routing schema by default provided in the IDE webstorms. I am not sure if I configured everything well, because I am having this error.
I can do a GET /users and POST /users properly with correct results on postman.
routes/users.js
const express = require('express');
const router = express.Router();
const _ = require('lodash');
const {ObjectID} = require('mongodb');
const {mongoose} = require('../db/mongoose')
const {User} = require('../db/models/users')
const {Project} = require('../db/models/projects')
const {Dialog} = require('../db/models/dialogs')
(...)
router.get('/users/:userid', (req, res) => {
var id = req.params.userid.toString();
if (!ObjectID.isValid(id)) {
return res.status(404).send();
}
User.findByID(id).then((user) => {
if (!user) {
return res.status(404).send();
}
res.send({user});
}).catch(() => {
res.status(404).send();
});
});
models/users.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema
// todo refactor userschema with proper validators (view udemy course)
const UserSchema = new Schema({
email: {type: String, required: true},
password: {type: String, required: true},
name: {type: String},
company: {type: String},
phone: {type: String},
projects: [{type: Schema.Types.ObjectId, ref: 'Project'}]
});
const User = mongoose.model('User', UserSchema);
module.exports = {User}
Mikey is right. Mongoose model function is findById() not findByID() - http://mongoosejs.com/docs/api.html#model_Model.findById

Resources