Pull element from array in mongodb - javascript

I'm using this schema for the USERS Collection
const usersSchema = new Schema({
username: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
select: false,
},
phone: {
type: Number,
required: true,
},
books: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "books",
},
],
});
and when the user want to delete a book in books collection, it should be delete in the user's books document (The above array) as well.
and I use this query but i get an error
const deleteBook = async (req, res, next) => {
try {
const { id } = req.query;
const deletedProduct = await books.findByIdAndDelete(id).populate(
"author"
);
// IT WORKS AND DELETE THE BOOK IN BOOKS COLLECTION
if (!deletedProduct) {
return next(new ErrorHandler("Product Was Not Found", 404));
}
await Users.findByIdAndUpdate(
deletedProduct.author._id,
{
$pull: { books: deletedProduct._id },
},
(err, docs) => {
// ERROR IS NULL IN HERE
console.log(err);
console.log(docs);
}
);
res.status(200).json("Success");
} catch (err) {
next(new ErrorHandler(err.message, 500));
}
};
Its My query but i get this error.
"Query was already executed: users.findOneAndUpdate({ _id: new ObjectId("THE USER'S ID"
i want to say. The book will be deleted from the books collection, but stay as same in the User's books array.

Related

Cannot read properties of null (reading 'experience'). after all this i should get profile with all experience .but all time i end up with server err

i was following brad traversy's one of his udemy course. after working on adding experience in profile in profile routes. i all time get server error. but it should end up with full profile details like in brad course. this is my github link for that project
https://github.com/arshad007hossain/findDevs
profile.js
const express = require("express");
const router = express.Router();
const auth = require("../../middleware/authmiddleware");
const { check, validationResult } = require("express-validator");
const Profile = require("../../models/Profile");
const User = require("../../models/User");
// #route GET api/profile/me
// #desc Get current users profile
// #access Private
router.get("/me", auth, async (req, res) => {
try {
const profile = await Profile.findOne({
user: req.user.id,
}).populate("user", ["name", "avatar"]);
if (!profile) {
return res.status(400).json({ msg: "There is no profile for this user" });
}
res.json(profile);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});
// #route POST api/profile/me
// #desc create or update users profile
// #access Private
router.post(
"/",
[
auth,
[
check("status", "status is required").not().isEmpty(),
check("skills", "skills is required").not().isEmpty(),
],
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const {
company,
website,
location,
bio,
status,
githubusername,
facebook,
linkedin,
twitter,
instagram,
youtube,
skills,
} = req.body;
//build user profile
const profileFields = {};
profileFields.user = req.user.id;
if (company) profileFields.company = company;
if (website) profileFields.website = website;
if (location) profileFields.location = location;
if (bio) profileFields.bio = bio;
if (status) profileFields.status = company;
if (githubusername) profileFields.githubusername = githubusername;
if (skills) {
profileFields.skills = skills.split(",").map((skill) => skill.trim());
}
//build social objects
profileFields.social = {};
if (youtube) profileFields.social.youtube = youtube;
if (twitter) profileFields.social.twitter = twitter;
if (linkedin) profileFields.social.linkedin = linkedin;
if (instagram) profileFields.social.instagram = instagram;
if (facebook) profileFields.social.facebook = facebook;
//console.log(profileFields.skills);
try {
let profile = await Profile.findOne({ user: req.user.id });
if (profile) {
//Update
profile = await Profile.findOneAndUpdate(
{ user: req.user.id },
{ $set: profileFields },
{ new: true }
);
return res.json(profile);
}
//create
profile = new Profile(profileFields);
await profile.save();
res.json(profile);
} catch (err) {
console.errora(err.message);
res.status(500).json("server error");
}
}
);
// #route GET api/profile
// #desc Get all profile
// #access Public
router.get("/", async (req, res) => {
try {
let profiles = await Profile.find().populate("user", ["name", "avatar"]);
res.json(profiles);
} catch (err) {
console.error(err.message);
res.status(500).json("server error");
}
});
// #route GET api/profile/user/user_id
// #desc Get single profile
// #access Public
router.get("/user/:user_id", async (req, res) => {
try {
const profile = await Profile.findOne({
user: req.params.user_id,
}).populate("user", ["name", "avatar"]);
if (!profile) return res.status(400).json({ msg: "Profile not found" });
res.json(profile);
} catch (err) {
if (err.kind == "ObjectId") {
return res.status(400).json({ msg: "Profile not found" });
}
console.error(err.message);
res.status(500).json("server error");
}
});
// #route DELETE api/profile
// #desc Delete profile, user
// #access Private
router.delete("/", auth, async (req, res) => {
try {
// Remove profile
await Profile.findOneAndRemove({ user: req.user.id });
// Remove user
await User.findOneAndRemove({ _id: req.user.id });
res.json({ msg: "User deleted" });
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});
// #route PUT api/profile/experience
// #desc Add profile experience
// #access Private
router.put(
'/experience',
[
auth,
[
check('title', 'Title is required field').not().isEmpty(),
check('company', 'Company is required field').not().isEmpty(),
check('from', 'From date is required field').not().isEmpty(),
],
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const {
title,
company,
location,
from,
to,
current,
description,
} = req.body;
const newExp = {
title,
company,
location,
from,
to,
current,
description,
};
try {
const profile = await Profile.findOne({ user: req.user.id });
profile.experience.unshift(newExp);
await profile.save();
res.json(profile);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
module.exports = router;
User.js
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,
},
avatar: {
type: String,
},
date: {
type: Date,
default: Date.now,
},
});
module.exports = User = mongoose.model("user", UserSchema);
Profile.js Model
const mongoose = require('mongoose');
const ProfileSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
},
company: {
type: String
},
website: {
type: String
},
location: {
type: String
},
status: {
type: String,
required: true
},
skills: {
type: [String],
required: true
},
bio: {
type: String
},
githubusername: {
type: String
},
experience: [
{
title: {
type: String,
required: true
},
company: {
type: String,
required: true
},
location: {
type: String
},
from: {
type: Date,
required: true
},
to: {
type: Date
},
current: {
type: Boolean,
default: false
},
description: {
type: String
}
}
],
education: [
{
school: {
type: String,
required: true
},
degree: {
type: String,
required: true
},
fieldofstudy: {
type: String,
required: true
},
from: {
type: Date,
required: true
},
to: {
type: Date
},
current: {
type: Boolean,
default: false
},
description: {
type: String
}
}
],
social: {
youtube: {
type: String
},
twitter: {
type: String
},
facebook: {
type: String
},
linkedin: {
type: String
},
instagram: {
type: String
}
},
date: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('profile', ProfileSchema);

Can't push items into mongodb arrays

I'm trying to make a simple social media app using react, express and mongodb.
This is the user model:
const UserSchema = new mongoose.Schema(
{
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
email: { type: String, required: true, unique: true},
followers: { type: Array, required: false },
following: { type: Array, required: false },
likes: { type: Array, required: false},
},
{ collection: 'users' }
)
This is the express server:
app.post('/api/follow', async (req, res) => {
const {token, username} = req.body
if (token === null)
{
return res.json({status: 'error'})
}
const user = await User.findOne({username}).lean()
const _visitor = jwt.verify(token, JWT_SECRET)
const visitor = await User.findOne({username: _visitor.username})
if (!user)
{
return res.json({status: 'error', error: 'User not found.'})
}
if (!visitor)
{
return res.json({status: 'error', error: 'User not found.'})
}
visitor.following.push(user._id)
user.followers.push(me._id)
return res.json({status: 'ok'})
})
But when I check the mongodb compass the following and followers arrays are empty.
The best way is to use findOneAndUpdate() method to update a value.
Also, if you are updating from two different collections you can use transactions. This is optional but can be useful to avoid inconsitences in your DB.
So your code can be something similar to this:
const updateVisitor = await User.findOneAndUpdate(
{
username: _visitor.username
},
{
$push:{
following: user._id
}
})
Example here
An the same code for user:
const updateUser = await User.findOneAndUpdate(
{
username: username
},
{
$push:{
followers: me._id
}
})

MongoDB only updates partially

My model has "id", "liked", "likedBy" and "matched" fields.
I can update my database and add id according to my hypotethical likes; it stores target's id to my current user's liked field, current user's id to target's likedBy field.
I'm trying to achieve, if a user has both liked and likedBy id matching then put liked id to my matched field on both users, but I can't for some reason. It just doesn't care if statement there.
Any ideas why?
//like user by using it's id. update it to liked
app.put("/like/:id", auth, async (req, res) => {
try {
const user = await User.findById(req.params.id);
const loggedUser = await User.findById(req.user.id).select("-password");
//check if it is already liked
if (
user.likedBy.filter((like) => like.user.toString() === req.user.id)
.length > 0
) {
return res.status(400).json({ msg: "Already Liked" });
}
user.likedBy.unshift({ user: req.user.id });
loggedUser.liked.unshift({ user: req.params.id });
await user.save();
await loggedUser.save();
//check matching
if (user.likedBy === user.liked) {
user.matched.unshift({ user: req.user.id });
}
await user.save();
await loggedUser.save();
res.status(200).send("Liked!");
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});
My Schema:
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
firstname: {
type: String,
required: true,
},
lastname: {
type: String,
required: true,
},
picture: {
data: Buffer,
contentType: String,
},
age: {
type: Number,
required: true,
},
gender: {
type: String,
required: true,
},
job: {
type: String,
required: true,
},
desc: {
type: String,
default: "Hasn't written anything yet.",
},
liked: [{}],
likedBy: [{}],
matched: [{}],
});
module.exports = User = mongoose.model("user", UserSchema);
I found the mistake I made.
I'm trying to compare objects, which isn't possible really. I got index of my array then extracted the data I need and stored it into value1 & value2.
I found my mistake the moment I console.log'ed my conditions as below:
if(console.log(user.liked) === console.log(user.likedBy)){
...}
Working version:
//like user by using it's id. update it to liked
app.put("/like/:id", auth, async (req, res) => {
try {
const user = await User.findById(req.params.id);
const loggedUser = await User.findById(req.user.id).select("-password");
//check if it is already liked
if (
user.likedBy.filter((like) => like.user.toString() === req.user.id)
.length > 0
) {
return res.status(400).json({ msg: "Already Liked" });
} else {
user.likedBy.unshift({ user: req.user.id });
loggedUser.liked.unshift({ user: req.params.id });
await user.save();
await loggedUser.save();
const value1 = user.likedBy[0].user;
const value2 = user.liked[0].user;
if (value1 === value2) {
user.matched.unshift({ user: req.user.id });
loggedUser.matched.unshift({ user: req.params.id });
await user.save();
await loggedUser.save();
res.status(200).send("Liked & Matched!");
} else {
res.status(200).send("Liked!");
}
}
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});

Mongoose returning empty array

I have a db in Mongo with 2 collections, users and campaigns. For the former, all of my requests (get,post, patch, etc...) work correctly. However, I am having an issue with campaigns.
I can create a new campaign in postman but not 'get' the campaigns. THe request appears successful but returns an empty array.
I have the campaigns split into:
campaignController,
***Model,
***Routes,
and a handlerFactory to cover users and campaigns.
handlerFactory:
exports.getAll = Model =>
catchAsync(async (req, res, next) => {
// To allow for nested GET reviews on tour (hack)
let filter = {};
if (req.params.campaignId) filter = { campaign: req.params.campaignId };
const features = new APIFeatures(Model.find(filter), req.query)
.filter()
.sort()
.limitFields()
.paginate();
// const doc = await features.query.explain();
const doc = await features.query;
// SEND RESPONSE
console.log('-------', doc);
res.status(200).json({
status: 'success',
results: doc.length,
data: {
data: doc
}
});
});
Campaign Model:
const campaignSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Campaign name can not be empty!']
},
clientID: {
type: String,
},
creator_id: {
type: String,
},
budget: {
type: Number,
min: 100,
required: [true, 'Campaign name can not be empty!']
},
startStatus: {
type: String,
enum: ['preStart', 'isStarted', 'preEnd'],
default: 'preStart'
},
startDate: {
type: Date,
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {type: Date,
default: Date.now
},
isDeleted: {
type: Boolean,
// required: [true, 'Must be true or false!']
default: false
},
Priority: {
type: Boolean,
default: false,
},
location: {
type: String,
enum: ['Helsinki', 'Tallinn'],
default: 'Helsinki'
}
});
campaignSchema.pre('save', function(next) {
if (!this.isModified('createdAt') || this.isNew) return next();
this.updatedAt = Date.now() - 1000;
next();
});
campaignSchema.pre(/^find/, function(next) {
// this points to the current query
this.find({ isDeleted: { $ne: false } });
next();
});
const Campaign = mongoose.model('Campaign', campaignSchema);
module.exports = Campaign;
campaignController:
exports.getAllCampaigns = factory.getAll(Campaign);
exports.getCampaign = factory.getOne(Campaign);
exports.createCampaign = factory.createOne(Campaign);
exports.updateCampaign = factory.updateOne(Campaign);
exports.deleteCampaign = factory.deleteOne(Campaign);
exports.getMe = (req, res, next) => {
req.params.id = req.campaign.id;
next();
};
exports.deleteCurrentCampaign = catchAsync(async (req, res, next) => {
await User.findByIdAndUpdate(req.campaign.id, { active: false });
res.status(204).json({
status: 'success',
data: null
});
});
campaignRoutes:
const router = express.Router();
router
.route('/')
.get(campaignController.getAllCampaigns)
.post(
authController.protect,
authController.restrictTo('admin', 'super-admin'),
campaignController.createCampaign
);
router
.route('/:id')
.get(campaignController.getCampaign)
.patch(
authController.protect,
authController.restrictTo('admin', 'super-admin'),
campaignController.updateCampaign
)
.delete(
authController.protect,
authController.restrictTo('admin', 'super-admin'),
campaignController.deleteCampaign
);
module.exports = router;
Any idea where I am going wrong?
All code looks good but may be problem is,your collection not contain any records whose isDeleted=true.
because "find query middleware" in campaignModel is called before any find* query and it find all document whose isDeleted != false.

Mongoose & Express: How to properly Remove, Create & Store data that are reference

The first problem I am having is that whenever I try to delete the Comment, I also try to find the index of that specific comment inside post.comments as well as inside user.comments, it consistently returns -1, the reason why I am trying to find it, is so that I can splice it from the comments array that user and post do have.
The second problem I am having is that whenever I create a comment, I try to store it in the comments array that user and post have, but it stores it only as a string, although I think it is supposed to be stored as an object right?, So I can access it later by populating?
I have been struggling for days now being very frustrated why it does not work. Please help me!
Below will be my two routes, for deleting and creating comments, and my Schemas, Thank You for all the help!
Creating Comments
// #route POST api/posts/comment/:id
// #desc Comment on a post
// #access Private
router.post(
'/comment/:id',
[
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 post = await Post.findById(req.params.id);
const user = await User.findById(req.user.id)
const newComment = {
text: req.body.text,
post: post._id,
user: req.user.id
};
const comment = new Comment(newComment);
post.comments.unshift(comment._id);
user.comments.unshift(comment._id)
console.log(user.comments);
console.log(post.comments);
console.log(comment)
await post.save();
await comment.save();
await user.save();
res.json(comment);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
}
);
Deleting comments
// #route DELETE api/posts/comment/:id/:comment_id
// #desc Delete comment
// #access Private
router.delete('/comment/:id/:comment_id', auth, async (req, res) => {
try {
const post = await Post.findById(req.params.id);
const user = await User.findById(req.user.id);
// Pull out comment by finding it through its id
const comment = await Comment.findById(req.params.comment_id);
// Make sure comment exists
if (!comment) {
return res.status(404).json({ msg: 'Post do not have this comment' });
}
// Check user
if (comment.user.toString() !== req.user.id) {
return res.status(401).json({ msg: 'User not authorized' });
}
// Get The value to be removed
const postCommentIndex = post.comments.findIndex(postComment => postComment === comment._id);
const userCommentIndex = user.comments.findIndex(userComment => userComment === comment._id);
console.log(`This is the post comment index ${postCommentIndex}`);
console.log(`This is the user comment index ${userCommentIndex}`);
post.comments.splice(postCommentIndex, 1);
user.comments.splice(userCommentIndex, 1);
// save user and post
await post.save();
await user.save();
await comment.remove();
// resend the comments that belongs to that post
res.json(post.comments);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
Schemas:
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
},
avatar: {
type: String
},
posts: [{type: mongoose.Schema.Types.ObjectId, ref: "Post"}],
comments: [{type: mongoose.Schema.Types.ObjectId, ref: "Comment"}],
date: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('User', UserSchema);
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const PostSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
text: {
type: String,
required: true
},
likes: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'User'
}
}
],
dislikes: [
{
user: {
type: Schema.Types.ObjectId,
ref: "User"
}
}
],
comments: [{type: Schema.Types.ObjectId, ref: "Comment"}],
date: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Post', PostSchema);
const mongoose = require("mongoose")
const Schema = mongoose.Schema;
const CommentSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
post: {
type: Schema.Types.ObjectId,
ref: "Post"
},
text: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
})
module.exports = mongoose.model("Comment", CommentSchema);
I think you need to redesign your schemas in a simpler way, there are too many references between the models, and this causes issues, for example you have 5 db access when you want to create a comment, and 6 db access when you want to delete a comment.
I would create the user schema like this removing the posts and comment references, but later when we want to access the posts from users, I set up virtual populate.
const UserSchema = new Schema(
{
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
avatar: {
type: String
},
date: {
type: Date,
default: Date.now
}
},
{
toJSON: { virtuals: true }
}
);
UserSchema.virtual("posts", {
ref: "Post",
localField: "_id",
foreignField: "user"
});
And in the posts schema, I removed the comments references.
(For simplicity I removed likes and dislikes fields.)
const PostSchema = new Schema(
{
user: {
type: Schema.Types.ObjectId,
ref: "User"
},
text: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
},
{
toJSON: { virtuals: true }
}
);
PostSchema.virtual("comments", {
ref: "Comment",
localField: "_id",
foreignField: "post"
});
Comment schema can stay as it is.
Now to add a comment to a post, we only need 2 db access, one for checking if post exists, and one for creating the post.
router.post(
"/comment/:id",
[
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 post = await Post.findById(req.params.id);
if (!post) {
return res.status(404).json({ msg: "Post not found" });
}
let comment = new Comment({
text: req.body.text,
post: req.params.id,
user: req.user.id
});
comment = await comment.save();
res.json(comment);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
}
);
Let's say we have these 2 users:
{
"_id" : ObjectId("5e216d74e7138b638cac040d"),
"name" : "user1"
}
{
"_id" : ObjectId("5e217192d204a26834d013e8"),
"name" : "user2"
}
User1 with _id:"5e216d74e7138b638cac040d" has this post.
{
"_id": "5e2170e7d204a26834d013e6",
"user": "5e216d74e7138b638cac040d",
"text": "Post 1",
"date": "2020-01-17T08:31:35.699Z",
"__v": 0,
"id": "5e2170e7d204a26834d013e6"
}
Let's say user2 with _id:"5e217192d204a26834d013e8" commented on this post two times like this:
{
"_id" : ObjectId("5e2172a4957c02689c9840d6"),
"text" : "User2 commented on user1 post1",
"post" : ObjectId("5e2170e7d204a26834d013e6"),
"user" : ObjectId("5e217192d204a26834d013e8"),
"date" : ISODate("2020-01-17T11:39:00.396+03:00"),
"__v" : 0
},
{
"_id": "5e21730d468bbb7ce8060ace",
"text": "User2 commented again on user1 post1",
"post": "5e2170e7d204a26834d013e6",
"user": "5e217192d204a26834d013e8",
"date": "2020-01-17T08:40:45.997Z",
"__v": 0
}
To remove a comment we can use the following route, as you see we decreased the db access from 6 to 3, and code is shorter and cleaner.
router.delete("/comment/:id/:comment_id", auth, async (req, res) => {
try {
const comment = await Comment.findById(req.params.comment_id);
if (!comment) {
return res.status(404).json({ msg: "Post do not have this comment" });
}
if (comment.user.toString() !== req.user.id) {
return res.status(401).json({ msg: "User not authorized" });
}
await comment.remove();
// resend the comments that belongs to that post
const postComments = await Comment.find({ post: req.params.id });
res.json(postComments);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});
Now you may ask, how will access the posts from an user? Since we setup virtual populate in our user schema, we can populate the posts like this:
router.get("/users/:id/posts", async (req, res) => {
const result = await User.findById(req.params.id).populate("posts");
res.send(result);
});
You can try this code snipet :
Comment.deleteOne({
_id: comment.id
}, function (err) {
if (err) {
console.log(err);
return res.send(err.message);
}
res.send('success');
});

Categories

Resources