Need help saving a game to a user's favorited games - reactjs

I'm receiving an error when trying to associate a saved game to the user that saves it. The error says "cannot read property push of undefined"
The user, and game can be read in the console. I think it may have something to do with the user model during the initial creation of the user, however I can't be sure. I did notice if I try to console.log(user.favGames) it will be returned undefined.
I've tried everything I can think of, I've re-written the controller roughly 10 times, to no avail.
user model
const mongoose = require('mongoose')
const bcrypt = require('bcrypt')
const SALT_ROUNDS = 6
const Schema = mongoose.Schema
const userSchema = new Schema(
{
username: { type: String, unique: true },
email: { type: String, unique: true, unique: true },
password: { type: String, required: true },
avatar: { type: String },
favGames: { type: Schema.Types.ObjectId, ref: 'Game', default: null },
comments: { type: Schema.Types.ObjectId, ref: 'Comment', default: null }
},
{
timestamps: true
}
)
userSchema.set('toJSON', {
transform: function(doc, ret) {
delete ret.password
return ret
}
})
userSchema.pre('save', function(next) {
const user = this
if (!user.isModified('password')) return next()
bcrypt.hash(user.password, SALT_ROUNDS, function(err, hash) {
if (err) return next()
user.password = hash
next()
})
})
userSchema.methods.comparePassword = function(tryPassword, cb) {
bcrypt.compare(tryPassword, this.password, cb)
}
module.exports = mongoose.model('User', userSchema)
game model
const mongoose = require('mongoose')
const Schema = mongoose.Schema
let gameSchema = new Schema({
name: { type: String, required: true },
boxArtUrl: { type: String, required: true },
twitchID: { type: String, required: true },
comments: { type: Schema.Types.ObjectId, ref: "Comment"}
})
module.exports = mongoose.model('Game', gameSchema)
game router
const express = require('express')
const router = express.Router()
const gamesCtrl = require('../../controllers/gameCtrl')
function isAuthed(req, res, next) {
if (req.user) return next()
return res.status(401).json({ msg: 'Unauthorized ' })
}
router.get('/')
router.post('/', isAuthed, gamesCtrl.addGame)
module.exports = router
game controller
const User = require('../models/user')
const Game = require('../models/Game')
function addGame(req, res) {
Game.create({
name: req.body.name,
twitchID: req.body.id,
boxArtUrl: req.body.box_art_url
})
.then(game => {
User.findById(req.user._id)
.then(user => {
console.log(game)
console.log(user.favGames)
// user.favGames.push(game)
// user.save()
})
.catch(err =>
console.log('error when updating user with new game', err)
)
})
.catch(err => console.log('error saving game', err))
}
module.exports = {
addGame
}
the error is flagged in my controller at user.favGames.push(game). Note that when a user creates a profile there are no games associated with their profile. I'm pretty sure I'm calling on the actual data instance of the model, not the model itself. Thanks in advance for your assistance.

Your favGames (and also comments) must be defined as array in user model like this.
const userSchema = new Schema(
{
username: { type: String, unique: true },
email: { type: String, unique: true, unique: true },
password: { type: String, required: true },
avatar: { type: String },
favGames: [{ type: Schema.Types.ObjectId, ref: 'Game', default: null }],
comments: [{ type: Schema.Types.ObjectId, ref: 'Comment', default: null }]
},
{
timestamps: true
}
)
Also user.save() returns a promise, so you need use then block, or await.
So the addGame function must be like this (I converted the code to async/await)
async function addGame(req, res) {
try {
let game = await Game.create({
name: req.body.name,
twitchID: req.body.id,
boxArtUrl: req.body.box_art_url
});
let user = await User.findById(req.user._id);
if (user) {
user.favGames.push(game);
await user.save();
res.status(200).send("game and user saved");
} else {
console.log("user not found");
res.status(404).send("user not found");
}
} catch (err) {
console.log("Err: ", err);
res.status(500).send("Something went wrong");
}
}

Looks like it's a matter of checking to see if it exists:
User.findById(req.user._id)
.then(user => {
if (!Array.isArray(user.favGames)) {
user.favGames = [];
}
user.favGames.push(game);
user.save();
})

Related

Mongoose deleting subdocuments from arrays using $pull

Hi I have a comment and post model that has a one-to-many relationship. It successfully creates a comments array and pushes the object id correctly, but when I try to delete a comment using $pull It doesn't delete or show errors just returns the object back unchanged and also I'm not sure how to update subdocuments either , it doesn't work
In comment.js
const mongoose = require("mongoose");
const AutoIncrement = require('mongoose-sequence')(mongoose);
const CommentSchema = new mongoose.Schema({
username: {
type: String,
required: true,
},
content:{
type: String,
required: true
},
createdAt:{
type: Date,
default: new Date()
},
})
CommentSchema.plugin(AutoIncrement, {inc_field: 'commentID'});
module.exports = mongoose.model("Comment", CommentSchema);
In post.js
const mongoose = require("mongoose");
const AutoIncrement = require('mongoose-sequence')(mongoose);
const PostsSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
content: {
type: String,
required: true
},
createdAt:{
type: Date,
default: new Date()
},
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Comment"
}
]
});
PostsSchema.plugin(AutoIncrement, {inc_field: 'postID'});
module.exports = mongoose.model("Post", PostsSchema);
For the routes
router.post("/:id/comments", CommentController.createComment)
router.get("/:id/comments", CommentController.viewComments)
router.delete("/:id/comments/:commentID", CommentController.deleteComment)
router.patch("/:id/comments/:commentID", CommentController.updateComment)
And the controllers
const commentsModel = require("../models/comments")
const PostsModel = require("../models/posts");
const mongoose = require("mongoose")
const objID = mongoose.Types.ObjectId;
async function createComment(req, res){
try{
const comment = await commentsModel.create(req.body)
const data = await PostsModel.findOneAndUpdate(
{postID: req.params.id},
{$push: {comments: objID(comment._id)}},
{ new: true, useFindAndModify: false }
)
res.status(200).json({data})
}catch (e) {
res.status(404).json(e.message)
}
}
async function viewComments(req, res){
try{
const data = await PostsModel.findOne({postID: req.params.id}).populate("comments");
if(!data){
return res.status(404).json({msg: "No data found"});
}
res.status(200).json({comments: data.comments});
}catch (e) {
res.status(500).json(e.message);
}
}
async function deleteComment(req, res){
try{
const post = await PostsModel.update({ postID: req.params.id },
{
'$pull': {
comments: {
_id: objID(req.params.commentID)
}
}
}, {new: true});
if (!post){
return res.status(404).json({msg: "No post found"});
}
res.status(200).json({post});
}catch (e) {
res.status(500).json(e.message);
}
}
async function updateComment(req, res){
try{
const post = await PostsModel.findOneAndUpdate(
{"postID": req.params.id, "comments._id": objID(req.params.commentID)},
{
"$set":{
"comments.$.username": req.body.username,
"comments.$.content": req.body.content
}
}
)
if (!post){
return res.status(404).json({msg: "No post found"});
}
res.status(200).json({post});
}catch (e) {
res.status(500).json(e.message);
}
}

Mern stack bcrypt password works many times, and suddenly doesn't match

i have a strange problem and don't find the same so i search some help :) :
I make a mern stack social media, and first all is okay with register new user and logged in.
I'm on development of the app, so i login and logout a lot. And after a few times, i don't count, maybe 20 or 30, the password is not recognized, and i can't login with the account.
So i make a new user to continue my work, and the same thing happen after a few logins.
Here is my userSchema
const mongoose = require("mongoose");
const {isEmail} = require("validator");
const bcrypt = require("bcrypt");
function validEmail(email) {
if(!isEmail(email)) {
return false;
}
else {
return true
}
}
const userSchema = new mongoose.Schema(
{
pseudo: {
type: String,
required: true,
minLength: 3,
maxLength: 20,
unique: true,
trim: true
},
email: {
type: String,
required: true,
lowercase: true,
trim: true,
unique: true,
validate: validEmail
},
password: {
type: String,
required: true,
maxLength: 1024,
minLength: 6
},
dimension: {
type: String,
},
galaxy: {
type: String,
},
picture: {
type: String,
default: "./uploads/profil/random-user.png"
},
bio: {
type: String,
maxLength: 1024
},
followers: {
type: [String],
},
following: {
type: [String]
},
likes: {
type: [String]
}
},
{
timestamps: true,
}
)
// Play function before save into display: block
userSchema.pre("save", async function(next) {
const salt = await bcrypt.genSalt();
this.password = await bcrypt.hash(this.password, salt);
next();
})
userSchema.statics.login = async function(email, password) {
const user = await this.findOne({email});
console.log(user)
if(user) {
const auth = await bcrypt.compare(password, user.password);
if(auth) {
return user;
} else {
throw Error("Incorrect password");
}
}
throw Error("Incorrect email");
}
const UserModel = mongoose.model("user", userSchema);
module.exports = UserModel;
And here the login function in my auth.controller.js:
module.exports.signIn = async (req, res) => {
const { email, password } = req.body;
try {
const user = await UserModel.login(email, password);
const token = createToken(user._id);
res.cookie("jwt", token, {
maxAge: maxAge, sameSite: "none",
secure: true,
})
res.status(200).json({ user: user._id })
}
catch (err) {
const errors = signInErrors(err);
res.status(200).json({ errors });
// console.log(err)
}
}
It's my first project with back-end side, and i follow a tutorial from 2020 to make it, so maybe there is new best practices to do it now. But it seems very strange that it works fine with new user, and not after some connections. I hope someone have an idea to try to help me :)
I find this and try, seems to work for me after 2 days, with adding a condition on the pre hook like this:
userSchema.pre("save", async function(next) {
if(!this.isModified("password")) {
return next()
}
const salt = await bcrypt.genSalt();
this.password = await bcrypt.hashSync(this.password, salt);
next();
})
Hope it help someone

How to make the mongoose update array work properly?

this is my schema.it has a user field and an array of object which contains a text message and direction will hold a either sent or received value.
const MessageSchema = new mongoose.Schema({
user:{type: String,required: true},
textArray:[
{
text:{type: String,required: true},
direction:{ type: String,required: true }
}]
});
This is the router code.btw should there be double quotes on the keys in var textmessage?
router.post('/',async(req,res)=>{
const {user,text,direction}=req.body;
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
var textmessage={
"text":text,
"direction":direction
}
const doc = await Message.updateOne(
{ user: user },
{ $set: { user:user,$push:{textArray:textmessage}} },
{ upsert: true },
function(err,result){
if (err){
console.log(err);
}
else{
console.log(result)
res.send(result)
}// Make this update into an upsert
});
})
this is how it showsup in mongodb.the user is working ok but when i click theres is nothing under textArray.What am I doing wrong exactly?
you are using await with callback function , which is wrong :
const filter = { user: user };
const update = { $push:{textArray:textmessage}};
let doc = await Message.findOneAndUpdate(filter, update, {
new: true,
upsert: true
});
console.log('doc=',doc);

Find all users that have the same reference objects -- mongoose

I have a users with an array of references to another model (sport). Now I want to create a GET route to find users who have common references with one specific user.
I just cant figure out, how to do it...... would be great if someone can help :)
I tried something like...
router.get("/matches", (req, res) => {
const { _id } = req.payload;
User.findById(_id)
.then((currentUser) => {
currentUser.sports.map((sportDetails) => {
sportDetails.map((sportId) => {
const {_id} = sportId
})
return User.find({ sports: { sportId } });
});
})
.then((matches) => {
res.json(matches)
})
.catch((err) => {
console.log("no matches...", err)
})
});
user.model
const userSchema = new Schema(
{
username: String,
sports: [{ type: Schema.Types.ObjectId, ref: "Sport" }]
}
);
sport.model
const sportSchema = new Schema(
{
title: String,
level: String
}
);

Find subdocument object in another db

I'm trying to check each email of attendees and see if they are a registered user. If not, I will send them an email (not yet coded, will do later).
Here's the event and user schema:
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
}
});
const Event = new Schema({
title: {
type: String,
required: true
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'users'
},
attendees:[
{email: {
type: String,
required: true
},
name: {
type: String,
required: true
},
status: {
type: String
}}
]
});
router.post('/', auth, async (req, res) => {
const {title,
attendees
} = req.body
if (!title) {
return res.status(400).json({ msg: 'Please enter a title' });
}
try{
const newEvent = new Event({
title,
user: req.user.id,
attendees: attendees.map(x => ({
email: x.email,
name: x.name,
status: x.status,
})),
});
const attendeeExists = await User.findOne({"attendees.email":email});
if (!attendeeExists) throw Error("User doesn't exist. Send email");
The last two lines are giving me an error: email is not defined.
Not sure what I'm missing.
This works in the user routes:
const user = await User.findOne({ email });
Thanks #ambianBeing, your solution helped me get a working model.
const email = attendees.map((a) => a.email);
const attendeesFound = await User.find({email});
For checking any of the attendee's email found, .find() with $in can be used which'll return the users found with any of the email ids.
/*collect all emails to test*/
const emails = attendees.map((a) => a.email);
const attendeesFound = await User.find({ "email": { $in: emails } });
Another Mongoose syntax wihich does the same thing as above:
/*collect all emails to test*/
const emails = attendees.map((a) => a.email);
const attendeesFound = await User.find({}).where("email").in(emails);

Resources