Why can't I access the elements of this object in Javascript? - javascript

I'm working on a simple hobby project after finishing a Udemy course on web development, and I'm having some issues with a simple middleware I've written.
The function in question goes as follows:
const User = require('../models/user.js');
const middleware = {
checkIfAdmin: function checkIfAdmin(req, res, next) {
if (req.isAuthenticated()) {
User.find({"username": "jemorgan"}, (err, foundUser) => {
console.log(foundUser._id);
if (err) {
req.flash("error", "Something went wrong");
} else if (foundUser._id === req.user._id) {
console.log("Going next");
next();
} else {
console.log("going back");
req.flash("error", "You don't have permission to do that.");
res.redirect("back");
}
});
}
}
}
module.exports = middleware;
When I ran the code without the console.log, evaluating the final else branch regardless of whether or not I was logged in as the correct user. I threw in the console.log to try to troubleshoot, and found that user._id was undefined. Strangely, when I remove the .id and simply call console.log(user), I get the following:
[
{
_id: 5ef5c1adfc196d47107679ce,
username: 'jemorgan',
...
}
]
I feel like there must be something simple here that I'm missing. My troubleshooting leads me to believe that the User.find() function is locating the correct document in the database, and that foundUser is a reference to the correct object. Yet when I try to access elements of that object, I'm being told they're undefined, even though I can clearly see that the object does have those elements.
To keep this brief, I'm not showing the whole project here, but if anyone wants context, the project is hosted on my github.
Thanks for any tips that you might habe.

It seems you are querying through Mongoose using .find and not .findOne, it's easy to confuse the two but the first one will always return an array of documents that matched the query while the second will return the first object it finds according to the query. When querying emails/ids etc. you should use findOne.

In your example :
let user=[
{
_id: 5ef5c1adfc196d47107679ce,
username: 'jemorgan',
...
}
]
since user is an array so you can't access user._id , Instead you should user[0]?._id

Related

Mongoose - No matching document found (ForEach)

I'm new to NodeJS and Mongoose and this might be a duplicate question, so don't reply back negatively please. I tried finding a solution but failed to fix this error.
Basically, I get this error when I try to update the database values. The first update I perform works perfectly but from the second one onward, I get this error. The values update but it updates several fields at once giving this error.
Here's my code: (and I have added my github link to the project):
User.js (model)
local: {
...,
education: [{
id: String,
degree: String,
institute: String,
dates: String,
description: String
}],
...
}
router.js
app.put('/put_education/:id', function(req, res) {
var row_id = req.params.id; //get education id from table row
//get the current logged in user
User.findById(req.user._id, function (err, doc) {
if (err) {
console.log('no entry found');
}
//match the table row id with the id in users education model
doc.local.education.forEach(function (education, index) {
console.log(education.id + " " + row_id);
//if rows match, replace the database document with values from client
if(education.id === row_id){
doc.local.education[index] = req.body;
doc.save(function (err) {
if (err) {
console.log(err);
} else {
res.send("Success");
}
});
}
});
});
});
I added a console.log to see the loop operates, the image below shows its fine for the first iteration but acts weirdly for the next ones:
I was thinking of breaking the loop after the id's match but the foreach loop doesnt have a break function, I changed it to a normal for loop but it still gives me the same error. So I dont think breaking the loop is an answer..
Edit: Added image of my website to show what happens after updating (duplicates rows)
Github: https://github.coventry.ac.uk/salmanfazal01/304CEM-Back-End
if break out of an iteration is your issue, then why not use a simple for loop with break statement.
let eduArray = doc.local.education;
for(i=0;i<eduArray.length;i++) {
if(eduArray[i]["id"] == row_id) {
// do your logic
break;
}
}
becuase you are updating the education first time with no problem, then the problem appears afterwards, I suspect that you update the education wrongly, look into this line of code you wrote:
doc.local.education[index] = req.body;
here I see you are assigning whatever inside the request body to education, but have you checked what data is actually inside the req.body?
try to log the req.body to see what you actually assigning to the education.

Update 2 different collections simultaneously

I have two models defined like this:
var OrganizationSchema = new mongoose.Schema({
users: [mongoose.Schema.Types.ObjectId]
});
and
var UserSchema = new mongoose.Schema({
organizations: [mongoose.Schema.types.ObjectId]
});
When a user want to join an organization I need to update both the organization collection and the user collection. I want to know what is the best way to achieve this ? Is it worth considering the case when one update request fail ? I'm currently doing something like this (where organization is a collection instance of the model Organization):
User.findByIdAndUpdate(req.userSession.userId, { $push: { organizations: organization.id } }, function (err){
if (err)
{
// server error
console.log(err);
}
else
{
organization.users.push(req.userSession.userId);
organization.save(function (err){
if (err)
{
// server error we need to cancel
User.findByIdAndUpdate(req.userSession.userId, { $pull: { organizations: organization.id } }, function (err){
if (err)
{
// we got a problem one collection updated and not the other one !!
console.log(err);
}
});
}
else
{
// success
}
});
}
});
The problem is: if my second update method fail I will end up with one collection updated and not the other ? Is there a way to make sure they are both updated ?
Well firstly, I would stay clear of that design. I would either reference or embed user in organisations and the other way around, not both of them at same time, so I wouldn't have problems like this(which happens every-time you duplicate data).
MongoDB doesn't have support for simultaneous updates, or transactions. So you are left to manage this in your code.
So yes, if the second update fails, then as you wrote your code you have to rollback, and if the rollback fails, you have to retry till it succeeds(though with exponential backoff probably). Keep in mind that might intefer with other requests(another user tries to save the same thing simultaneously). To handle that you have to give a unique to each entry in the array.

Adding a filter inside a beforeRemote remote hook

I have a problem I can't find an answer to in Loopback's docs.
Say I have a model Company and a modelEmployee. There is an 1Xn relation between the Company and its Employees. When /api/Employees is called, server returns all the employees.
I only want to return the list of employees who are in the same company with the user requesting the list.
For this, I created a remote hook
Employee.beforeRemote('find', function(context, modelInstance, next) {
var reject = function() {
process.nextTick(function() {
next(null, false);
});
};
// do not allow anonymous users
var userId = context.req.accessToken.userId;
if (!userId) {
return reject();
}
//I get the details of the user who sent the request
//to learn which company does he belong to
Employee.findById(userId, function(err, user) {
if(!context.req.query.filter) context.req.query.filter={};
context.req.query.filter.where = {brandId:user.companyId};
console.log(context.req.query);
next();
});
});
I thought this should work every time, but appearantly it only works when find already has some query filters like include - although the console.log prints a correct context.req.query object.
What am I missing? Any help would be greatly appreciated!
context.args.filter seems to work for this purpose.
As a side note, instead of replacing where, you might want to merge it with something provided by client. For implementation idea you can refer to: https://github.com/strongloop/loopback-datasource-juggler/blob/master/lib/utils.js#L56-L122

Finding a user by ID inside a collection.allow behaving very strangely in Meteor

I only want to allow a user to insert a document when his email is verified, I wrote the following code.
Events.allow({
insert: function (userId, doc) {
var user = Meteor.users.find({_id: userId});
console.log(user); // logs a large object
// if the user's email is verified
if (user.emails[0].verified) {
return true;
}
return false;
}
});
Running this, I get an error, "internal server error", looking at the server, I get TypeError: Cannot read property '0' of undefined, which means that something is wrong with my user object. So I logged the object, and instead of seeing a user, I get a huge object, abbreviated below:
{ _mongo:
// lots of stuff
}
I think that's the top level mongoDB object that Meteor uses.
How in the world is Meteor.users.find({_id: userId}) returning the MongoDB object instead of the user I'm looking for?
You're probably looking for findOne, and not find. find returns a cursor to the result, findOne returns the first matched document.

Add a new field to a document mongodb

I am very new to mongodb and have a basic question that I am having trouble with. How do I get the ID field of a document that has already been created? I need the ID so i can update/add a new field to the document.
//newProfile is an object, one string it holds is called school
if(Schools.find({name: newProfile.school}).fetch().length != 1){
var school = {
name: newProfile.school
}
Meteor.call('newSchool', school);
//Method 1 (doesn't work)
var schoolDoc = Schools.findOne({name: newProfile.school});
Schools.update({_id: schoolDoc._id}, {$set: {enrolledStudents: Meteor.user()}});
//Method 2?
//Schools.update(_id: <what goes here?>, {$push: {enrolledStudents: Meteor.user()}});
}
else {
//Schools.update... <add users to an existing school>
}
I create a new school document if the listed school does not already exist. Schools need to hold an array/list of students (this is where i am having trouble). How do I add students to a NEW field (called enrolledStudents)?
Thanks!
I'm having some trouble understanding exactly what you're trying to do. Here's my analysis and understanding so far with a couple pointers thrown in:
if(Schools.find({name: newProfile.school}).fetch().length != 1){
this would be more efficient
if(Schools.find({name: new Profile.school}).count() != 1) {
Meteor.call('newSchool', school);
Not sure what you're doing here, unless you this will run asynchronously, meaning by the time the rest of this block of code has executed, chances are this Meteor.call() function has not completed on the server side.
//Method 1 (doesn't work)
var schoolDoc = Schools.findOne({name: newProfile.school});
Schools.update({_id: schoolDoc._id}, {$set: {enrolledStudents: Meteor.user()}});
Judging by the if statement at the top of your code, there is more than one school with this name in the database. So I'm unsure if the schoolDoc variable is the record you're after.
I believe you are having trouble because of the asynchronous nature of Meteor.call on the client.
Try doing something like this:
// include on both server and client
Meteor.methods({
newSchool: function (school) {
var newSchoolId,
currentUser = Meteor.user();
if (!currentUser) throw new Meteor.Error(403, 'Access denied');
// add some check here using the Meteor check/match function to ensure 'school'
// contains proper data
try {
school.enrolledStudents = [currentUser._id];
newSchoolId = Schools.insert(school);
return newSchoolId;
} catch (ex) {
// handle appropriately
}
}
});
// on client
var schoolExists = false;
if (Schools.findOne({name: newProfile.school})) {
schoolExists = true;
}
if (schoolExists) {
var school = {
name: newProfile.school
};
Meteor.call('newSchool', school, function (err, result) {
if (err) {
alert('An error occurred...');
} else {
// result is now the _id of the newly inserted record
}
})
} else {
}
Including the method on both the client and the server allows Meteor to do latency compensation and 'simulate' the insert immediately on the client without waiting for the server round-trip. But you could also just keep the method on the server-side.
You should do the enrolledStudents part on the server to prevent malicious users from messing with your data. Also, you probably don't want to actually be storing the entire user object in the enrolledStudents array, just the user _id.
For what you're trying to do, there is no need to get the _id. When you use update, just switch out the {_id: schoolDoc._id} with your query. Looks like using {name: newProfile.school} will work, assuming that the rest of your code does what you want it to do.
While that would work with the normal Mongo driver, I see that Meteor does not allow your update query to be anything but _id: Meteor throws throwIfSelectorIsNotId exception
First, make sure that you're pulling the right document, and you can try something like this:
var school_id = Schools.findOne({name: newProfile.school})._id;
Schools.update({_id: school_id}, { $push: { enrolledStudents: Meteor.user()}});
If that doesn't work, you'll have to do a little debugging to see what in particular about it isn't working.

Categories

Resources