so I'm trying to create a party with creator field with id of a user, and at the same time adding a party id to users parties using mongoose sessions. Here's the code of a request:
const createParty = async (req, res, next) => {
const {title, description, address, creator} = req.body;
const createdParty = new Party({
title,
description,
image: 'https://media-cdn.tripadvisor.com/media/photo-s/14/03/b3/4e/tlv.jpg',
address,
creator,
savedBy: []
});
let user;
try {
user = await User.findById(creator);
} catch (err) {
let error = new HttpError('Fetching user failed', 500);
return next(error);
}
if (!user) {
return next(new HttpError('Could not find user for providen id', 404));
}
try {
const sess = await mongoose.startSession();
sess.startTransaction();
await createdParty.save({ session: sess });
user.parties.push(createdParty);
console.log(user);
await user.save({ session: sess });
await sess.commitTransaction();
} catch (err) {
let error = new HttpError('Creating party failed', 500);
return next(error);
}
res.status(201).json({party: createdParty});
};
And my user and parties schemas:
const userSchema = new Schema({
username: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true, minlength: 6 },
image: { type: String, required: true },
parties: [{ type: mongoose.Types.ObjectId, required: true, ref: 'Party' }],
savedParties: [{ type: mongoose.Types.ObjectId, required: true, ref: 'Party' }]
});
const partySchema = new Schema({
title: { type: String, required: true },
description: { type: String, required: true },
image: { type: String, required: true },
address: { type: String, required: true },
creator: { type: mongoose.Types.ObjectId, required: true, ref: 'User' },
savedBy: [{ type: mongoose.Types.ObjectId, required: true, ref: 'User' }]
});
The problem is I can't save a user with new party id, only this line fails:
await user.save({ session: sess });. Tried to move this line to a separate try/catch, tried to add user.markModified('parties'); didn't help. Please help those who may know the solution.🙏🏻
UPDATE ON THE PROBLEM
So I did some testing and found out that if I delete everything from the database, and I'll create a user I will be able to add parties, and it'll work as it should. But if I'll create another user and afterward will try to add a party to one of the users it won't work.
when you session it won't create the collection if it doesn't exist and you need to do it manually in the data
I am trying to do a check to see if a logged-in user's id req.user.id. is in an array of followers of the user being checked in req.params.id, bit for some reason it doesn't work.
router.get('/api/:id/isfollowing', auth, async (req, res) => {
if (req.params.id==req.user._id) {
return res.status(200).send({ "isfollowing": "Myself" })
}
try {
const followers = await Follow.find({
user: req.params.id
})
let followersArr = followers.map(follower=>{
return follower.followedBy
})
const yes = followersArr.includes(req.user._id)
// const yes = followersArr.filter((objId) => objId==req.user._id)
console.log(yes, followersArr, req.user._id)
if (yes===false) {
return res.status(200).send({ "isfollowing": false })
}
return res.status(200).send({ "isfollowing": true })
} catch (e) {
res.status(500).send()
}
})
for some reason the check doesn't work and even when using the filter, it still returns nothing. But when I console.log the values, it is right there.
[] [ 5fa4f0af4a7bf5471c41e225, 5f9dc1777a695570e878424d ] 5f9dc1777a695570e878424d
EDIT
schemas below
User schema
const userSchema = new mongoose.Schema({
fullname: {
type: String,
required: true,
trim: true,
lowercase: true
},
username: {
type: String,
unique: true,
required: true,
trim: true,
lowercase: true
},
email: {
type: String,
unique: true,
required: true,
trim: true,
lowercase: true,
validate(value) {
if (!validator.isEmail(value)) {
throw new Error('Email is invalid')
}
}
},
password: {
type: String,
required: true,
minlength: 7,
trim: true,
validate(value) {
if (value.toLowerCase().includes('password')) {
throw new Error('Passwoed cannot contain "password"')
}
}
}
})
follow schema
const followSchema = new mongoose.Schema({
// the logged in user who will be trying to follow someone will be added to "followedBy"
// the user who is getting followed will be added to "user"
user: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User'
},
followedBy: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'Showcase'
}
}, {
timestamps: true
})
I gave follow its own schema so I can record other info like time and other info whenever a user follows another.
If I've understand well you only need a simple query.
Since you only want to know if the id is into an array, you can check that directly with mongo. You don't need load every document into memory and use JS functions like filter or something similar.
You only need a query similar to this:
db.collection.find({
"user": ObjectId("user_id"),
"followedBy": ObjectId("follower_id")
})
This will return a document that match both values.
Check here it works and tell me if is the behaviour and output you expect.
Also I will code a mongoose query and I'll update the answer.
You can use also this query in mongoose to get how many documents find the query:
var find = await model.find({"user":mongoose.Types.ObjectId(user_id),"followedBy":mongoose.Types.ObjectId(follower_id)}).countDocuments()
Includes cannot be used in this case since you are trying to find ObjectId in an array.
To find if req.user._id is present in followersArr, use Array.some() function as below
const yes = followersArr.some(followerId=>{followerId.equals(req.user._id)})
The some call will iterate over the followersArr array, calling equals on each one to see if it matches req.user._id and stop as soon as it finds a match. If it finds a match it returns true, otherwise false.
You can't use something simpler like indexOf because you want to compare the ObjectIDs by value, not by reference.
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.
I 've a UserSchema that looks like:
export var UserSchema: Schema = new mongoose.Schema({
createdAt: Date,
email: {
type: String,
required: true,
trim: true,
unique: false,
},
firstName: {
type: String,
required: false,
trim: true
},
lastName: {
type: String,
required: false,
trim: true
},
password: {
type: String,
trim: true,
minlength: 6
},
tokens: [{
access: {
type: String,
required: true
},
token: {
type: String,
required: true
}
}]
});
And I 've a instance method like:
UserSchema.methods.printThis = () => {
var user = this;
console.log("========>>> PRINTING USER NOW");
console.log(user);
};
The method printThis is being called from
router.post('/signup', (req, res) => {
var body = _.pick(req.body, ['email', 'password']);
var user = new User(body);
console.log("created user as: ", user);
user.printThis();
});
Below is the output:
created user as: { email: 'prsabodh.r#gmail.com',
password: '123456',
_id: 59be50683606a91647b7a738,
tokens: [] }
========>>> PRINTING USER NOW
{}
You can see that the user is getting created properly. However, when I call printThis method on User - I'm not able to print the same user back and an empty {} is printed. How to fix this?
You shouldn't use arrow functions (=>) if the calling function is explicitly setting a context (which is what Mongoose does):
UserSchema.methods.printThis = function() {
var user = this;
console.log("========>>> PRINTING USER NOW");
console.log(user);
};
More info on arrow functions and their handling of this here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#Arrow_functions
To get the _id value from the instance method can use _conditions that should work
UserSchema.methods.printThis = function(password) {
var user = this;
console.log(user._conditions['_id']);
};
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