Call a related collection via populate - node.js

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!

Related

Pass a Button value and Update the database

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'm unable to push my array to mongodb document using node.js

In my previous html code when I submit it sends a post to /comment/:id then the website crashes and outputs MongoError: Unsupported projection option: $push: { comment: { content: "gfdghd" } } in my console. I don't know how to solve it and I hope I can get some help on the issue as I'm a starter with web development.
I want this to work by pushing the array which includes the req.body into a certain mongodb array default collection where it finds the parent post _id. If you need me to elaborate please ask, thanks.
This is my code:
app.js
const Post = require("./models/Post");
mongoose
.connect("secret", {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: true,
})
.then(() => {
console.log("connected to mongodb cloud! :)");
})
.catch((err) => {
console.log(err);
});
app
.post("/comment/:id", authenticateUser, async (req, res) => {
const content = req.body;
// checks for missing fields
if (!content){
return res.send("Please enter all the required credentials!");
}
//This is where I tried to match and then push it to mongodb
Post.update({"_id": ObjectId(req.params.id) }, {
$push: {
comment: content,
}
}, function (error, success) {
if (error) {
console.log(error);
} else {
console.log(success);
}
});
})
Post Mongoose Schema
Post.js
const mongoose = require("mongoose");
const PostSchema = new mongoose.Schema({
title: {
type: String,
required: true,
},
content: {
type: String,
required: true,
},
postedAt: {
type: String,
default: new Date().toString()
},
postedBy: {
type: String,
},
warned: {
type: String,
},
comment: [String]
});
module.exports = new mongoose.model("Post", PostSchema);
Everything else works but the array functionality.
I think there are a few mistakes, you didn't await the request and you put "_id" when querying instead of _id.
Another way you could do it too would be using findByIdAndUpdate method.
await Post.findByIdAndUpdate(req.params.id, {
$push: {
comment: content,
},
function(error, success) {
if (error) {
console.log(error);
} else {
console.log(success);
}
},
});

find an id in embedded document node mongoose

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
}
}
}
);
});

get by id in mongoose

If I want to get by id a only subdocument that is an array into a document, how I need to call it? I have these models
/* Build */
var buildSchema = new Schema({
buildName: String,
package: [packageSchema],
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
}
})
var Build = mongoose.model('build', buildSchema)
/* Project */
var projectSchema = new Schema({
projectName: String,
build: [buildSchema],
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
}
})
var Project = mongoose.model('project', projectSchema)
my get function and route is this:
router.get('/build/:id', function (req, res, next) {
var token = helperJwt.decodeToken(req)
jwt.verify(token, helperJwt.secret, function (error, decoded) {
if (error) {
res.status(401)
} else {
Project.findOne({ _id: req.params.id}, 'build', function (err, build) {
if (err) {
res.status(401)
} else {
build.findOne({build: build._id}, 'postedBy', 'email', function (err, getBuild) {
if (err) {
res.status(401)
} else {
return status(200).json(build)
}
})
}
})
}
})
})
that is good or how can I do for get only one build?
I'm using vue as front end but it doesn't recognise only one build data.
I don't know how to findById a subdocument.
I want to bring something like:
{
"_id" : ObjectId("5a26fbfd1866893baf14b175"),
"postedBy" : "5a2021539f1d222bfc915fa2",
"projectName" : "El pipiripau",
"build" : [
{
"buildName" : "bdfbdgf",
"postedBy" : ObjectId("5a2021539f1d222bfc915fa2"),
"_id" : ObjectId("5a26fc0dbe4de0039cdae0c7"),
"package" : []
},
}
If you use buildSchema inside of projectSchema, the build array will contain subdocuments so they won’t have ids.
You could set a custom id field in your build schema and then reference that in your query, but you would have to take care of setting those ids when you save your project docs.
Alternatively, if your build docs are being stored in a separate collection (as you have a build model) then they will have an _id field and you could store an array of references to the build model in your projectSchema:
var projectSchema = new Schema({
projectName: String,
build: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'build'
}],
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
}
})
And then, assuming req.params.id is the id of a build doc, you could find projects where the build array has the id and then use populate to replace the array of build ids with the corresponding subdocuments:
Project
.find({ build: req.params.id })
.populate('build')
.exec(function (err, projects) {
if (err) {
// Handle error
} else {
// For each project, filter builds for just the one matching the id param
projects.forEach(project => {
project.build = project.build.filter(item => item._id == req.params.id )
})
// Handle result
}
})
Here are the docs on population:
http://mongoosejs.com/docs/populate.html
I hope this helps.
https://mongoosejs.com/docs/api.html#model_Model.findById
// Find the adventure with the given `id`, or `null` if not found
await Adventure.findById(id).exec();
// using callback
Adventure.findById(id, function (err, adventure) {});
// select only the adventures name and length
await Adventure.findById(id, 'name length').exec();

Mongoose .pre('save') does not trigger

I have the following model for mongoose.model('quotes'):
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var quotesSchema = new Schema({
created: { type: String, default: moment().format() },
type: { type: Number, default: 0 },
number: { type: Number, required: true },
title: { type: String, required: true, trim: true},
background: { type: String, required: true },
points: { type: Number, default: 1 },
status: { type: Number, default: 0 },
owner: { type: String, default: "anon" }
});
var settingsSchema = new Schema({
nextQuoteNumber: { type: Number, default: 1 }
});
// Save Setting Model earlier to use it below
mongoose.model('settings', settingsSchema);
var Setting = mongoose.model('settings');
quotesSchema.pre('save', true, function(next) {
Setting.findByIdAndUpdate(currentSettingsId, { $inc: { nextQuoteNumber: 1 } }, function (err, settings) {
if (err) { console.log(err) };
this.number = settings.nextQuoteNumber - 1; // substract 1 because I need the 'current' sequence number, not the next
next();
});
});
mongoose.model('quotes', quotesSchema);
There is an additional Schema for mongoose.model('settings') to store an incrementing number for the incrementing unique index Quote.number im trying to establish. Before each save, quotesSchema.pre('save') is called to read, increase and pass the nextQuoteNumber as this.number to the respectively next() function.
However, this entire .pre('save') function does not seem to trigger when saving a Quote elsewhere. Mongoose aborts the save since number is required but not defined and no console.log() i write into the function ever outputs anything.
Use pre('validate') instead of pre('save') to set the value for the required field. Mongoose validates documents before saving, therefore your save middleware won't be called if there are validation errors. Switching the middleware from save to validate will make your function set the number field before it is validated.
quotesSchema.pre('validate', true, function(next) {
Setting.findByIdAndUpdate(currentSettingsId, { $inc: { nextQuoteNumber: 1 } }, function (err, settings) {
if (err) { console.log(err) };
this.number = settings.nextQuoteNumber - 1; // substract 1 because I need the 'current' sequence number, not the next
next();
});
});
For people who are redirected here by Google, make sure you are calling mongoose.model() AFTER methods and hooks declaration.
In some cases we can use
UserSchema.pre<User>(/^(updateOne|save|findOneAndUpdate)/, function (next) {
But i'm using "this", inside the function to get data, and not works with findOneAndUpdate trigger
I needed to use
async update (id: string, doc: Partial<UserProps>): Promise<User | null> {
const result = await this.userModel.findById(id)
Object.assign(result, doc)
await result?.save()
return result
}
Instead of
async update (id: string, doc: Partial<UserProps>): Promise<User | null> {
const result = await this.userModel.findByIdAndUpdate(id, doc, { new: true, useFindAndModify: false })
return result
}
The short solution is use findOne and save
const user = await User.findOne({ email: email });
user.password = "my new passord";
await user.save();
I ran into a situation where pre('validate') was not helping, hence I used pre('save'). I read that some of the operations are executed directly on the database and hence mongoose middleware will not be called. I changed my route endpoint which will trigger .pre('save'). I took Lodash to parse through the body and update only the field that is passed to the server.
router.post("/", async function(req, res, next){
try{
const body = req.body;
const doc = await MyModel.findById(body._id);
_.forEach(body, function(value, key) {
doc[key] = value;
});
doc.save().then( doc => {
res.status(200);
res.send(doc);
res.end();
});
}catch (err) {
res.status(500);
res.send({error: err.message});
res.end();
}
});

Resources