Nested mongoose Schema giving trouble when trying to query in controller - javascript

I'm working on a small project and I have a solution to this problem, but it involves creating a new Schema with a reference to the new Schema in the old Schema. I would like to avoid this if at all possible because it will mean spending a couple hours rewriting some code and messing with tests.
The project is a forum site, and there are three main Schemas that comprise it (in addition to cursory Schemas for the forums, notifications, settings and the schemas for the user and the users activities). The Board Schema (contains a list of all forum sections if that wasn't apparent) Is a Schema that makes a reference to the Threads Schema so it can get the threads for each Board. My problem is in the Thread Schema.
var ThreadSchema = new mongoose.Schema({
... other unrelated Schema stuff...
comments: [{
created: {
type: Date,
default: Date.now
},
creator: {
type: mongoose.Schema.ObjectId,
required: true,
ref: 'User'
},
content: {
type: String,
required: true,
get: escapeProperty
},
likes: [{
type: mongoose.Schema.ObjectId,
required: false,
ref: 'User'
}],
liked: {
type: Boolean,
default: false
},
saved: [{
type: mongoose.Schema.ObjectId,
required: false,
ref: 'User'
}]
}]
});
blah blah blah.
I'm trying to pull for the users profile only the comments that that user has posted. The threads were easy, but comment data is not coming through. The request to the server goes through as successful, but I don't get any data back. This is what I am trying.
obj.profileComments = function (req, res) {
var userId = req.params.userId;
var criteria = {'comments.creator': userId};
Thread.find(criteria)
.populate('comments')
.populate('comments.creator')
.skip(parseInt(req.query.page) * System.config.settings.perPage)
.limit(System.config.settings.perPage + 1)
.exec(function (err, threads) {
if (err) {
return json.bad(err, res);
}
json.good({
records: threads
}, res);
});
};
This is a controller, and the json.bad and json.good are helpers that I have created and exported they basically are res.send.
var good = function (obj, res) {
res.send({
success: 1,
res: obj
});
};
and the bad is very similar, it just handles errors in an obj.res.errors and puts them into messages.
So now that that is all out of the way, I'm a little lost on what to do?
Is this something I should try to handle with a method in my Schema? It seems like I might have a little bit more luck that way. Thank you for any help.

Related

Can I update mongoose model that is once already rendered by using fetch?

I'm creating a web s like youtube using express.
and I'm using mongoose for db and pug(jade) template for view.
If I get to the home router("/"), the view file-"Home"- and db array-videos- are rendered.
The home controller looks like this,
import Video from "../models/Video.js";
export const home = async (req, res) => {
let videos = [];
videos = await Video.find({ hashtags: "#rap" }).populate("video");
return res.render("home", { pageTitle: "Home", videos });
};
At this same route, I want to filter my video db and re-render the filtered videos by using fetch. then send those videos to the pug template.
So I created a new function called 'handleVideoFilter()' in my 'main.js'.
const categoryBtn = document.querySelectorAll("#category_bar_chips a");
export const handleVideoFilter = async (event) => {
event.preventDefault();
const clickedCategory = event.target;
const id = clickedCategory.dataset.id;
fetch(`/api/videos/filtered/${id}`, { method: "GET" });
};
Array.from(categoryBtn).forEach((li) =>
li.addEventListener("click", handleVideoFilter)
);
and for this API router and controller are like this.
//router
apiRouter.get("/videos/filtered/:id", getFilteredVideos);
//the controller
export const getFilteredVideos = async (req, res) => {
const { id } = req.params;
const videos = await Video.find({ hashtags: `#${id}` });
return res.render({ videos });
};
But an TypeError occured and I don't see why. The fetch request and getting filltered videos array were successful, I guess there's something wrong with 'return res.render' code.
(node:4172) UnhandledPromiseRejectionWarning: TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received an instance of Object
I'm filltering videos using 'data-id' attribution in the nav, and using mixin to render Video model. the "home" pug file looks like this
include mixins/video
ul#category_bar_chips
a(href="/")
li all
a
li(data-id="music") music
a
li(data-id="rap") rap
a
li(data-id="cooking") cooking
a
li(data-id="pet") pet
a
li(data-id="recent") recent
.contents
.video-wrap
each video in videos
+video(video)
else
h1 No video found :(
here's the video mixin file
here's the Video model schema
//video schema
const videoSchema = new mongoose.Schema({
title: { type: String, required: true, trim: true, maxLength: 50 },
fileUrl: { type: String, required: true },
thumbUrl: { type: String, required: true },
description: { type: String, required: true, trim: true, minLength: 2 },
createdAt: { type: Date, required: true, default: Date.now },
hashtags: [{ type: String }],
meta: {
views: { type: Number, default: 0, required: true },
rating: { type: Number, default: 0, required: true },
},
comments: [
{ type: mongoose.Schema.ObjectId, required: true, ref: "Comment" },
],
owner: { type: mongoose.Schema.Types.ObjectId, required: true, ref: "User" },
});
Sorry If this question was too ambiguous or not professional since I'm new to this whole programming world.
But I'd really like to figure this out. And I don't have a clue what kind of languege or framework to solve this error.
Your answer would be very helpful, thanks a lot for reading.
Is this where the error is getting thrown?
return res.render({ videos });
There is no path specified for the the render method. It should look something like this:
return res.render('videos', { videos });
... where 'videos' is the view you want rendered and {videos} are the values you want to pass back to the view.

Mongoose is not populating (.populate()) on Production (Heroku), but works on Local

Essentially I am having one of those moments. My app's on Heroku, and the DB it uses is mLab (MongoDB).
It works on local (Cloud9), but not on production (Heroku).
I can't get .populate() to work on production.
Do you see any gaps in my code below (snippet) that may cause Heroku to fail, while it works on local?
Thank you.
(I have tried purging the DB (deleting the DB and making a new one. Also I've for similar questions on this site. Also I have tried 'heroku local --tail' command to debug and ran it on my local machine; it works on local... Just not on Heroku; appears buggy.)
People.find(id).populate("friends").exec(function(err, user){
if(err){
console.log("! Error retrieving user. " + err);
reject ("! Error retrieving user. " + err);
}
else {
console.log("0! Friends should be populated: " + user);
resolve(user);
}
});
My model:
var mongoose = require('mongoose');
var personSchema = mongoose.Schema({
name: String,
friends: [
{
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "Person"
},
name: String
}
],
username: String,
password: String,
});
module.exports = mongoose.model("Person", personSchema);
Your API function looks ok.
I suspect your issue is with how your models are setup, or what is in your data-base. I had similar issues the first time I tried to use Heroku, because Localhost is more forgiving.
In order for your API to work, the following 3 things must be setup:
(1) Model file: people.js
must look like something like:
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var peopleSchema = new Schema({
name: {
type: String,
required: true,
trim: true
},
friends: [{
type: Schema.Types.ObjectId,
ref: "Friends"
}]
});
const People = mongoose.model('Peoples', peopleSchema);
module.exports = People;
And then you must have a 'Friends' model, that 'People' is referencing.
(2) Model file: friends.js
must look something like:
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
// Create the Comment schema
var friendsSchema = new Schema({
friend_name: {
type: String,
required: true,
trim: true
},
});
const Friends = mongoose.model('Friends', friendsSchema);
module.exports = Friends;
And lastly, in order for .Populate to work, you need at least two docs in the database.
(3) Database must contain a Person doc and a Friend doc
must look something like:
people.js :
"_id": {
"$oid": "5bef3480f202a8000984b3c5"
},
"name": "Monica Geller"
"friends": [
{
"$oid": "5bef3480f202a8000984b5b4"
}
]
friends.js :
"_id": {
"$oid": "5bef3480f202a8000984b5b4"
},
"friend_name": "Rachel Green"
Hopefully this helps, or gets you closer to your answer.
It was a version issue.
Had to ensure that all platforms (mLab, and my local database) were using the same version of Mongoose.
npm install mongoose#5.4.8 --save

Waterline queries similar to HQL

I have models in Sails as follows:
User Model
attributes: {
firstName: {
type: 'string',
required: true
},
lastName: {
type: 'string',
required: true
},
company: {
model: 'Company'
}
}
Company
name: {
type: 'string',
required: true,
unique: true
},
description: {
type: 'string',
required: true
}
In HQL queries, for getting a user working in a specific company, we use something like this:
Select * from User where company.name=?
How can I achieve same in Sails, Right now there are two queries which I am running, one to get User and then another to get company for that user. Is there any way both can be combined in one?
and one more thing, how does waterline deal with scenarios where in we need to get something out of a foreign key directly i.e. If I get user data, then can I get company details by just calling:
User.findOne({id:1}, function(err, res){
//Res contains user data with just companyId now,
//how can i get whole company object here
var companyName = res.company.name; //This is not working currently
})
Try something like this:
User.findOne({id: 1})
.populate('company')
.exec(function(err, user) {
if(err){
//handle errors
}else{
//do stuff
}
});
This should get the values from the association (foreign key).

Express returning "User is not authorized" when modifying object from another user

I have a Classroom model that looks like this:
/**
* Classroom Schema
*/
var ClassroomSchema = new Schema({
created: {
type: Date,
default: Date.now
},
participants: [{
type: Schema.ObjectId,
ref: 'User'
}],
lesson: {
type: Schema.ObjectId,
ref: 'Lesson',
required: 'Define a lesson for this classroom',
},
user: {
type: Schema.ObjectId,
ref: 'User'
},
currentTaskIndex: {
type: Number,
default: 0
},
});
As you can see, the model keeps a reference to the user that have created the Classroom (User) and it also has many participants (more Users).
I'm trying to add the functionality to allow other Users (not the creator of the Classroom) to join the Classroom by sending a PUT request to the classroom API with a new User in the participants field. However, since the User sending the request is not the one who created the object, Express is returning a 403 (Forbidden):
exports.hasAuthorization = function(req, res, next) {
if (req.classroom.user.id !== req.user.id) {
return res.status(403).send('User is not authorized');
}
next();
};
What's the best approach/pattern to solve this allowing Users to join Classrooms created by another User but not to do other actions like deleting the object. Other fields might be updated by another participants, like the currentTaskIndex.
Thanks for your help!
You could add a 'master' or 'admin' field to your classroom, and store the Creator's userId in it.
Then you can allow the delete/modify, etc actions only for the user who is master of this particular classroom, while letting everyone in.

Understanding Mongoose sub documents

I'm learning Mongoose. At the moment I did few nice things but I really don't understand exactly how Mongoose manage relationships between Schemas.
So, easy thing (I hope): I'm doing a classic exercise (by my self because I cannot find a good tutorial that create more than 2 Schemas) with 3 Schemas:
User, Post, Comment.
User can create many Post;
User can create many Comment;
Post belong to User.
Comment belong to User and Post.
I don't think it is something very hard uhu?
At the moment I can manage very well Relation between User and Post. My Unit test return exactly what I need, at the moment I'm using mongo-relation and I don't know if it is a good idea...
it('Use should create a Post', function(done) {
User.findOne({ email: 'test#email.com' }, function(err, user) {
var post = {
title: 'Post title',
message: 'Post message',
comments: []
};
user.posts.create(post, function(err, user, post) {
if (err) return done(err);
user.posts[0].should.equal(post._id);
post.author.should.equal(user._id);
// etc...
done();
});
});
});
The problem now is to create a comment.
I can not create a comment that refere to the Post and to the User together.
I did something like that and works but when I perform a remove it is removed only from the Post and not from the User.
So I think there is something I miss or I still need to study to enhance it.
it('User should add a Comment to a Post', function(done) {
User.findOne({ email: 'test#email.com' }, function(err, user) {
if (err) return done(err);
var comment = new Comment({
author: user._id,
message: 'Post comment'
});
Post.findOne({ title: 'Post title'}, function(err, post) {
if (err) return done(err);
post.comments.append(comment, function(err, comment) {
if (err) return done(err);
post.save(function(err) {
if (err) return done(err);
});
comment.author.should.equal(user._id);
post.comments.should.have.length(1);
// etc...
done();
});
});
});
});
As you can see the code is not very "nice to see" but it works fine in terms of creations.
The problem is when I remove a Comment. It seems like something is wrong.
Here is the Model relationship:
// User Schema
var userSchema = new mongoose.Schema({
// [...],
posts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Post' }],
comments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }],
});
// Post Schema
var postSchema = new mongoose.Schema({
author: { type: mongoose.Schema.ObjectId, ref: 'User', refPath: 'posts' },
title: String,
message: String,
comments: [{ type: mongoose.Schema.ObjectId, ref: 'Comment' }]
});
// Comment Schema
var commentSchema = new mongoose.Schema({
author: { type: mongoose.Schema.ObjectId, ref: 'User', refPath: 'comments' },
post: { type: mongoose.Schema.ObjectId, ref: 'Post', refPath: 'comments' },
message: String
});
I really hope in your help to understand all this.
It will be nice also a simple good tutorial about it.
I think you are misunderstanding subdocuments. The way you have your schema setup you are creating references to documents in other collections.
For example if you create a post, in the database it will look like this:
{
"author": ObjectId(123),
"title": "post title",
"message": "post message",
"comments": [ObjectId(456), ObjectId(789)]
}
Notice the "author" field just contains the ID of the author who created it. It does not actually contain the document itself.
When you read the document from the DB you can use the mongoose 'populate' functionality to also fetch the referred to document.
Ex (with populate):
Post
.findOne({ title: 'Post title'})
.populate('author', function(err, post) {
// this will print out the whole user object
console.log(post.author)
});
Ex (no populate):
Post
.findOne({ title: 'Post title'}, function(err, post) {
// this will print out the object ID
console.log(post.author)
});
Subdocuments:
You can actually nest data in the DB using subdocuments, the schema would look slightly different:
var postSchema = new mongoose.Schema({
author: { userSchema },
title: String,
message: String,
comments: [commentSchema]
});
When saving a post the user document would be nested inside the post:
{
"author": {
"name": "user name",
"email": "test#email.com"
...
},
"title": "post title",
"message": "post message",
"comments": [{
"message": "test",
...
}, {
"message": "test",
...
}]
}
Subdocuments can be useful in mongo, but probably not for this case because you would be duplicating all of the user data in every post.
Removing documents
When you issue a Comment.remove(id) the comment will be removed but it will not affect the other documents referring to it. So you will then have a Post and a User with a comment ID that does not exist. You need to manually clean up the comment ID from the other documents. You could use the mongoose pre remove event to do this. http://mongoosejs.com/docs/middleware.html
commentSchema.pre('remove', function (next) {
// this refers to the document being removed
var userId = this.author;
var postId = this.post;
User.findById(userId, function(err, user) {
// remove comment id from users.comments here;
Post.findById(postId, function(err, post) {
// remove comment id from post.comments;
next();
});
});
});

Categories

Resources