Mongoose - get data from multiple collections - node.js

I want to get data in nodejs application as describe bellow out put.
user.js, userProfile are the models of mongoose.
user.js
var userSchema = new Schema({
nick_name:{type:String},
email: {type: String},
password: {type: String},
is_active:{type:String,enum:['1','0'],default:"1"},
},{ collection: 'user'});
userProfile.js
var userProfileSchema = new Schema({
user_id:{type:Schema.Types.ObjectId, ref:'User'},
first_name:{type:String},
last_name:{type:String},
address:{type:String},
country:{type:String},
state:{type:String},
city:{type:String},
gender:{type:String,enum:['m','f','o'],default:"m"},
is_active:{type:String,enum:['1','0'],default:"1"},
},{ collection: 'userProfile'});
wants to get out put as follows
{
"nick_name" : "laxman",
"email" : "laxman#mailinator.com",
"first_name" : "my first name",
"last_name" : "my last name",
"address" : "my address",
"country" : "my country",
"state" : "my state",
"city" : "my city",
"gender" : "m",
}
dependencies
"express" => "version": "4.7.4",
"mongoose" => "version": "4.4.5",
"mongodb" => "version": "2.4.9",
"OS" => "ubuntu 14.04 lts 32bit",
SQL query for reference
SELECT * FROM `user`
left join `user_profile`
on user.id =user_profile.user_id
WHERE 1

The answer to this question is going to depend on how you are doing your authentication, but I will answer this question how I would achieve what you are asking.
Firstly, I would make one Schema instead of having two separate ones. Then, depends on your authentication method, I use tokens, you will pass that data to whatever you is receiving it on the client side. It would also help to know if you are using anything on the client side that handles HTTP requests or if this is just a node application.
I'll try to cover both methods in some detail.
Say you are using cookies for instance, I'm going to assume you know how to set up a session using Express, or whatever if any framework you are using on top of node, as well as assuming you know what you are doing as far as securing your passwords properly and skip straight to the api building.
Pretend this is a controller on your server side to authenticate the user on login.
var User = mongoose.model('User');
exports.login = function (req, res) {
User.findOne({email: req.body.email), function (err, user) {
if (err) {
return res.json(err);
} else if (!user) {
return res.json({message: 'User not found.'});
} else {
if (req.body.password === user.password) {
req.session.user = user;
} else {
return res.json({message: 'Invalid username or password.'});
}
}
)};
};
Of course, you want to make sure you have some middleware set up so the password is definitely not send along with the user, but that is not the matter here.
Once you have that, then any other route where you need user data is fairly simple. You make another controller like so:
exports.profile = function (req, res) {
if (req.session && req.session.user) {
User.findOne({email: req.session.user.email}, function (err, user) {
if (!user) {
res.session.reset();
res.redirect('/login');
} else {
res.locals.user = user;
res.render('/profile);
}
)};
}
};
I did the rendering on the server side, but if you are using something like Angular, you can pull that to the client side.
If you are using tokens, which I highly recommend, then the method is very similar. I will be happy to explain some of that as well.

Related

how to stop users from viewing and updating another user's data in node.js?

I am storing a parking detail with a merchant id in the mongoose schema since a parking belongs to a certain merchant user and it cannot be empty or null.
Here is the model:
const parkingSchema = new mongoose.Schema({
merchantId: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: "Merchant",
},
//other details
})
merchant model is something like this:
const merchantSchema = new mongoose.Schema({
merchantId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Auth",
},
//other details
})
And finally the auth schema:
const authSchema = new mongoose.Schema({
accountType: {
type: String,
required: true,
trim: true,
default: "user",
enum: ["merchant", "user", "provider"],
},
//other details
})
If the original user wishes it, I simply want to update the parking data; otherwise, I want to throw an error.
I am using jsonwebtoken to authenticate users.
Here is the query to update the data:
exports.updateParking = async (req, res) => {
try {
const { parkingName, price, address, name, phoneNumber, about } = req.body;
const { parkingImage } = req.files;
const check_exist = await Auth.findById(req.data.id);
if (!check_exist) return res.status(404).json({ error: "User not found" });
console.log(req.data.id);
const updateData = await Parking.findByIdAndUpdate(
{ _id: req.params.id, merchantId: req.data.id }, // I think here is the problem
{
$set: {
parkingName,
price,
address,
...
},
}
);
return res.status(200).json({
success: true,
msg: "Parking has updated successfully",
});
} catch (error) {
return error.message;
}
};
However, the issue is that other users can now update another user's data which I want to stop
below is the query of middleware:
routing.patch("/parking/update/:id", middleware.authenticateToken, merchant.updateParking)
You should be showing each user only their parkings that they have created or belong to them.
const myParkings = async (req, res) => {
// always await code in try/catch block
const merchants = await Parkings.find({ user: req.user._id })
.. then populate the fields that you want to show
res.status(200).json({
success: true,
bookings,
});
};
you have to set this req.user._id when user logins. You could create a session.
I think what you're looking for is something like CASL Mongoose (or a similar package), and more specifically, the "conditions" section of the CASL docs.
What you're dealing with here is the distinction between 2 concepts:
AuthN (authentication) - determines who someone is and whether they are "authenticated" to make an API request
AuthZ (authorization) - determines what the authenticated user is allowed to do
In your app, middleware.authenticateToken is responsible for the AuthN piece of the equation. It makes sure that only users that have created an account are able to make requests to your API routes.
What you still need to solve for is the AuthZ piece, which can be done in a bunch of different ways, but one popular one is to use CASL, which is a Node AuthZ library that allows you to utilize your ORM's native query syntax to limit actions based on the authenticated (AuthN) user's attributes.
In other words, you can do something like, "Only allow user with ID 1 to update Parking entities that he/she owns". Below is generally what you're looking for (not tested for your use case, but the general idea is here):
const casl = require('#casl/ability');
// Define what a `Auth` (user) can do based on their database ID
function defineMerchantAbilities(merchantUser) {
const abilities = casl.defineAbility((allow, deny) => {
// Allow merchant to update a parking record that they own
allow('update', 'Parking', { merchantId: merchantUser.id })
})
return abilities
}
exports.updateParking = async (req, res) => {
const userId = req.data.id
const parkingId = req.params.id
// Find your merchant user in DB (see my comments at end of post)
const merchantUser = await Auth.findById(userId)
// Find your parking record
const parking = await Parking.findById(parkingId)
// Pass user to your ability function
const ability = defineMerchantAbilities(merchantUser)
// This will throw an error if a user who does not own this Parking record
// tries to update it
casl.ForbiddenError
.from(ability)
.throwUnlessCan('update', casl.subject('Parking', parking))
// If you make it here, you know this user is authorized to make the change
Parking.findByIdAndUpdate( ...your code here )
}
Additional comments/notes:
I would recommend removing your try/catch handler and using an Express default error handler as it will reduce the boilerplate you have to write for each route.
I would also recommend writing a middleware that finds a user by ID in the database and attaches it to a custom property called req.user so you always have req.user available to you in your authenticated routes.

Mongoose-encryption Failure, Page keeps rolling

I am having a problem with an npm package called 'mongoose-encryption'. Here is my code.
main().catch((err) => console.log(err));
async function main() {
await mongoose.connect('mongodb://localhost:27017/userEncryptedDB', { useNewUrlParser: true });
}
const userSchema = new mongoose.Schema({
email: String,
password: String
});
const secret = 'xxs4Ox4nSKSVnJzIxzy+es6ouOmoMcqcarAnEVRP26Q=';
userSchema.plugin(encrypt, {secret: secret, encryptedFields: ['password']});
const User = mongoose.model('User', userSchema);
Now, if I don't use the package, registration and logging in to the website works properly but if I use the plug in, it says "You are never registered!" even though I register for the email and password...
app.post('/login', (req, res) => {
const email = req.body.username;
const password = req.body.password;
User.findOne({email: email, password: password}, (err, user) => {
if(err) {
console.error(err);
} else {
if(user){
console.log('You are already registered!');
res.render('secrets');
} else {
console.error('You are never registered!');
}
}
});
});
What am I doing wrong here? I started computer stopped database, restarted it. Registration is successfully done but when I try to login page keeps rolling... If I dont use mongoose-encryption, login page works fine once I enter password.
I can clearly see that the new username gets added to the database like so:
{ "_id" : ObjectId("616a4aa45dfbf877d9f92468"), "email" : "aras#aras.com", "_ct" : BinData(0,"YbVYW9Pi6dz1yqRD7XgcEyEp48lLGVjp3J40ZgxGt/YuA8+eyiyebMaO9AGdygBj6A=="), "_ac" : BinData(0,"YQiV2QvS9AyrE1hjm81x5U8K6+0lT8h1ruxtCEZv/iwZWyJfaWQiLCJfY3QiXQ=="), "__v" : 0 }
But then when I try to login, it fails to retrieve password. Password I use here in this case is "1".
Try dropping the collection (first save it somewhere for later use) and try again. It will work!

Mongoose model methods - create document if unique

I'm new to MongoDB and Mongoose. I'm using it with my Node project (with Express), and I'm trying to keep everything organized and separated. For example, I'm trying to keep all the database queries in each model file. This way all other files could simply use User.createNew({ fields }) and a new user will be created.
I need each user to be unique (based on their usernames), and I'm not sure exactly where to keep this functionality. I set unique: true in the Schema but upon reading Mongoose's documentation, they stated that unique is not real validation (or something about how validation should happen beforehand). So my main problem is how to create a static method to create a new user, and also validate this user doesn't exist beforehand. I could implement all of this in one static method:
userSchema.statics.createUser = function (username, ..., cb) {
this.findOne({ username }, function (err) {
if (err) {
return new this({
username,
...
}).save(cb);
} else {
return Promise.reject(new Error("User already exists!"));
}
});
};
I'm pretty confused with the whole cb function and what I'm supposed to pass to it.
After reading other posts about validation, I realized I could also do something like this:
const userSchema = new mongoose.Schema({
username: {
type: String,
unique: true,
validate: function (val, fn) {
this.find({ username: val }, function (err) {
fn(err || true);
});
},
message: function (props) {
`Username '${props.value}' already exists.`;
},
},
...
Also here I'm confused about what fn accepts and what it even does (I found an answer similar to this with no explanation online).
In the end, I would like to use this model in a controller to create a new user, like this
User.createNew({ username: "example", ...})
.then(doc => console.log("User was created: " + doc))
.catch(err => console.error) // The error is something custom like "This user already exists"
Any help is appreciated!

How to create route for particular user using postgreSQL and nodejs?

I am working on a nodejs project where I am using postgreSQL. The project is related to creating a timesheet for employers. I have already created routes for showing timesheet and project.
app.route(/api/timesheet)
Now, I want to create a user model for this project and want to add particular timesheet for particular user. That means, user with particular userid can only view timesheet information related to this user.
I can do it with mongodb but I would like to do it with postgreSQL.
Thank you in any advance for your help.
well, I've created users array where I have two users
const users = [
{
"id": "userId1",
"email": "user1#mail.com",
"password": "password"
},
{
"id": "userId2",
"email": "user2#mail.com",
"password": "password2"
}
];
then I created route as you wrote above
app.route('/api/timesheet')
.get(function (req, res) {
let id = req.params.id;
let user = users.find(user => user.id = id);
res.json(user);
});
Routes does not varies, route is always same, just it brings different data according to what you pass as a parameter.
if your GET request in query has "?userId=userId1" that will give you first object in response, so does if you pass second "userIs2", it will give you second one.
When I am using postgreSQL in nodejs project, I always do with sequelize
You can define user schema like that
module.exports = function(sequelize, DataTypes) {
return sequelize.define('user', {
email: Sequelize.STRING,
password: Sequelize.STRING
});
}
and then you will call User.findById(id) and it will give you user specified with id of course
sequelize is a powerful library so you should look at the documentation before use it

Data model reference using ObjectID in Mongoose

I have two mongoose models users and requests. One user can have multiple requests. To link these two data models, I am referencing the objectID of the user in the Request model. However when I am trying to fetch all the requests for a particular user ID, I don't get any result.
Users
var UserSchema = new Schema({
name: String,
username: String
});
module.exports = mongoose.model('User', UserSchema);
Requests
var RequestSchema = new Schema({
type: String,
user_id: { type : mongoose.Schema.Types.ObjectId, ref: 'User'}
});
module.exports = mongoose.model('Request', RequestSchema);
Below is the sample code which fetches the requests based on the User ID passed in the URL
exports.getRequests = function (req, res, next) {
var userId = req.params.id;
console.log('USER ID'+userId);
Request.findOne({'user_id': 'kenan'}, function (err, request) {
if (err) return next(err);
if (!requests){
console.log('NO REQUEST FOUND');
return res.send(401);
}
res.json(request);
});
};
I know I am using findOne which will return me a single resource. However this is failing and I am not getting any result. I have in my DB the data populated. Below is the URL for fetching the requests.
http://localhost:9000/api/V1/users/547ee1e82e47bb982e36e433/requests
Please help suggest as to what I am doing wrong over here.
Thanks
You need to populate the references to the users once you do a find().
Once you find all the requests, you need to resolve the references to
the users. Use populate to resolve the references.
After resolving, filter the requests based on the name that you
are searching for.
Updated Code:
Request
.find().populate({'path':'user_id',match:{'name':'kenan'}})
.exec(function (err, resp) {
var result = resp.filter(function(i){
return i.user_id != null;
});
console.log(result);
})
OK, the above answer is correct and thanks for your response. I incorrectly populated my Requests collection. This is just for every one's else knowledge. When you have a Object ID reference as one of the fields in the collection then make sure that you are inserting the ObjectID object instead of just the numerical value.
Wrong
{ "_id" : ObjectId("54803ae43b73bd8428269da3"), "user_id" : "547ee1e82e
47bb982e36e433", "name" : "Test", "service_type" : "test", "version" : "10"
, "environment" : "test", "status" : "OK", "__v" : 0 }
Correct
{ "_id" : ObjectId("54803ae43b73bd8428269da3"), "user_id" : ObjectId("547ee1e82e
47bb982e36e433"), "name" : "test", "service_type" : "test", "version" : "10"
, "environment" : "test", "status" : "OK", "__v" : 0 }

Resources