Get info from 2 separate Mongo documents in one mongoose query - node.js

Im using MongoDb, and I have a workspace schema with mongoose (v4.0.1):
var Workspace = new mongoose.Schema({
name: {
type: String,
required: true
},
userId: {
type: String,
required: true
},
createdOn: {
type: Date,
"default": Date.now
}
});
And a user schema:
var User = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true
},
organisation: {
type: String,
required: true
},
location: {
type: String,
required: true
},
verifyString: {
type: String
},
verified: {
type: Boolean,
default: false
},
password: {
type: String,
required: true
},
createdOn: {
type: Date,
"default": Date.now
},
isAdmin: {
type: Boolean,
default: false
}
});
So the Workspace userId is the ObjectID from the User document.
When Im logged in as an adminstrator, I want to get all workspaces, as well as the email of the user that owns the workspace.
What Im doing is getting very messy:
Workspace.find({}).exec.then(function(workspaceObects){
var userPromise = workspaceObects.map(function(workspaceObect){
// get the user model with workspaceObect.userId here
});
// somehow combine workspaceObjects and users
});
The above doesnt work and gets extremely messy. Basically I have to loop through the workspaceObjects and go retrieve the user object from the workspace userId. But because its all promises and it becomes very complex and easy to make a mistake.
Is there a much simpler way to do this? In SQL it would require one simple join. Is my schema wrong? Can I get all workspaces and their user owners email in one Mongoose query?

var Workspace = new mongoose.Schema({
userId: {
type: String,
required: true,
ref: 'User' //add this to your schema
}
});
Workspace.find().populate('userId').exec( (err, res) => {
//you will have res with all user fields
});
http://mongoosejs.com/docs/populate.html

Mongo don't have joins but mongoose provides a very powerfull tool to help you with you have to change the model a little bit and use populate:
Mongoose population
You have to make a few changes to your models and get the info of the user model inside your workspace model.
Hope it helps

Related

how reuse a like model in nodejs

I have 2 models named "posts" and "status" and want to implement likes in them. first of all, I want the like to be able to record data like timestamps and other stuff depending on how it grows, which is why I made "like" be a model of its own.
The issue is since "posts" and "status" two models of their own are going to have "like" functionality.
Is there a way I could reuse the "like" model, instead of creating a separate "like" model for "posts" and "status", or how would you personally implement something like this?
Below is the post model
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true,
trim: true
},
description: {
type: String,
required: true,
trim: true
},
owner: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User'
}
}, {
timestamps: true
})
below is the status model
const statusSchema = new mongoose.Schema({
Body: {
type: String,
required: true,
trim: true
},
owner: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User'
},
tags: [{
type: mongoose.Schema.Types.ObjectId,
required: True,
ref: 'User'
}]
}, {
timestamps: true
})
here is the like model which I would like users to be able to like both posts by users and statuses, while still able to retain information like the time it was liked and other information depending on the growth and need
const likeSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User'
},
likedObject: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'Posts'
}
}, {
timestamps: true
})
Is there a way I could reuse the "like" model, instead of creating a separate "like" model for "posts" and "status" to capture the users and the time that they liked other user's statuses and posts?
Making Like Model was over complicating the entire thing so you could have something like this in your postSchema.
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true,
trim: true
},
description: {
type: String,
required: true,
trim: true
},
owner: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User'
},
timeStamps:true,
//Adding the like property in each post but setting it to a default of 0
likes:{type:Number, default:0}
})
Now the key is identifying the post when like button is smashed
So what you could do on the client side is making sure that when like button is clicked you have an ID of the post to the function that your calling and, then pass that ID back to the server and you can have a logic like this below on your server and you would be able to add like functionality..
Server logic for adding like functionality
router.post("/api/likes/:id", async (request, response) => {
const post_id = request.params.id;
const post = await postModel.findOne({ _id: post_id });
post.likes += 1;
const updateDocument = await postModel.findOneAndUpdate(
{ _id: post_id },
post,
{
new: true,
}
);
return response.status(201).json({ msg: "Liked post" });
});
So the idea is two always be updating that specific document

How to query and get documents from two collections on mongoose

I need to query documents from two collections together on mongoose.
I am familiar with SQL query and but not familiar with mongoDB.
I have two schema for Users, Messages like following.
Users
const UserSchema = new mongoose.Schema({
name: String,
email: {type: String, unique: true},
password: String,
avatar: {type: String, default: ""},
created_at: { type: Date, default: Date.now() }
});
module.exports = mongoose.model('User', UserSchema);
Messages
const MessageSchema = new mongoose.Schema({
message: { type: String, default: "" },
from: { type: String, default: "" },
to: { type: String: default: "" },
is_read: { type: Boolean, default: false },
channel: { type: String, default: ''},
created_at: { type: Date, required: true, default: Date.now }
});
module.exports = mongoose.model('Message', MessageSchema);
I need to get messages with "is_read" is "false".
I want to get "user name" and "avatar" together.
The "from" value of message should be matched with "_id" of User.
I think this post sums it up well: Mongoose - query to get data from multiple collections
Specifically the second upvoted answer mentions similarities between sql and mongodb, and goes on to explain how to link collections in mongoose queries.

How to create MongoDB schema design while dealing with single User account and multiple user (Organization account with roles)

I am working on a Nodejs Express API project using mongoDB with mongoose and i would like to get some advice on best practices and going about creating an efficient schema design from community
The app deals with two type of user accounts
Account type :
Single (default)
Organization (can switch to from settings)
Note:
In organisation account there will be a admin (owner) and other invited user and each user is assigned permission level / access level .One user will always be associated with only one account, ie he cannot be invited again to another account or start a new account if he is already part of a existing account. Also billing and shipping address is specific to account rather than user in the case of an organization account (address of user switching to organization account will be the address of Organization account )
I have completed the authentication part with the help of passport.js JWT and local strategy
i tried to develop one similar to RDBMS approach ( i used to be RDBMS guy ) and failed
Models and schemas
const userSchema = new Schema({
first_name: String,
last_name: String,
email: String,
phone: String,
avatar: String,
password: String,
active: Boolean
});
const User = mongoose.model('user', userSchema);
const accountSchema = mongoose.Schema({
account_type: { type: String, enum: ['single', 'organization'], default: 'single' },
organization: { type: Schema.Types.ObjectId, ref: 'organization', required: false },
billing_address: String,
shipping_address: String,
});
const Account = mongoose.model('account', accountSchema);
const accountUserRoleSchema = mongoose.Schema({
user : { type: Schema.Types.ObjectId, ref: 'user', },
role: { type: String, enum: ['admin', 'user'], default: 'user' },
account: { type: Schema.Types.ObjectId, ref: 'account', required: true }
});
const AccountUserRole = mongoose.model('accountUserRole', accountUserRoleSchema);
const permissionSchema = mongoose.Schema({
user : { type: Schema.Types.ObjectId, ref: 'user', required: true },
type: { type: Schema.Types.ObjectId, ref: 'permissionType', required: true },
read: { type: Boolean, default: false, required: true },
write: { type: Boolean, default: false, required: true },
delete: { type: Boolean, default: false, required: true },
accountUser : { type: Schema.Types.ObjectId, ref: 'account',required: true }
});
const Permission = mongoose.model('permission', permissionSchema);
const permissionTypeSchema = mongoose.Schema({
name : { type: String, required: true }
});
const PermissionType = mongoose.model('permissionType', permissionTypeSchema);
const organizationSchema = mongoose.Schema({
account : { type: Schema.Types.ObjectId, ref: 'account', },
name: { type: String, required: true },
logo: { type: String, required: true }
});
const Organization = mongoose.model('organization', organizationSchema);
Now i am developing Authorisation part where the user need to be restricted access to the resource by checking the permission he or she is assigned with .
The solution i found was to develop a Authorisation middleware which run after the authentication middleware which check for the access permissions assigned
But the problem appeared while i tried to access account data based on the user currently logged in , as i will have to search document based on the objectId reference . And i could understand that this could happen if i continue with my current design .This works fine but searching document using objectId reference seems not be a good idea
Authorization middleware
module.exports = {
checkAccess : (permission_type,action) => {
return async (req, res, next) => {
// check if the user object is in the request after verifying jwt
if(req.user){
// find the accountUserRole with the user data from the req after passort jwt auth
const accountUser = await AccountUserRole.findOne({ user :new ObjectId( req.user._id) }).populate('account');
if(accountUser)
{
// find the account and check the type
if(accountUser.account)
{
if(accountUser.account.type === 'single')
{
// if account is single grant access
return next();
}
else if(accountUser.account.type === 'organization'){
// find the user permission
// check permission with permission type and see if action is true
// if true move to next middileware else throw access denied error
}
}
}
}
}
}
}
I decided to scrap my current schema as i understand that forcing RDBMS approach on NoSQL is a bad idea.
Unlike relational databases, with MongoDB the best schema design depends a lot on how you're going to be accessing the data. What will you be using the Account data for, and how will you be accessing it
My new redesigned schema and models
const userSchema = new Schema({
first_name: String,
last_name: String,
email: String,
phone: String,
avatar: String,
password: String,
active: Boolean
account : { type: Schema.Types.ObjectId, ref: 'account', },
role: { type: String, enum: ['admin', 'user'], default: 'user' },
permssion: [
{
type: { type: Schema.Types.ObjectId, ref: 'permissionType', required: true },
read: { type: Boolean, default: false, required: true },
write: { type: Boolean, default: false, required: true },
delete: { type: Boolean, default: false, required: true },
}
]
});
const User = mongoose.model('user', userSchema);
const accountSchema = mongoose.Schema({
account_type: { type: String, enum: ['single', 'organization'], default: 'single' },
organization: {
name: { type: String, required: true },
logo: { type: String, required: true }
},
billing_address: String,
shipping_address: String,
});
const Account = mongoose.model('account', accountSchema);
const permissionTypeSchema = mongoose.Schema({
name : { type: String, required: true }
});
const PermissionType = mongoose.model('permissionType', permissionTypeSchema);
Still i am not sure if this is the right way to do it , please help me with you suggestions.
you can merge user and user account schema :
added some more fileds which is useful to you .
const userSchema = new Schema({
first_name: { type: String,default:'',required:true},
last_name: { type: String,default:'',required:true},
email: { type: String,unique:true,required:true,index: true},
email_verified :{type: Boolean,default:false},
email_verify_token:{type: String,default:null},
phone: { type: String,default:''},
phone_verified :{type: Boolean,default:false},
phone_otp_number:{type:Number,default:null},
phone_otp_expired_at:{ type: Date,default:null},
avatar: { type: String,default:''},
password: { type: String,required:true},
password_reset_token:{type: String,default:null},
reset_token_expired_at: { type: Date,default:null},
active: { type: Boolean,default:true}
account_type: { type: String, enum: ['single', 'organization'], default: 'single' },
organization: {type:Schema.Types.Mixed,default:{}},
billing_address: { type: String,default:''}
shipping_address: { type: String,default:''}
role: { type: String, enum: ['admin', 'user'], default: 'user' },
permission: [
{
type: { type: Schema.Types.ObjectId, ref: 'permissionType', required: true },
read: { type: Boolean, default: false, required: true },
write: { type: Boolean, default: false, required: true },
delete: { type: Boolean, default: false, required: true },
}
],
created_at: { type: Date, default: Date.now },
updated_at: { type: Date, default: Date.now }
});
in your middleware :
module.exports = {
checkAccess : (permission_type,action) => {
return async (req, res, next) => {
// check if the user object is in the request after verifying jwt
if(req.user){
if(req.user.account_type === 'single')
{
// if account is single grant access
return next();
}
else{
// find the user permission
// check permission with permission type and see if action is true
// if true move to next middileware else throw access denied error
}
}
}
}
};
I would suggest:
1 - Define your permission levels, for example: If the user is assigned to a specific Role / Permission level, what features/options he can access.
2 - Permission levels should be recognized by Number (1 = Admin, 2 = User) etc and that key should be indexed in MongoDB (You can use and rely on the ObjectID as well).
3 - Your user object/schema should only have a permission key with the type of Number in Mongoose - no need to create a separate schema for this.
const userSchema = new Schema({
first_name: String,
last_name: String,
email: String,
phone: String,
avatar: String,
password: String,
active: Boolean
account : { type: Schema.Types.ObjectId, ref: 'account', },
permssion: {type: Number, required: true, default: 2} // Default's User
});
With this approach, you can modify your auth check middleware to just check if the permission level sent by the client is identified by the DB and if it does, give the user access else throw access denied error.
If you want you can add another field with permission type and return the name of the permission as well but I think you should handle it on the client, not on the server / be.
I partially understood the requirements (Bad at reading too many words) so I have left anything untouched, let me know.

Dont know how to populate Mongoose Query

I have a problem with a mongoose population and I don't know what I should do.
I got two schemas:
var userSchema = new userSchema({
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
mods: [{ type: mongoose.Schema.Types.ObjectId, ref: 'users'}]
});
var dataSchema = mongoose.Schema({
title: { type: String, required: true, unique: true },
description: { type: String, required: true },
owner: {type: mongoose.Schema.Types.ObjectId, required: true}
});
So one user can have several data packages.
Some users are moderated by other users.
Whats the query for a moderator, that all his own data packages and the ones of the users he is moderating are listed?
You see that I have a SQL background and there's definitely another way to do it with MongoDB.
Thanks for your help!
I'm not clear understand what queries do you need but first you need set ref property in 'owner' field in dataSchema. As about population it's look like this:
//if you use callback
users.find({/*your query*/}).populate('mods')
.exec((err, result)=>{/*your code*/});
//if you use promise
users.find({/*your query*/}).populate('mods').exec()
.then(result=>{/*your code*/})
.catch(err=>{throw err});

Tinder like application MongoDB database schema

I am developing an app like Tinder to experiment with MongoDB.
I am wondering about the database schema.
The main idea is that a user can "like" many users but no matter how much the number of "liked" profiles grows, it is very unlikely to hit the 16MB document size ceiling, so in my design, "liked" profiles are embedded inside one's profile.
below is a sample of my users schema using mongoose
var UserSchema = mongoose.Schema({
fullName: {
type: String,
trim: true
},
phone: {
type: String,
trim: true,
required: true,
},
gender: {
type: String,
enum: ['male', 'female'],
},
age: {
type: Number,
required: true
},
favorites: []
});
On the other hand, a user might be "disliked" by my many users.
So a user should not see on his next profile search the profiles of users who "disliked" him, so in my design I created a collection that holds the ID of the user who "disliked" and the ID of the user being "disliked".
below is a sample of my blocked schema using mongoose
var BlockedSchema = mongoose.Schema({
BlockerUserId: {
type: String,
required: true
},
BlockedUserId: {
type: String,
required: true
}
});
Do you think this is a good approach? and which indexes needs to be created?
Best,
You can manage dislike in the user collection only, you don't need a new collection.
var UserSchema = mongoose.Schema({
fullName: {
type: String,
trim: true
},
phone: {
type: String,
trim: true,
required: true,
},
gender: {
type: String,
enum: ['male', 'female'],
},
age: {
type: Number,
required: true
},
favorites: [],
dislike[]
});
and search like
var current_user_id = userdata._id;
db.users.find({dislike:{$ne:current_user_id}})
The above code is not syntactically correct but it will give you an idea.

Resources