Trying to add followers in Mongoose subarray - node.js

I'm learning node.js and I'm trying to figure out how to add users to a subarray in my schema. I'm basically doing a twitter-clone in order to learn how node works.
This is my UserSchema. I want to add users into the "following" field-array.
#Usermodel.js
var UserSchema = mongoose.Schema({
username: {
type: String,
index:true
},
password: {
type: String
},
email: {
type: String
},
name: {
type: String
},
facebook : {
id : String,
token : String
},
resetPasswordToken: {type: String},
resetPasswordExpires: {type: Date},
following: [{type: mongoose.Schema.Types.ObjectId, ref: 'User'}], <-- I want to add users here
posts : [{ type: mongoose.Schema.Types.ObjectId, ref: 'Post' }]
});
UserSchema.index({username: 'text'});
var User = module.exports = mongoose.model('User', UserSchema);
Further down in this file you will find my schema method for adding users into the "following" subarray:
#Usermodel.js
module.exports.addFollowers = function (req, res, next){
User.findOneAndUpdate({_id: req.user._id}, {$push: {following: req.body.id}})
};
I'm querying a route in order to call my schema function. It looks like this:
#routes.js
router.get('/follow', User.addFollowers);
In my ejs front-end, I try to call my schema function by sending a GET-request to my route:
#index.ejs
<ul>
<%results.forEach(function(element){%> <-- Here I'm looping over users
<% if(user.id != element.id) { %> <-- Not show if it is myself
<li>
<%=element.username%></a> <br>
<form action="/follow" method="GET"> <-- Call route form
<input type="hidden" name="id" value=<%=element._id%>>
<button type="submit">Follow</button> <-- Submit GET
</form>
</li>
<% } %>
<br>
<%});%>
</ul>
Not sure what to do from here. When I press the "Follow" button my site keeps loading. Couldn't find any posts here on stackoverflow that could help me more at this stage.
Anyone who knows what is wrong? Is this the right way to do it?

Make some changes in #Usermodel.js
module.exports.addFollowers = function (req, res, next){
User.findOneAndUpdate({_id: req.user._id}, {$push: {following: req.body.id}}, next)
};
Try this code.

Related

Mongoose: push a string into array

I have a website where any logged-in user can leave a review for the shop.
So basically I have two schemas:
const journalSchema = new mongoose.Schema({
title: String,
category: String,
subcategory: String,
rating: Number,
review: [{type: String}],
link: String,
description: String,
});
const userSchema = new mongoose.Schema ({
username: String,
password: String,
journal: [{type: mongoose.Schema.Types.ObjectId, ref: 'Journal'}]
});
const Journal = mongoose.model("Journal", journalSchema);
const User = mongoose.model("User", userSchema);
form from the ejs file:
<div class="container my-3">
<h1>Compose</h1>
<form class="" action="/stats" method="post">
<div class="form-group">
<label for="review">Description</label>
<textarea id="review" class="form-control" name="journalReview" rows="5" cols="30"></textarea>
</div>
<button class="btn btn-primary my-2" type="submit" name="button">Publish</button>
</form>
</div>
post route:
app.post("/stats", function(req, res){
if(req.isAuthenticated()){
const favJournal = req.body.savedJournal;
const userId = req.user.id;
const userReview = req.body.journalReview;
User.findById(userId, function(err, foundUser){
Journal.findById(favJournal, function(err, foundJournal){
if(err){
console.log(err);
}
else{
if(foundUser){
foundJournal.review.push(userReview);
foundJournal.save(function(){
console.log(foundJournal);
});
foundUser.journal.addToSet(foundJournal);
foundUser.save(function(){
res.redirect("/favourite");
});
}
}
});
})
.populate('journal')
.exec(function(err, user){
if(err) return handleError(err);
});
}
else{
res.redirect("/login");
}
});
Every time I try to push review from the ejs file I keep getting this error:
events.js:353
throw er; // Unhandled 'error' event
^
TypeError: Cannot read property 'review' of null
at C:\Users\HelloThere\Desktop\miesto\app.js:337:24
at C:\Users\HelloThere\Desktop\miesto\node_modules\mongoose\lib\model.js:5065:18
at processTicksAndRejections (internal/process/task_queues.js:77:11)
Emitted 'error' event on Function instance at:
at C:\Users\HelloThere\Desktop\miesto\node_modules\mongoose\lib\model.js:5067:15
at processTicksAndRejections (internal/process/task_queues.js:77:11)
I tried different solutions from similar posts. Like mongoose methods: findOneAndUpdate and updateOne, but they just return null.
Instead of getting the shop and manipulating it with JavaScript code and then saving it back to the database, you could achieve this through findOneAnUpdate and $push operator.
For instance, this query
Shop.findById( shopId, (shop) => {
shop.products.push(product);
shop.save();
}
can be done through this query
Shop.findOneAndUpdate(
{ _id: shopId },
{ $push: {
products: product
}
})
$pop, $push, and $pull are very powerful tools to manipulate arrays in Mongoose. Take a look at the docs.
For the error you're getting, I think you're getting because you're passing a wrong journalId to findById. Check with MongoDB Compass if you do have a document with that id favJournal
I think I figured out the cause of the problem, I have two post forms in my ejs file, and since both forms have submit button with nothing differentiating them, only the first form gets called in the post route.

Accessing child methods in parent for mongoose succeeds in array and fails with single child

UPDATE : Solution is at bottom of question
I have an express site using mongoose.
I'll greatly simplify to say that I have adults, kids, and house models. When I create methods on kids, I can call them from within methods on adults and get a result. I can also call them from my .ejs views. However, when I create methods on house, I can only get a result from my .ejs views and get undefined when called from within methods on adults. Example code follows.
adult.js
const mongoose = require('mongoose');
const adultSchema = mongoose.Schema({
name: { type: String },
size: {type: String},
kids: [{type: mongoose.Schema.Types.ObjectId, ref: 'Kid', required: true}]
house:{type: mongoose.Schema.Types.ObjectId, ref: 'House', required: true}
});
adultSchema.method({
getKidsDescription: function() {
if (this.kids.length < 1) {
return 'No kids yet';
} else {
let ev = 'Kids, aged: ';
let kds = this.kids;
kds.forEach(function(k){
ev = ev + 'k.getAge()' // works
})
return ev;
}
},
getHouseDescription: function(){
return 'A fabulous house on '+this.house.getFullStreet(); // does not work
}
})
module.exports = mongoose.model('Adult', adultSchema);
kid.js
const mongoose = require('mongoose');
const kidSchema = mongoose.Schema({
name: { type: String },
size: {type: String},
birthdate: {type:Date}
});
kidSchema.method({
getAge: function() {
return (Math.floor(new Date() - this.birthdate)/(1000*60*60*24*365))
},
})
module.exports = mongoose.model('Kid', kidSchema);
house.js
const mongoose = require('mongoose');
const houseSchema = mongoose.Schema({
name: { type: String },
city: {type: String},
street: {type:String}
});
houseSchema.method({
getFullStreet: function() {
return this.street + ' Road';
},
})
module.exports = mongoose.model('House', houseSchema);
When I make a query for theAdult, it looks like this:
controller.js
exports.main = async (req, res, next) => {
if (req.theAdult) {
try {
const found = await db.fetchAdult(req.theAdult._id)
res.render('/main', {
//theHouse: found.house //below I show this working
});
} catch(e) {
throw new Error(e.message)
}
} else {
res.redirect('/');
}
}
db.js
exports.fetchAdult = (id) => {
return Adult.findById(id)
.populate({ path: 'kids'})
.populate({ path: 'house'})
.exec()
.then(doc => {
return doc;
});
}
Assuming house is passed to view as an object when rendered (commented out above), this works
view.ejs
<p> <%= theHouse.getFullStreet() %></p>
Assuming house populated on the call to load the Adult, this returns undefined.
view.ejs
<p> <%= theAdult.house.getFullStreet() %></p>
At the same time, both of these work
view.ejs
<ul> <% theAdult.kids.forEach(function(k) { %>
<li><%= k.getAge() %> </li>
<% }); %>
</ul>
<p> <% theAdult.getKidsDescription() %> </p>
What I am not understanding is how the method calls work for objects in array and work in the view but do not work for objects on in an array. This is a single child error (for me). If it did not work in the view, I would assume that the method getFullStreet() was the problem, but it works in the view. If the array methods could not be called within the parent, I would assume the issue was with trying to access getFullStreet() in the parent.
What am I missing?
SOLUTION
I was fetching theAdult in my call to show view.ejs, but I was then actually relying on currentAdult which referred to req.adult and did not have the fields populated. My solution was to add a pre hook to the adult schema that always populates house on find.
in adult.js
adultSchema.pre('find', function() {
this.populate('house')
})
Have you tried passing a hydrated theAdult? It might only see the ObjectID, without any other data or methods.

How can I add/subtract data from a EJS template

I cant figure out how to substract data from an EJS template. This is my Schema:
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var InventorySchema = Schema({
name: [{type: String, required: true, unique: true}],
quantity: [Number],
code: [String],
price: [Number],
stock: [{type: Boolean, default: true}]
})
var Inventory = mongoose.model("Inventory", InventorySchema);
module.exports = Inventory;
The below is the main query to show all my data:
router.get("/", (req, res)=>{
InventoryModel.find({})
.then((inventory)=>{
console.log(inventory);
res.render("inventory/index", {inventory: inventory})
})
.catch((err)=>{
console.log(err);
})
})
And below is the form Where I want to add/subtract data from:
<div class="container">
<form action="/inventory" method="POST">
<label>Name</label>
<input type="text" name="itemName" placeholder="item name"><p/>
<label>Quantity</label>
<input type="number" name="quantity" placeholder="quantity"><p/>
<button>Create</button>
</form>
</div>
So here's what I'm trying to accomplish. Whatever number I enter in the quantity field, subtract that data from my mongodb database whenever I hit the create button (which is a POST)
Any help?
Thank you
You would need to do an update on the existing document and use the $inc operator.
Example:
# create query conditions and update variables
const conditions = { },
update = { $inc: { views: 1 }}; # inc accepts negative numbers
# update documents matching condition
Model.update(conditions, update)

Questions about POST request to node.js from ajax (Error)

When I first send data (post), it will be stored, but when I send different data second time, it produces the error message
"error": "E11000 duplicate key error index: mongodb-database.users.$name_1 dup key: { : null }"
But, in my db, there is no null data, which I think it shouldn't be duplicated? I am not sure if there is another code that causes error.
Any ideas help me. Thanks.
My route
.post(function(req, res, next){
Users.create(req.body).then(function(user){
res.json(user);
}).catch(next);
});
My model
var userSchema = new Schema({
name: {
type: String,
// required: [true, "Name field is required"],
unique:true
}
});
My index.html (using Ajax)
$.("form").submit(function(event){
var order = {
name: $username.val(),
};
$.ajax({
type: 'POST',
url: '/users',
data: order
success: function(newOrder){
$result.append('<li>name: ' + newOrder.username + '</li>');
},
error: function(){
alert('error saving order');
}
});
});
});
</script>
</head>
<body>
<div id="result"></div>
<form action="/users" method="POST">
<label>Username:<input type="text" id="username"></label>
<input type="submit" value="Submit" id="post_message">
</form>
This error is occurring due to unique key constraint that you added in model definition. Remove unique from model and add require if you want to make name field required. You can redefine your model like this
var userSchema = new Schema({
name: {
type: String,
// required: [true, "Name field is required"],
required: true
}
});

Create search articles feature in MEAN stack

First of all, let me tell you that I'm a novice in the world of javascript and node.js. I have been searching for help in trying to do what i want but haven't found yet.
I am using the MEAN stack(http://mean.io/) and I am trying to implement a search feature in the included articles model. The search would look for articles with a specific tag and would be implemented in the index page. Follow me and see if you can find what I am missing please.
In the backend:
app/models/
/**
* Article Schema
*/
var ArticleSchema = new Schema({
created: {
type: Date,
default: Date.now
},
title: {
type: String,
default: '',
trim: true
},
content: {
type: String,
default: '',
trim: true
},
tag: {
type: String,
default: '',
trim: true
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
app/controllers/
exports.searcharticle = function(req, res) {
Article.find({'tag': req.params.tag}).sort('-created').populate('user', 'name username').exec(function(err, articles) {
if (err) {
res.render('error', {
status: 500
});
} else {
res.jsonp(articles);
}
});
};
Added the route for the search in app/routes/articles.js
app.get('/articles/search/:tag', articles.searcharticle);
In the frontend:
Created the view for the search wich will display the search results - public/views/articles/search.html
<section data-ng-controller="ArticlesController" data-ng-init="searchart()">
<ul class="articles unstyled">
<li data-ng-repeat="article in articles">
<span>{{article.created | date:'medium'}}</span> /
<span>{{article.user.name}}</span>
<h2><a data-ng-href="#!/articles/{{article._id}}">{{article.name}}</a></h2>
<div>{{article.tag}}</div>
</li>
</ul>
<h1 data-ng-hide="!articles || articles.length">Your search hasn't returned any results. <br> Why don't you Create One?</h1>
</section>
The view for the index.html, where the searchbox will be implemented
<section data-ng-controller="ArticlesController">
<form role="form" data-ng-submit="searchart()">
<div>
<div>
<input type="text" id="tag" ng-model="selected" class="form-control" placeholder="Tag">
</div>
<div>
<button type="submit" class="btn btn-default">Submit</button>
</div>
</div>
</form>
Added the route to the config.js
when('/articles/search/:tag', {
templateUrl: 'views/articles/search.html'
}).
And added the search function to the articles controller
$scope.searchart = function() {
Articles.query(function(articles) {
$scope.articles = articles;
});
};
Right now, with this code implemented, when I click in the submit button in the index page, nothing happens.
Can you find what am I missing?
Thanks in advance!
In order to use a URL in your client Article Service, you should define the URL Parameter in the articles service at: packages/articles/public/services/article.js, like the articleId parameter already defined in there like this:
angular.module('mean.articles').factory('Articles', ['$resource',
function($resource) {
return $resource('articles/:articleId', {
articleId: '#_id'
}, {
update: {
method: 'PUT'
}
});
}
]);
Then you need to pass it in your angular controller search method, like the function that gets one by id, like this:
$scope.findOne = function() {
Articles.get({
articleId: $stateParams.articleId
}, function(article) {
$scope.article = article;
});
};
Personally I don't know how to add another parameter to the $resource object in addition to the existing one (articleId), you may have to create another $resource service with the new parameter (:tag) and use it in your search method in your angular controller.
Another way that sounds more simple and flexible to me is to just pass the search parameters in the query method, like this:
$scope.searchart = function() {
Articles.query({tag:$scope.selectedTag}, function(articles) {
$scope.articles = articles;
});
};
and then at the server side controller, read your query parameters like this:
exports.searcharticle = function(req, res) {
Article.find(req.query).sort('-created').populate('user', 'name username').exec(function(err, articles) {
if (err) {
res.render('error', {
status: 500
});
} else {
res.jsonp(articles);
}
});
};

Resources