Node get the first element in array of object - javascript

I'm trying to do some relations between my schemas and I have some problems with my solution.
user schema:
let userSchema = new mongoose.Schema({
username: { type:String, default:null },
gender: { type:String, default:null },
role: { type: mongoose.Schema.Types.ObjectId, ref: 'Role', required: true },
});
role schema:
let roleSchema = new mongoose.Schema({
name: { type:String, default:"Player" },
privileges:
[
{
resource: String ,
actions: [ String ]
},
],
});
and my query is
User.find({ "email":email }).populate({path: 'role', model: 'Role'}).limit(1).exec().
then(results => {
if (results.length) {
user = results[0];
console.log(user.role.privileges);
}
else {
throw new APIError("login_failed");
}
})
I need to access to the first element of privileges and Thank you.

The first parameter returned by Mongoose (and Node in general) is not your data. It's the error (if any). You need to write then( (error, results) => {
I need to access to the first element of privileges
privileges being an array, simply access the first element using :
user.role.privileges[0]
As an aside, you can also use Mongoose's .findOne() method, instead of .find().limit(1) :
User
.findOne({ email }) // Shorthand for { email : email }
.populate({path: 'role', model: 'Role'})
.exec()
.then( (error, user) => {
if(error || !user) throw new APIError("login_failed");
console.log(user.role.privileges[0]);
})

Related

Find and update double nested referenced document

I have 3 models, which are referenced with one another, I want to search for the first document based on a query (this will always exist and return a result), once that returns to search for the second one which is referenced to the first one (which may or may not exist), and push into the 3rd one which is referenced in the 2nd one.
User (Parent Model).
Location (Child of User)
Reports (Child of Location)
I want something similar to the following:
Search User (findById), search for Location using query (search for locationName) - if it exists update it and also push into Reports, else if Location doesn't exist, create one and push into Reports.
Parent Model:
const User = mongoose.model(
"User",
new mongoose.Schema({
firstName: String,
lastName: String,
dateOfBirth: Date,
email: String,
password: String,
verified: Boolean,
locations: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Locations"
}
]
})
);
Child Model:
const Locations = mongoose.model(
"Locations",
new mongoose.Schema({
address: String,
adress2: String,
city: String,
state: String,
country: String,
zip: String
reports: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Reports"
}
]
})
);
Child of Child Model:
const Reports = mongoose.model(
"Reports",
new mongoose.Schema({
severity: String,
note: String,
date: Date,
})
);
Any help is greatly appreciated!
I was able to figure this out, by first setting the report values, setting the location values, saving the report, adding the report number to the location value, then searching for a location based on location ID using fineOneAndUpdate:
Location.findOneAndUpdate(
{ _id: req.body.locationID},
{
$push: {
reports: report
}
},
(err, result) => {
if (err) {
res.status(500).send({ message: err });
return;
};
if (!result) {
location.save((err, done) => {
if (err) {
res.status(500).send({ message: err });
return;
};
User.findByIdAndUpdate(
{ _id: req.body.id },
{
$push: {
locations: done
}
},
(err, doc) => {
if (err) {
res.status(500).send({ message: err });
return;
};
res.status(200).send({ message: 'Successfully added new location with report.' });
return;
})
})
}
if (result) {
res.status(200).send({ message: 'Successfully updated existing location with report.' });
}
}
);

How to insert data into MongoDB collection?

I'm using NodeJS with Mongoose. I've two tables into db.js:
const mongoose = require('mongoose')
const UserSchema = new mongoose.Schema(
{
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true }
},
{ collection: 'users' }
)
const model = mongoose.model('UserSchema', UserSchema)
const AccountSchema = new mongoose.Schema(
{
username: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'hahaha' },
balance: { type: Number, required: true }
},
{ collection: 'accounts' }
)
module.exports.UserSchema = model
module.exports.AccountSchema = model
As you can see the first collection contains users (username, email, password). The second collection represents a bank account, related to a specific user. So, it has two fields: user (foreign key) and balance ($100, i.e.). First question: is my code correct to accomplish this task?
Second question: how can I insert data into the AccountSchema?
I obviously want to insert data only if the user exists into Userschema. I think that this doesn't work:
const Schema = require('./model/db')
app.post('/api/addaccount', async (req, res) => {
const { username, balance } = req.body
try {
const response = await Schema.AccountSchema.create({
username,
balance
})
console.log('User account successfully: ', response)
res.json({status : "ok"})
} catch (error) {
throw error
}
})
How can I do this?
This won't work. You've to query the User model first to check if any user exists with this username. If yes, you'll continue to store data in the Account model, if not you'll return a response something like user doesn't exist

problems to update mongodb collection

so I have two different collections for my social media app. One for the users and the other one for the user's posts. Whenever I'm updating the info from one of my user's collection it should also modify it on the post (since my post collection includes data from the user too), but it's only doing it on the posts that I create after that and not on the ones that I've been creating before. How can I fix it?
USER SCHEMA
const userSchema = new Schema({
name: { type: String, required: true },
lastname: { type: String, required: true },
username: { type: String, required: true },
email: { type: String, required: true },
password: { type: String, required: true, minlength: 8 },
avatar: { data: Buffer, contentType: String },
});
POST SCHEMA
const postSchema = new Schema({
user: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
name: { type: String, required: true },
lastname: { type: String },
username: { type: String },
avatar: { data: Buffer, contentType: String },
date: { type: Date, default: Date.now() },
textOfThePost: { type: String, required: true },
});
EDIT FUNCTION EXPRESS/MONGOOSE
router.put("/edit_profile", async (req, res) => {
try {
const { name, lastname, username } = req.body;
const user = await User.findById(req.user).select("-password");
if (!user) return res.status(404).json("User doesn't exists");
if (name) user.name = name;
if (lastname) user.lastname = lastname;
if (username) user.username = username;
await user.save();
res.json(user);
} catch (err) {
res.status(500).json({ error: err.message });
}
};
You can use updateMany() for that purpose.
const res = await Post.updateMany({ user: user.id }, { name: user.name, username: user.username /* ... */ });
However as already pointed out you are storing user data redundant on the post model as well as on the user model which is not necessary. Similar to joins in SQL you can simply use populate() and not store any user-related data on your post model. This way everytime you query your posts it will automatically pull the latest matching user model by its id.
myPost.populate('user')
Note that therefore the ref is required, which tells mongoose how to populate the user field.

Mongoose & Express: How to properly Remove, Create & Store data that are reference

The first problem I am having is that whenever I try to delete the Comment, I also try to find the index of that specific comment inside post.comments as well as inside user.comments, it consistently returns -1, the reason why I am trying to find it, is so that I can splice it from the comments array that user and post do have.
The second problem I am having is that whenever I create a comment, I try to store it in the comments array that user and post have, but it stores it only as a string, although I think it is supposed to be stored as an object right?, So I can access it later by populating?
I have been struggling for days now being very frustrated why it does not work. Please help me!
Below will be my two routes, for deleting and creating comments, and my Schemas, Thank You for all the help!
Creating Comments
// #route POST api/posts/comment/:id
// #desc Comment on a post
// #access Private
router.post(
'/comment/:id',
[
auth,
[
check('text', 'Text is required')
.not()
.isEmpty()
]
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
try {
const post = await Post.findById(req.params.id);
const user = await User.findById(req.user.id)
const newComment = {
text: req.body.text,
post: post._id,
user: req.user.id
};
const comment = new Comment(newComment);
post.comments.unshift(comment._id);
user.comments.unshift(comment._id)
console.log(user.comments);
console.log(post.comments);
console.log(comment)
await post.save();
await comment.save();
await user.save();
res.json(comment);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
}
);
Deleting comments
// #route DELETE api/posts/comment/:id/:comment_id
// #desc Delete comment
// #access Private
router.delete('/comment/:id/:comment_id', auth, async (req, res) => {
try {
const post = await Post.findById(req.params.id);
const user = await User.findById(req.user.id);
// Pull out comment by finding it through its id
const comment = await Comment.findById(req.params.comment_id);
// Make sure comment exists
if (!comment) {
return res.status(404).json({ msg: 'Post do not have this comment' });
}
// Check user
if (comment.user.toString() !== req.user.id) {
return res.status(401).json({ msg: 'User not authorized' });
}
// Get The value to be removed
const postCommentIndex = post.comments.findIndex(postComment => postComment === comment._id);
const userCommentIndex = user.comments.findIndex(userComment => userComment === comment._id);
console.log(`This is the post comment index ${postCommentIndex}`);
console.log(`This is the user comment index ${userCommentIndex}`);
post.comments.splice(postCommentIndex, 1);
user.comments.splice(userCommentIndex, 1);
// save user and post
await post.save();
await user.save();
await comment.remove();
// resend the comments that belongs to that post
res.json(post.comments);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
Schemas:
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
avatar: {
type: String
},
posts: [{type: mongoose.Schema.Types.ObjectId, ref: "Post"}],
comments: [{type: mongoose.Schema.Types.ObjectId, ref: "Comment"}],
date: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('User', UserSchema);
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const PostSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
text: {
type: String,
required: true
},
likes: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'User'
}
}
],
dislikes: [
{
user: {
type: Schema.Types.ObjectId,
ref: "User"
}
}
],
comments: [{type: Schema.Types.ObjectId, ref: "Comment"}],
date: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Post', PostSchema);
const mongoose = require("mongoose")
const Schema = mongoose.Schema;
const CommentSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
post: {
type: Schema.Types.ObjectId,
ref: "Post"
},
text: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
})
module.exports = mongoose.model("Comment", CommentSchema);
I think you need to redesign your schemas in a simpler way, there are too many references between the models, and this causes issues, for example you have 5 db access when you want to create a comment, and 6 db access when you want to delete a comment.
I would create the user schema like this removing the posts and comment references, but later when we want to access the posts from users, I set up virtual populate.
const UserSchema = new Schema(
{
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
avatar: {
type: String
},
date: {
type: Date,
default: Date.now
}
},
{
toJSON: { virtuals: true }
}
);
UserSchema.virtual("posts", {
ref: "Post",
localField: "_id",
foreignField: "user"
});
And in the posts schema, I removed the comments references.
(For simplicity I removed likes and dislikes fields.)
const PostSchema = new Schema(
{
user: {
type: Schema.Types.ObjectId,
ref: "User"
},
text: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
},
{
toJSON: { virtuals: true }
}
);
PostSchema.virtual("comments", {
ref: "Comment",
localField: "_id",
foreignField: "post"
});
Comment schema can stay as it is.
Now to add a comment to a post, we only need 2 db access, one for checking if post exists, and one for creating the post.
router.post(
"/comment/:id",
[
auth,
[
check("text", "Text is required")
.not()
.isEmpty()
]
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
try {
const post = await Post.findById(req.params.id);
if (!post) {
return res.status(404).json({ msg: "Post not found" });
}
let comment = new Comment({
text: req.body.text,
post: req.params.id,
user: req.user.id
});
comment = await comment.save();
res.json(comment);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
}
);
Let's say we have these 2 users:
{
"_id" : ObjectId("5e216d74e7138b638cac040d"),
"name" : "user1"
}
{
"_id" : ObjectId("5e217192d204a26834d013e8"),
"name" : "user2"
}
User1 with _id:"5e216d74e7138b638cac040d" has this post.
{
"_id": "5e2170e7d204a26834d013e6",
"user": "5e216d74e7138b638cac040d",
"text": "Post 1",
"date": "2020-01-17T08:31:35.699Z",
"__v": 0,
"id": "5e2170e7d204a26834d013e6"
}
Let's say user2 with _id:"5e217192d204a26834d013e8" commented on this post two times like this:
{
"_id" : ObjectId("5e2172a4957c02689c9840d6"),
"text" : "User2 commented on user1 post1",
"post" : ObjectId("5e2170e7d204a26834d013e6"),
"user" : ObjectId("5e217192d204a26834d013e8"),
"date" : ISODate("2020-01-17T11:39:00.396+03:00"),
"__v" : 0
},
{
"_id": "5e21730d468bbb7ce8060ace",
"text": "User2 commented again on user1 post1",
"post": "5e2170e7d204a26834d013e6",
"user": "5e217192d204a26834d013e8",
"date": "2020-01-17T08:40:45.997Z",
"__v": 0
}
To remove a comment we can use the following route, as you see we decreased the db access from 6 to 3, and code is shorter and cleaner.
router.delete("/comment/:id/:comment_id", auth, async (req, res) => {
try {
const comment = await Comment.findById(req.params.comment_id);
if (!comment) {
return res.status(404).json({ msg: "Post do not have this comment" });
}
if (comment.user.toString() !== req.user.id) {
return res.status(401).json({ msg: "User not authorized" });
}
await comment.remove();
// resend the comments that belongs to that post
const postComments = await Comment.find({ post: req.params.id });
res.json(postComments);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});
Now you may ask, how will access the posts from an user? Since we setup virtual populate in our user schema, we can populate the posts like this:
router.get("/users/:id/posts", async (req, res) => {
const result = await User.findById(req.params.id).populate("posts");
res.send(result);
});
You can try this code snipet :
Comment.deleteOne({
_id: comment.id
}, function (err) {
if (err) {
console.log(err);
return res.send(err.message);
}
res.send('success');
});

Populate Query Options with Async Waterfall

I'm trying mongoose populate query options but i don't know why the query options doesn't work.
I have user schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const UserSchema = new Schema(
{
username: { type: String, required: true },
email: { type: String },
name: { type: String },
address: { type: String }
},
{ timestamps: true }
);
module.exports = mongoose.model('User', UserSchema);
and feed schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const FeedSchema = new Schema(
{
user: { type: Schema.ObjectId, ref: 'User' },
notes: { type: String, required: true },
trx_date: { type: Date },
status: { type: Boolean, Default: true }
},
{ timestamps: true }
);
FeedSchema.set('toObject', { getters: true });
module.exports = mongoose.model('Feed', FeedSchema);
I want to find all feed by user id, i used async waterfall like the following code:
async.waterfall([
function(callback) {
User
.findOne({ 'username': username })
.exec((err, result) => {
if (result) {
callback(null, result);
} else {
callback(err);
}
});
},
function(userid, callback) {
// find user's feed
Feed
.find({})
// .populate('user', {_id: userid._id}) <== this one also doesn't work
.populate({
path: 'user',
match: { '_id': { $in: userid._id } }
})
.exec(callback);
}
], function(err, docs) {
if (err) {
return next(err);
}
console.log(docs);
});
With above code, i got all feeds, and it seems like the query option do not work at all, did i doing it wrong ?
Any help would be appreciate.
Not sure why you are looking to match "after" population when the value of _id is what is already stored in the "user" property "before" you even populate.
As such it's really just a simple "query" condition to .find() instead:
async.waterfall([
(callback) =>
User.findOne({ 'username': username }).exec(callback),
(user, callback) => {
if (!user) callback(new Error('not found')); // throw here if not found
// find user's feed
Feed
.find({ user: user._id })
.populate('user')
.exec(callback);
}
], function(err, docs) {
if (err) {
return next(err);
}
console.log(docs);
});
Keeping in mind of course that the .findOne() is returning the whole document, so you just want the _id property in the new query. Also note that the "juggling" in the initial waterfall function is not necessary. If there is an error then it will "fast fail" to the end callback, or otherwise pass through the result where it is not. Delate "not found" to the next method instead.
Of course this really is not necessary since "Promises" have been around for some time and you really should be using them:
User.findOne({ "username": username })
.then( user => Feed.find({ "user": user._id }).populate('user') )
.then( feeds => /* do something */ )
.catch(err => /* do something with any error */)
Or indeed using $lookup where you MongoDB supports it:
User.aggregate([
{ "$match": { "username": username } },
{ "$lookup": {
"from": Feed.collection.name,
"localField": "_id",
"foreignField": "user",
"as": "feeds"
}}
]).then( user => /* User with feeds in array /* )
Which is a bit different in output, and you could actually change it to look the same with a bit of manipulation, but this should give you the general idea.
Importantly is generally better to let the server do the join rather than issue multiple requests, which increases latency at the very least.

Categories

Resources