My website has a 'follow' feature, where users can follow each other.
Ideally, a user1 would be able to follow a user2, and have their _id present inside user2.followers, aswell as user2 should be present in user1.following.
My first thought was to simply do something like
/*
UserSchema = new Schema({
username: String,
followers: [
{
type: Schema.Types.ObjectId,
ref: 'user'
}
],
following: [
{
type: Schema.Types.ObjectId,
ref: 'user'
}
]
})
*/
// POST /api/users/follow
let user = await User.findById(userId)
let follow = await User.findById(followId)
if (!user || !follow) {
return errorHandle()
}
user.following.push(follow._id);
follow.followers.push(user._id);
user.save();
follow.save();
/*
User.findById(userId)
.populate('following', 'username')
.then(user => console.log(user.following))
*/
But these would be difficult to scale and maintain (as well as other problems).
So I want to hear from you, the stack community, what a proper way to deal with this system be, as I am new to MongoDB and Databases as a whole.
Any help is appreciated
hey I was getting the same problem I figured it out how it can be done. I have two way of writing the code choose as per your choice.
UserSchema
var UserSchema = new Schema({
name:{
type:String,
required:true
},
email:{
type:String,
required:true
},
password:{
type:String,
required:true
},
following: [
{
user:{
type: Schema.ObjectId,
ref: 'User'
},
}
],
followers: [
{
user:{
type: Schema.ObjectId,
ref: 'User'
},
}
],
date:{
type:Date,
default: Date.now
},
});
First Method
User.js
You can use .then and .catch method
router.post("/user/:user_id/follow-user", (req,res) => {
// finding user by id (to whom user is going to follow)
User.findById(req.params.user_id)
.then(user => {
//check if follow reqest by user2 is already exist in user1 followers
if(user.followers.filter(follower => follower.user.toString()=== req.user._id).length > 0 ){
return res.status(400).json({ alreadyLike : "User already like the post"})
}
// the requested user will push and save to followers of other user to whom request has made
user.followers.push(req.user._id);
var followedUser = user._id;
user.save()
// we will find current user by email you can find it by _id also
User.findOne({ email: req.user.email })
.then(user => {
// now push the user to following of its own and save the user
user.following.push(followedUser);
user.save().then(user => res.json(user))
})
.catch(err => console.log("error cant follow again you jave already followed the user"))
})
})
second method
Normal method by call backs
router.post("/user/:user_id/follow-user", (req,res) => {
// finding user by id (to whom user is going to follow)
User.findById(req.params.user_id, function(err, user) {
// the requested user will push and save to followers of other user to whom request has made
user.followers.push(req.user._id);
var followedUser = user._id;
user.save(function(err){
if(err){
console.log(err)
}
else
{
// Secondly, find the user account for the logged in user
User.findOne({ email: req.user.email }, function(err, user) {
user.following.push(followedUser);
user.save(function(err){
if(err){
console.log(err)
}
else{
//send success response
res.json(user)
}
});
});
}
});
});
});
Related
I'm working on a website with a catalog of shops where users can leave comments and rate these shops.
My code so far:
Schemas:
const journalSchema = new mongoose.Schema({
title: String,
category: String,
subcategory: String,
review: [{type: mongoose.Schema.Types.ObjectId, ref: 'Review'}],
link: String,
description: String,
});
const userSchema = new mongoose.Schema ({
username: String,
nickname: String,
password: String,
journal: [{type: mongoose.Schema.Types.ObjectId, ref: 'Journal'}]
});
const reviewSchema = new mongoose.Schema({
author: {type: mongoose.Schema.Types.String, ref: 'User'},
content: String,
date: Date,
rating: Number
});
const Journal = mongoose.model("Journal", journalSchema);
const User = mongoose.model("User", userSchema);
const Review = mongoose.model("Review", reviewSchema);
Get route for individual shop page:
app.get("/journals/:journalId", function(req, res){
const requestedJournalId = req.params.journalId;
Journal.findOne({_id: requestedJournalId}, function(err, foundJournal){
Review.aggregate([
{$match: {_id: {$in: foundJournal.review}}},
{$group: {_id: foundJournal.review, average: {$avg: "$rating"}}}
], function(err, result){
if(err){
console.log(err);
}
else{
result.forEach(function(review){
Review.find({_id: review._id}, function(err, reviews){
res.render("stats", {
_id: foundJournal._id,
title: foundJournal.title,
subcategory: foundJournal.subcategory,
link: foundJournal.link,
description: foundJournal.description,
reviews: reviews,
avg: review.average
});
})
})
}
});
})
});
Post route:
app.post("/stats/review", function(req, res){
if(req.isAuthenticated()){
const userId = req.user.id;
const userReview = req.body.journalReview;
const userRating = req.body.reviewRating;
const journalId = req.body.journalId;
User.findById(userId, function(err, foundUser){
if(err){
console.log(err);
}
else{
const review = new Review();
review.author = foundUser.nickname;
review.content = userReview;
review.rating = userRating;
review.save()
.then((result) =>{
Journal.findOneAndUpdate(
{_id: journalId},
{$push: {
review: review
}},
{useFindAndModify: false},
function(err, success){
if(err){
console.log(err);
}
else{
res.redirect("back");
}
}
);
})
.catch((error) =>{
console.log(error);
})
}
});
}
else{
res.redirect("/login");
}
});
Currently, the code is working and doing what I want it to do. Any logged in user can leave a comment and rating for any shop. But my concern is how to improve the existing code, I'm sure there are better ways to achieve the same result with a more clean and efficient code. I'm new to mongoose and learned about the aggregate method just recently. Thanks in advance
You can use ES6 for better formatting your code. here is the link you can follow:https://www.w3schools.com/js/js_es6.asp
I formatted get route for your understanding:
app.get("/journals/:id", async (req, res) => {
const journal = await Journal.findOne(req.params.id);
if (!journal) {
res.status(404).sent({ error: "journal not found" });
}
try {
const reviews = await Review.aggregate([
{ $match: { _id: { $in: journal.review } } },
{
$group: {
_id: journal.review,
average: { $avg: "$rating" },
},
},
]);
reviews.forEach((review) => {
const reviews = await Review.find({ _id: review._id });
res.render("stats", {
...journal,
reviews,
avg: review.average,
});
});
} catch (error) {
console.log(error);
res.status(400).send(error);
}
});
you can use mongoose-autopopulate package for auto-populating your document. https://www.npmjs.com/package/mongoose-autopopulate
avoid using a nested Database queries.
try to implement async await for better readability.
Working on a personal project, one of the functions of the project is to update the user status on what event they are participating.
i wanted to submit a value using a button
<form action="/users/fooddrivebanner" method="POST"><button name="fooddrive" type="submit" value="fooddrive" id="fooddrive">Participate</button></form>
then pass the value to my route and save it inside my database
router.post('/fooddrivebanner', (req,res)=>{
const { fooddrive } = req.body;
const _id = ObjectId(req.session.passport.user._id);
User.findOne({ _id: _id }).then((user)=>{
if (!user) {
req.flash("error_msg", "user not found");
res.redirect("/fooddrivebanner");
}
if (typeof eventparticpating !== "undefined") {
user.eventparticpating = 'fooddrive';
}
user.save(function (err, resolve) {
if(err)
console.log('db error', err)
// saved!
});
})
.catch((err) => console.log(err));
Here is the User model
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
},
eventparticpating: {
type: String,
default: 'None At The Moment'
}
});
const User = mongoose.model('User', UserSchema);
module.exports = User;
It showed a console error
TypeError: Cannot set property 'eventparticpating' of null
UPDATE
Edit 1:
I followed Mr Gambino instructions, error Gone yet cannot update the database, how would i be able to adjust and find my user?
Instead of saving within the findOne function,you can do this:
router.post('/fooddrivebanner', async (req,res) => {
const { fooddrive } = req.body;
const _id = ObjectId(req.session.passport.user._id);
await User.findOne({ _id: _id }, (error, user) => {
if (error) {
req.flash("error_msg", "user not found");
res.redirect("/fooddrivebanner");
}
}).updateOne({ eventparticpating: "foodrive" });
});
I hope that answers your question
I have a model of courses with the following structure:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const user_shortid = require('shortid');
// Create Course schema
const CourseSchema = new Schema({
courseDetail: {
type: String
},
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
enrolledUsers: [{
type: Schema.Types.ObjectId,
ref: 'users'
}],
currentStatus: {
type: String,
default: 'Planned'
}
});
mongoose.model('courses', CourseSchema);
I have created a post request for adding a signed in user to the array of enrolledUsers, the problem is, I want to check first if the req.user.id exists in the enrolledUsers array. Following is my post request:
router.post('/joincourse', [ensureAuthenticated], (req, res) => {
Course.findByIdAndUpdate({ _id: req.body.coursecode },
{ $push: { enrolledUsers: req.user.id } },
{ safe: true, upsert: true },
function (err, doc) {
if (err) {
req.flash('error_msg', 'Could not enroll in the course');
res.redirect('/dashboard');
} else {
req.flash('success_msg', 'You are now enrolled in the course');
res.redirect('/dashboard');
}
}
);
});
Right now the behavior is that a user can enroll again and again in the same course.
Is there some way I can check for the req.user.id in the enrolledUsers array before it is added?
you can do find the user first by using find() and then if a user exists, update it , else
give an error like this
router.post('/joincourse', [ensureAuthenticated], (req, res) => {
Course.findById({ _id: req.body.coursecode },
function (err, doc) {
if (err) {
req.flash('error_msg', 'Could not enroll in the course');
res.redirect('/dashboard');
} else {
if(doc){
if(!doc.enrolledUsers.includes(req.user.id)){ // here is the checking
doc.enrolledUsers.push(req.user.id);
doc.save();
req.flash('success_msg', 'You are now enrolled in the course');
res.redirect('/dashboard');
}
}else{
// show error msg
}
}
}
);
});
I try to call a related list of logs for a certain user via Mongoose populate. Who can help me with finishing the response?
These are the schemes:
const logSchema = new Schema({
logTitle: String,
createdOn:
{ type: Date, 'default': Date.now },
postedBy: {
type: mongoose.Schema.Types.ObjectId, ref: 'User'}
});
const userSchema = new Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
}
logs: { type: mongoose.Schema.Types.ObjectId, ref: 'logs' }
});
mongoose.model('User', userSchema);
mongoose.model('logs', logSchema);
Inspired by the Mongoose documentary (see above) and other questions in relation to this subject I think I got pretty far in making a nice get. request for this user. I miss the expierence to 'translate it' to Express.
const userReadLogs = function (req, res) {
if (req.params && req.params.userid) {
User1
.findById(req.params.userid)
.populate('logs')
.exec((err, user) => {
if (!user) { }); // shortened
return;
} else if (err) {
return; // shortened
}
response = { //question
log: {
user: user.logs
}
};
res
.status(200)
.json(response);
});
} else { }); //
}
};
The response in Postman etc would be something like this:
{
"log": {5a57b2e6f633ce1148350e29: logTitle1,
6a57b2e6f633ce1148350e32: newsPaper44,
51757b2e6f633ce1148350e29: logTitle3
}
First off, logs will not be a list of logs; it will be an object. If you want multiple logs for each user, you will need to store is as an array: logs: [{ type: mongoose.Schema.Types.ObjectId, ref: 'logs' }]
From the Mongoose docs: "Populated paths are no longer set to their original _id , their value is replaced with the mongoose document returned from the database by performing a separate query before returning the results." In other words, in your query user.logs will be the logs document for each user. It will contain all the properties, in your case logTitle, createdOn, and postedBy.
Sending user.logs as json from the server is as easy as: res.json(user.logs). So your query can look like this:
const userReadLogs = function (req, res) {
if (req.params && req.params.userid) {
User1
.findById(req.params.userid)
.populate('logs')
.exec((err, user) => {
if (!user) { }); // shortened
return;
} else if (err) {
return; // shortened
}
res.status(200).json(user.logs)
});
} else { }); //
}
};
I hope this makes it a little bit clearer!
I'm developing an app using Node.js, Mongoose, MongoDb, express.
I have 2 schemas one for student and one for snippets. I'm using the population model population model. I can create a user, and create a snippet and link it to the user. But I can't link and save the snippets in the user collection.
How to link and save the user so that it can have a reference to his snippets?
user and snippet schema
var userSchema = Schema({
name: { type: String, required: true, unique: true },
password: { type: String, required: true },
snippet: [{ type: Schema.Types.ObjectId, ref: 'Snippet' }]
})
var snippetSchema = Schema({
user: {type: Schema.Types.ObjectId, ref: 'User'},
title: String,
body: String,
createdAt: {
type: Date,
require: true,
default: Date.now
}
})
This is how I save the snippets I add it inside a user .save() function so that it saves the snippet ref but it gives me user.save() is not a function error.
var name = request.session.name.name
User.find({ name: name }).then(function (user) {
if (user) {
console.log('====================')
console.log(user)
user.save().then(function () { // problem is here?
var newSnippet = new Snippet({
user: user._id,
title: title,
body: snippet
})
newSnippet.save().then(function () {
// Successful
console.log('success')
response.redirect('/')
})
})
}
}).catch(function (error) {
console.log(error.message)
response.redirect('/')
})
But, I actually get the object printed after searching for it!
[ { _id: 5a2e60cf290a976333b19114,
name: 's',
password: '$2a$10$vD3EaQly4Sj5W3d42GcWeODuFhmHCSjfAJ1YTRMiYAcDBuMnPLfp6',
__v: 0,
snippets: [] } ]
You need to use User.findOne to get a valid user object, here you get an array. Also, don't forget to always return something in you promises (or throw an error).
Here is a quick rewrite of your function. With a few improvements such as arrow functions, const and a flat promise chain (never using any .then inside another .then) and avoiding code repetition
const name = request.session.name.name
User.findOne({ name })
.then(user => {
if (user) return user.save()
// What to do if not found? Throw an error?
throw new Error('User not found')
})
.then(() => {
const newSnippet = new Snippet({
user: user._id,
title: title,
body: snippet,
})
return newSnippet.save()
})
.catch((error) => console.log(error.message))
.then(() => response.redirect('/'))