How can I seed objects with relational fields in Node.js - node.js

Complete Node.js beginner with strong Django background here, I am building a small platform to listen music online (MEAN stack).
I have a hard time using seeds for testing purposes, here is my attempt so far:
var TrackSchema = new Schema({
title: String,
user: { type: Schema.ObjectId, ref: 'User' }
});
Track.find({})
.remove(function () {
Track.create({
title: 'Foo',
user: User.findOne({ email: 'admin#admin.com' })._id
}, {
title: 'Bar',
user: User.findOne({ email: 'test#ŧest.com' })._id
}, function () {
console.log('Finished populating tracks');
});
});
I can't find a way to link a Track to a User. Tracks are correctly served at api/tracks but if I uncomment the line where I set a user for the track, api/tracks then return an empty list (no error message provided).

Are you using mongoose as your database wrapper? If so, it doesn't support synchronous queries.
You will have to return your User in a callback function, then have your Track.create method inside it.
User.findOne({ email: 'admin#admin.com' }, function(error, user) {
Track
.create({
title: 'Foo',
user: user._id
});
});

Related

(Mongoose) How to get user._id from session in order to POST data including this user._id

I am new in Mongoose.
I'm developing a MEAN stack To do list with user authentification.
(In other words, a user can register login and create, get, update and delete the to do's).
It means 2 schemas: 'users' and 'tasks'
With a relationship one to many: a user can have many tasks, many tasks belongs to a user.
This is how it looks the 'tasks' Schema:
const TaskSchema = new Schema({
title:{
type: String,
required: true
},
owner:{
type: Schema.Types.ObjectId,
ref:'User'
}
});
In order to build the CRUD methods I will need the user._id as a 'owner' attribute, otherwhise any user could have access to the tasks list, create update or delete a task,
To get the user._id it I was thinking two options:
Angular2 at the front end would get the user._id from the localStorage of the browser where was stored previously to keep the user logged in.
const user = localStorage.getItem('user');
And then send it in the same object as I send the 'title' attribute.
I think this option is too insecure as anyone from the front-end could send any id.
Get the current user._id at the back-end from the sessions. (I would't know how to do it though). And include it in the new task object at the POST method, something like this:
.post('/task', function(req, res, next){ function(req, res, next){
var task = new Task({
title: req.body.title,
owner : req.user._id /// Does not do nothing
});
if(!task.title){
res.status(400);
res.json({
"error":"Bad Data"
});
} else{
task.save(task, function(err, task){
if(err){
res.send(err);
}
res.json(task);
});
}
});
Taking the second option (unless the former is better), how would you build the POST method?
Concretely, how can I get the current user._id from the session and include it the new Task object?
I look forward of receiving your feedback soon.
Thank you.
A bit different but:
User Model:
var UserSchema = new mongoose.Schema({
username: String,
password: String
});
Tasks Model:
var taskSchema = mongoose.schema({
text: String,
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
}
});
module.exports = mongoose.model("Task", taskSchema);
Create a task with post route:
var text = req.body.text;
var author = {
id: req.user._id,
username: req.user.username
};
var newTask = {text: text, author: author};
Task.create(newTask, function(err, addedTask){
// what you wanna do
});
Similarly with edit/update you can use a put route (edit) and delete route (method override for delete) with a 'checkTaskOwnership' middleware and then
Task.findByIdAndUpdate / Task.findByIdAndRemove
I think you should store user's _id in session. To store _id in the session use passport. It handles Authentication really well, and on successful authentication it stores users credentials in req.user. This req.user is present in all the requests. So for any Route, you can get the user's _id from req.user object. you wont need to send user's _id from the Frontend.
While saving Task use this:
var task = new Task({
title: req.body.title,
owner : req.user._id
});
task.save(function(err){...});
Read PassportJS docmentation to get more detailed information about Session and Authentication.

How to update an array in Sequelize Postgres

I have a user model in Sequelize for a Postgres db:
var User = sequelize.define('User', {
fb_id: DataTypes.STRING,
access_token: DataTypes.TEXT,
first_name: DataTypes.STRING,
last_name: DataTypes.STRING,
email: DataTypes.TEXT,
profilePictureURL: DataTypes.TEXT,
library: DataTypes.ARRAY(DataTypes.STRING)
}, {
underscored: true,
classMethods: {
associate: function(models) {
}
}
});
I am trying to update the library field by adding ISBNs to the array. This is the code for my POST request:
req.user.library.push(req.body._isbn); // adding the posted ISBN to the user object in my express-session
User.findOrCreate({where: {fb_id: req.user.fb_id},
defaults: {
access_token : req.user.access_token,
first_name : req.user.first_name,
last_name : req.user.last_name,
email : req.user.email,
profilePictureURL : req.user.profilePictureURL,
library: req.user.library // new library object
}})
.spread(function (updatedUser, created){
res.status(200).json(updatedUser);
}).error(function(err){
res.status(500).json(err);
});
There is no error, but the library field is not updated after checking the updatedUser object. How do I correctly update an array field in Sequelize?
For next visitors, I may have found a better way to solve this issue :
User.update(
{library: Sequelize.fn('array_append', Sequelize.col('library'), req.body._isbn)},
{where: {fb_id: req.user.fb_id}}
);
I ran into this before and found the answer deep in their Github issues. The way I accomplished it is
User.find({
where: {
fb_id: req.user.fb_id
}
})
.then((user) => {
user.library.push(req.body._isbn)
user.update({
library: user.library
},{
where: {
fb_id: req.user.fb_id
}
})
.then(user => res.json(user))
})
It definitely feels like there is a better way, but this way how I found a way.
Kinda old issue but I found a workaround that could help in the future, from what I understand sequelize may not recognize an updated data as new instance so it will consider it as local scope only. Solution for me was to create a new object upon the needed array, something like:
let newArray = Object.assign([], instance.arrayToUpdate);
newArray.push(myInterestingData)
await instance.update({
arrayToUpdate: newArray
});

How to get around E11000 MongoError without deleting 'unique: true'

I am trying to build a forum in order to learn the MEAN stack. I ran into an issue while using mongoose...
I have this...
var UserSchema = new Schema({
id: ObjectId,
firstName: String,
lastName: String,
role: String,
email: {
type: String,
unique: true
},
password: String,
workers: [WorkerSchema]
});
var TopicSchema = new Schema({
id: ObjectId,
title: String,
moderator: UserSchema,
posts: [PostSchema]
});
var Topic = mongoose.model('Topic', TopicSchema);
app.post('/topics', requireLogin, function(req, res) {
User.findOne({"email": req.session.user.email}, function(err, user) {
if (user.role == "moderator" || user.role == "admin") {
var topic = new Topic({
title: req.body.title,
moderator: req.session.user,
posts: []
});
topic.save(function(err) {
if (err) console.log(err);
res.status(204).end();
});
}
});
});
My issue is this... When I POST a topic to /topics, it works the first time, populating the topics collection with one item. But then, when I POST to /topics again, from the same user, I get an E11000 MongoError that looks like this:
message: 'E11000 duplicate key error index: MY_MONGO_DB.topics.$moderator.email_1 dup key: { : "myuser#example.com" }'
I know that removing the 'unique: true' property from the email field of UserSchema would fix this issue, but I don't want to remove that uniqueness property since I use it elsewhere in my code to ensure that users are unique by email.
Is there any way around this? In other words, is there any way to keep the 'unique: true' property and also retain the ability of users to be able to post multiple topics without triggering the E11000 error?
What you did was to embed the user. In your database, the resulting document would look something like
{
...
moderator: {..., email: "john#example.com"}
}
Which, of course, would violate the unique constraint if you have the same person as a moderator twice.
What you should do instead is to reference the user in your schema:
var user = mongoose.model('User', UserSchema);
var TopicSchema = new Schema({
id: ObjectId,
title: String,
moderator: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
posts: [PostSchema]
});

Mongoose - trying to do 'JOINS' in MEAN stack

I am having a hard time understanding the async nature of NodeJS.
So, I have an articles object with this schema:
var ArticleSchema = new Schema({
created: {
type: Date,
default: Date.now
},
title: {
type: String,
default: '',
trim: true,
required: 'Title cannot be blank'
},
content: {
type: String,
default: '',
trim: true
},
creator: {
type: Schema.ObjectId,
ref: 'User'
}
});
and the User schema is:
var UserSchema = new Schema({
firstName: String,
lastName: String,
...
});
The problem is when I query for all the documents like so:
exports.list = function(req, res) {
// Use the model 'find' method to get a list of articles
Article.find().sort('-created').populate('creator', 'firstName lastName fullName').exec(function(err, articles) {
if (err) {
// If an error occurs send the error message
return res.status(400).send({
message: getErrorMessage(err)
});
} else {
// Send a JSON representation of the article
res.json(articles);
}
});
};
I get all the articles back successfully, but for some reasons, the article creator is returning different results
for locally authenticated users (localStrategy) and facebook authenticated users (facebook strategy) for locally authenticated users, I get:
articles = {
creator: {
id: 123,
firstName: 'Jason',
lastName: 'Dinh'
},
...
}
for fb authenticated users, I get:
articles = {
creator: {
id: 123
},
...
}
I can't seem to get a grip on PassportJS API, so what I want to do is
iterate through articles and for each article, find the user document using the article creator ID and add the user firstName and lastName to the articles object:
for each article in articles {
User.findOne({ '_id': articles[i].creator._id }, function(err, person){
//add user firstName and lastName to article
});
}
res.json(articles);
You can probably already see the problem here... my loop finishes before the documents are returned.
Now, I know that MongoDB doesn't have any 'joins' and what I want to do is essentially return a query that 'joins' two collections. I think I'm running into problems because I don't fundamentally understand the async nature of
node.
Any help?
You can use find instead of findOne and iterate inside your callback function.
User.find({ }, function(err, personList){
for each person in personList {
for each article in articles {
if (person._id === article.creator._id) {
//add user firstName and lastName to article
}
}
}
res.json(articles);
});
UPDATE:
Considering the scenario that #roco-ctz proposed (10M users), you could set a count variable and wait for it to be equal to articles.length:
var count = 0;
for each article in articles {
User.findOne({ '_id': articles[i].creator._id }, function(err, person){
//add user firstName and lastName to article
count += 1;
});
}
while (count < articles.length) {
continue;
}
res.json(articles);

Nested query with mongoose

I have three models: User, Post and Comment
var User = new Schema({
name: String,
email: String,
password: String // obviously encrypted
});
var Post = new Schema({
title: String,
author: { type: Schema.ObjectId, ref: 'User' }
});
var Comment = new Schema({
text: String,
post: { type: Schema.ObjectId, ref: 'Post' },
author: { type: Schema.ObjectId, ref: 'User' }
});
I need to get all posts in which the user has commented.
I know it should be a very simple and common use case, but right now I can't figure a way to make the query without multiple calls and manually iterating the results.
I've been thinking of adding a comments field to the Post schema (which I'd prefer to avoid) and make something like:
Post.find()
.populate({ path: 'comments', match: { author: user } })
.exec(function (err, posts) {
console.log(posts);
});
Any clues without modifying my original schemas?
Thanks
You have basically a couple of approaches to solving this.
1) Without populating. This uses promises with multiple calls. First query the Comment model for the particular user, then in the callback returned use the post ids in the comments to get the posts. You can use the promises like this:
var promise = Comment.find({ "author": userId }).select("post").exec();
promise.then(function (comments) {
var postIds = comments.map(function (c) {
return c.post;
});
return Post.find({ "_id": { "$in": postIds }).exec();
}).then(function (posts) {
// do something with the posts here
console.log(posts);
}).then(null, function (err) {
// handle error here
});
2) Using populate. Query the Comment model for a particular user using the given userId, select just the post field you want and populate it:
var query = Comment.find({ "author": userId });
query.select("post").populate("post");
query.exec(function(err, results){
console.log(results);
var posts = results.map(function (r) { return r.post; });
console.log(posts);
});

Resources