Updating a record with mongoose - node.js

I'm learning MEAN stack applications and am working on a tv watchlist app. I built the api successfully without the seasons field in the model. Now I'd like to add this field so that a user can add seasons and episodes to the document to keep track of episodes they have watched. Through trial and error I found the query I'd use in the mongo shell to update a field in the episodes object but I can't create the right syntax to do this with mongoose inside my route. Can someone look in my tele.js routes at the router.put and tell me what's wrong.
Models (TVSeries.js)
var mongoose = require('mongoose')
var Schema = mongoose.Schema
var tvSchema = new Schema({
title:String,
poster:String,
rated:String,
program_time:Number,
network:String,
airs_on:[],
streams_on:[],
genre:[],
seasons:[
season_number:Number,
episodes:[
{
episode_number:Number,
title:String,
watched:Boolean
}
]
]
}, {collection: 'tvShows'});
module.exports = mongoose.model('tv', tvSchema);
Routes (tele.js)
var express = require('express');
var router = express.Router();
var TV = require('../models/TVSeries.js');
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Your Watchlist' });
});
router.get('/api/shows/:id/seasons', function(req, res){
TV.findById(req.params.id, 'seasons', function(err, data){
if(err){console.log(err)}
else{res.json(data)};
});
});
router.put('/api/shows/:id/seasons/:sid', function(req, res){
var setField = {
seasons:[
{
season_number:req.params.sid,
episodes:[
req.body
]
}
]
}
TV.findOneAndUpdate({"_id":req.params.id}, setField, {upsert:true}, function(err, results){
if(err){console.log(err)}
else{
console.log(setField);
res.json(results)
}
})
})
module.exports = router;
Mongo Shell command
db.tvShows.update({"_id":ObjectId('######################')},{$set: {'seasons.1.episodes.1.title':'This is my title change'}})

You can use &elementMatch to find the desire season in the array, and in the setField object you can use the positional $ operator which identify the element matched in the query.
The problem is that if it doesn't find any season that match the season_number, the document will not be updated. In this case you can set another update query to add this season in the seasons array.
router.put('/api/shows/:id/seasons/:sid', function(req, res){
var query = {
"_id": req.params.id,
"seasons": {
$elemMatch: {
"season_number": req.params.sid
}
}
}
var setField = {
$addToSet: {
"seasons.$.episodes": req.body
}
}
TV.findOneAndUpdate(query, setField, {upsert:true}, function(err, results){
if (err && err.code == 16836) { // no document was matched
var season = {
"season_number": req.params.sid
"episodes": [ req.body]
}
TV.findOneAndUpdate({"_id": req.params.id}, {$push: {"seasons": season}} , function(err, result) {
console.log("Inserted document in array");
res.json(result)
});
}
else if (err){
console.log(err);
res.status(500).send('Something wrong!');
}
else {
console.log(setField);
res.json(results)
}
})
})
You can see here some mongodb array operators.
Hope it helps.

Related

Problem with mongoose schema reference in Node

I'm new to Node, Mongoose and to everything related to Backend. I ran into a problem recently. I couldn't figure it out as the same code in exercise files of a development course seems to be the same. Nevertheless problem still doesn't resolve. Thing I'm trying to do, coding along with the tutorials, is that I want to define a Mongoose model for comments in the Mongo database for every campground section that I have displayed on the page. I have models folder where I define models for campground and comments, a seeds.js file where I dynamically add data to campgrounds (in order for it to appear with comments instantly) and then add comments. Here is what the actual code looks like for these files:
app.js-->
var express = require("express");
var app = express();
var bodyParser = require("body-parser");
var mongoose = require('mongoose');
var Campground=require("./models/campground");
var seedDB = require("./seeds");
var PORT = process.env.IP || 3200;
seedDB();
mongoose.connect('mongodb://localhost/yelp_camp', { useNewUrlParser: true },
(err, res) => {
if (err) throw err;
console.log('Database online');
});
app.use(bodyParser.urlencoded({ extended: true }));
app.set("view engine", "ejs");
//schema
app.get("/", function (req, res) {
res.render("landing");
});
app.get("/campgrounds/tornike", function (req, res) {
//get all campgrounds
res.send(Campground.name);
});
app.get("/campgrounds", function (req, res) {
//get all campgrounds
Campground.find({}, function (err, camp) {
if (err) {
console.log("ERROR");
console.log(err);
} else {
res.render("index", { campgrounds: camp });
}
});
});
app.post("/campgrounds", function (req, res) {
var name = req.body.name;
var image = req.body.image;
var desc = req.body.description;
var newCampground = { name: name, image: image, description:desc };
//create new camp and save to database
Campground.create(newCampground, function (err, newlyCreated) {
if (err) {
console.log(err);
} else {
res.redirect("/campgrounds");
}
});
});
app.get("/campgrounds/new", function (req, res) {
res.render("new.ejs");
});
//shows more info about camp
app.get("/campgrounds/:id", function (req, res) {
//find camp with provided id
Campground.findById(req.params.id, function (err, foundCampground) {
if (err) {
console.log(err);
} else {
//render show template
res.render("show", { campground: foundCampground });
}
});
});
app.listen(PORT, process.env.IP, function () {
console.log("camp");
});
seeds.js ---->
var mongoose = require("mongoose");
var Campground = require("./models/campground");
var Comment = require("./models/comment");
var data = [
{
name: "something",
image: "image URL",
description: "blah blah bla1"
},
{
name: "something",
image: "image URL",
description: "blah blah bla2"
},
{
name: "something",
image: "image URL",
description: "blah blah bla3"
}
];
function seedDB() {
Campground.deleteMany({}, function(err) {
console.log("removed camps");
data.forEach(function(seed) {
Campground.create(seed, function(err, data) {
if (err) {
console.log(err);
} else {
console.log("added campground");
Comment.create(
{
text: "dubdabdubadah",
author: "Homer"
},
function(err, comment) {
if (err) {
console.log(err);
} else {
campground.comments.push(comment);
campground.save();
console.log("Created new comment");
}
}
);
}
});
});
});
}
module.exports = seedDB;
comment.js --->
var mongoose = require("mongoose");
var commentSchema= new mongoose.Schema({
text:String,
author:String
});
module.exports=mongoose.model("Comment", commentSchema);
campground.js ---->
var mongoose = require("mongoose");
var campgroundSchema = new mongoose.Schema({
name: String,
image: String,
description: String,
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Comment"
}
]
});
module.exports = mongoose.model("Campground", campgroundSchema);
Error occurs in the seeds.js file on this line campground.comments.push(comment);
it looks like this:
[nodemon] starting `node app.js`
camp
Database online
removed camps
added campground
added campground
added campground
events.js:183
throw er; // Unhandled 'error' event
^
ReferenceError: campground is not defined
I can't actually get my head around this one as when comparing the code to the exercise files code - it is the same. The only thing I can think of is the versions of mongoose express or something else may not be relevant or some methods deprecated, but honestly no idea what's wrong. I've been trying to solve it for several days. any idea people ?
The error message is telling you that you haven't defined a variable named campground, which is correct, as you haven't.
To fix this, change data to campground in your Campground.create callback:
Campground.create(seed, function(err, campground) {

Sorting up updatedAt mongoDB

So on my webpage www.groupwrites.com I am showing an Index of stories in the "Read" page. These stories currently show in the order of which they were created (i.e the newest ones on bottom). I am trying to figure out how to display them with the most recently created/updated one first. I am using mongoDB, node JS on cloud9. I have been trying to research and know that I should use updatedAt but I am not sure how to plug everything in. I am not sure how to update the timestamp for updatedAt in the put routes.
This is my code for the index:
// INDEX - show all stories
router.get("/browse", function(req, res, next){
// Get all stories from DB
Story.find({}, function(err, allStories){
if (err) {
return next(err);
} else {
// if user is logged in then render stories and any alerts
if(req.user) {
User.findById(req.user._id).populate({
path: 'alerts',
model: 'Alert',
match: { 'isRead': { $eq: false }}
}).exec(function(err, user) {
if(err) {
return next(err);
}
res.render("stories/index", {stories:allStories, alerts: user.alerts.length, page: 'browse'});
});
} else {
res.render("stories/index", {stories:allStories})
}
}
})
})
// CREATE - add new story to DB
router.post("/browse", middleware.isLoggedIn, function(req, res, next){
// get data from form and add to stories array
var title = req.body.title
var image = req.body.image
var desc = req.body.description
var category = req.body.category
var author = {
id: req.user._id,
username: req.user.username
}
var newStory = {title: title, image: image, description: desc, author: author, category: category}
// Create a new story and save to database
Story.create(newStory, function(err, newlyCreated){
if (err) {
return next(err);
} else {
// redirect back to stories page
req.flash("success", "Successfully published story!")
res.redirect("/browse")
}
})
})
This is the code for the content of the stories, (i.e when adding a chapter to the story):
// New Content
router.get("/stories/:id/content/new", middleware.isLoggedIn, function(req, res, next){
// Find story by id
Story.findById(req.params.id, function(err, story){
if (err) {
return next(err);
} else {
res.render("content/new", {story: story})
}
})
})
// Create Content
router.post("/stories/:id/content", middleware.isLoggedIn, function(req, res, next){
// Look up story using ID
Story.findById(req.params.id).populate({path: 'subscribors', model: 'User'}).exec(function(err, story){
if (err) {
return next(err);
} else {
Content.create(req.body.content, function(err, content){
if (err) {
return next(err);
} else {
if(story.subscribors.length) {
var count = 0;
story.subscribors.forEach(function(subscribor) {
// create alert for each subscribor and add to subscribor's alerts
Alert.create({follower: story.author.id, followed: subscribor, story: story, isUpdated: true}, function(err, newAlert) {
if(err) {
return next(err);
}
// console.log(newAlert);
subscribor.alerts.push(newAlert);
subscribor.save();
count+=1;
if(count === story.subscribors.length) {
// Add username and ID to content
content.author.id = req.user._id;
content.author.username = req.user.username;
// Save content
content.save();
story.content.push(content);
story.save();
req.flash("success", "Successfully added chapter!");
return res.redirect("/stories/" + story._id);
}
});
});
} else {
// Add username and ID to content
content.author.id = req.user._id;
content.author.username = req.user.username;
// Save content
content.save();
story.content.push(content);
story.save();
req.flash("success", "Successfully added chapter!");
return res.redirect("/stories/" + story._id);
}
}
});
}
});
});
// Content Edit Route
router.get("/stories/:id/content/:content_id/edit", middleware.checkContentOwnership, function(req, res){
Content.findById(req.params.content_id, function(err, foundContent){
if(err){
res.redirect("back")
} else{
res.render("content/edit", {story_id: req.params.id, content: foundContent})
}
})
})
// Content Update
router.put("/stories/:id/content/:content_id", middleware.checkContentOwnership, function(req, res){
Content.findByIdAndUpdate(req.params.content_id, req.body.content, function(err, updatedContent){
if(err){
res.redirect("back")
} else {
req.flash("success", "Successfully edited chapter!")
res.redirect("/stories/" + req.params.id)
}
})
})
While defining a Mongoose Schema,
1 for ascending and -1 for descending
Example:
"use strict";
var mongoose = require('mongoose');
var db= require('mongoose').models;
let findOrCreate = require('findorcreate-promise');
var abc= new mongoose.Schema({
name: String,
updated_At: { type: Date, default: Date.now } // like this you can define
});
mongoose.model('abc', abc);
and you can use this by :
db.abc.find({})
.sort({'updated_At':1}) //1 for ascending and -1 for descending
.exec(Your callback function)
this will make sorting from smallest updated_At date to largest.
Thanks

Need to do a many comments belong to one article relation MongoDB

I am using Mongoose/MongoDB and I am trying to associate many comments to one article. My app begins by scraping from a website and then the user has the option to save each article that was scraped into the MongoDB. When the user chooses to save one article, I save it into database. So when a user clicks on one of their saved articles, they can comment on them. Each article has its own comment section I need to retrieve the correct comments.
//My post comment request in JS file
function postComment(){
var articleComment = {
comment: $('#comment').val().trim()
}
$.post('/comments/' + articleID, articleComment).done(function(data){
$('.main-popup').fadeOut();
console.log('DONNE', data);
});
}
//Post route in controller
router.post('/comments/:id', function(req, res){
var newComment = new Comment(req.body);
newComment.save(function(err, doc){
if(err){
console.log(err);
}else{
Comment.findOneAndUpdate({ "_id": doc._id }, { "article": req.params.id }).exec(function(err, doc){
if(err){
console.log(err);
res.send(err);
}else{
res.send(doc);
}
});
}
});
});
//Get request to get correct comments when clicked on specific article
function showCommentBox(){
$('.comments').empty();
$('#comment').val("");
articleID = $(this).attr('data-article-id');
$.get('/comments/' + articleID, function(data){
if(data.article){ //This is undefined*********************
for(var x = 0; x < data.comment.length; x++){
$('.comments').append("<div><h2>" + data.comment[x].comment + "</h2><span><button>×</button></span></div>");
}
}
$('.main-popup').fadeIn();
});
}
//Get route in controller
router.get('/comments/:id', function(req, res){
Comment.findOne({ "article": req.params.id }).populate("article").exec(function(err, doc){
if(err){
console.log(err)
}else{
res.json(doc);
}
});
});
//Article Model
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ArticleSchema = new Schema({
title: {
type: String
},
link: {
type: String
},
description: {
type: String
},
img: {
type: String
}
});
var Article = mongoose.model("Article", ArticleSchema);
module.exports = Article;
//Comment Model
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var CommentSchema = new Schema({
comment: {
type: String
},
article: {
type: Schema.Types.ObjectId,
ref: 'Article'
}
});
var Comment = mongoose.model('Comment', CommentSchema);
module.exports = Comment;
First, you're missing $set when you do .findOneAndUpdate. Also I think you should convert a string to Mongo ObjectId before setting it.
So it might look likt this:
const ObjectId = mongoose.Types.ObjectId;
Comment.findOneAndUpdate({ "_id": doc._id }, {$set: {"article": new ObjectId(req.params.id) }})
Also you don't need to make 2 database calls. You could article id before saving newComment and then simply send it as a response like this:
//Please notice that mongoose.Schema.Types.ObjectId and mongoose.Types.Object are different types.
//You need this one here:
const ObjectId = mongoose.Types.ObjectId;
router.post('/comments/:id', function(req, res){
var newComment = new Comment(req.body);
newComment.article = new ObjectId(req.params.id);
newComment.save(function(err, doc){
if (err) {
console.error(err);
res.send(err);
return;
}
res.send(doc);
});
});

Node.js express download from mongodb

I want to write a rest api, with which i am able to download some data. All datas were stored in a mongodb. I don't know what to pass to the download method, to make it possible.
Here is my current code:
router.get('/download/:productId/:username/:token', function (req, res) {
var auth = require('../provider/authProvider.js');
var authInst = new auth();
authInst.checkAuth(req.params.username, req.params.token, res, function (err, obj) {
if (obj == true) {
res.status(200);
// here is my problem, what to pass to the download-method
res.download('');
}
});
});
I could not find anything else, than passing paths to the download method.
Does anyone has an idea how to solve my problem?
I assume you know how to set up mongoose environment, putting config, connecting to MongoDB. If not please refer to my answer here.
Now let's say we have a Document in MongoDB as Blog.
So we need to create a model for Blog so that we can do CRUD operations using Mongoose ORM.
you need mongoose module for this to be included in your project.
so run this command from your project root directory, it will automatically download mongoose for you.
npm install mongoose --save
BlogModel.js
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var BlogSchema = new Schema({
"title" : { type: String },
"user_id" : { type: String },
"blog_uri" :{ type: String },
"post_date" : { type : Date, default: Date.now},
"body" : { type: String, default: '' },
"comments" : [
{ 'content' : { type: String },
'user_id' : { type: String },
'comment_date' : { type: Date },
'votes' : [
{
'user_id' : { type: String }
}
]
}
],
"hidden" : {type:Boolean, default: false }
});
mongoose.model('Blog', BlogSchema);
So let's create a separate file called BlogController.js where we will write methods for CRUD.
var mongoose = require('mongoose');
var Blog = mongoose.model('Blog');
var ObjectId = require('mongoose').Types.ObjectId;
exports.create = function(req,res){
var blog = new Blog(req.body);
blog.save(function(err){
if(err)
res.json({message: "Error occured while saving"});
else{
res.redirect('/home');
}
});
};
exports.getAll = function(req,res){
Blog.find(function(err,blogs){
if(err){
res.send(err);
}else{
res.json(blogs);
}
});
};
exports.get = function(req,res){
var id ;
try{
id = new ObjectId(req.params.id);
Blog.findById(id,function(err,blog){
if(err){
res.send(err);
}else{
res.render('blog.ejs', {
blog: blog
});
}
});
}catch(e){
res.send(404);
}
};
exports.update = function(req,res){
var id ;
try{
id = new ObjectId(req.params.blog_id);
Blog.findById(id,function(err,blog){
if(err){
res.send(err);
}
blog.save(function(err){
if(err)
res.send(err);
res.render('blog.ejs', {
message: "Blog Updated successfully"
});
});
});
}catch(e){
res.send(404);
}
};
exports.delete = function(req,res){
var id ;
try{
id = new ObjectId(req.params.blog_id);
Blog.remove({_id:id},function(err,blog){
if(err){
res.send(err);
}
res.render('blog.ejs', {
message: "Blog deleted successfully"
});
});
}catch(e){
res.send(404);
}
};
So this was about CRUD using Mongoose. I usually don't use res.render(..) in my projects because i put Templating logic in front end. I just use res.json(..) and pass the json data to the the frontend. So please go ahead and try. I hope i answered your question. You can refer to
this repo, for better example. Here i got a very clean CRUD implementation.

How do I pass multiple models into a view?

I am easily and successfully passing a single model into a view in one of my express routes like this:
exports.locations = function(req, res){
Location.find(function(err, results) {
res.render('locations', { title: 'Locations', locations: results });
});
};
I have another route where I need to pass 2 result sets into the view, how do I do that? I have tried doing this, but it doesn't seem to be working:
exports.locationdetail = function(req, res) {
var packages = Package.find();
Location.findById(req.params.id, function(err, result) {
res.render('location-detail', { title: 'Location Details', location: result, packages: packages });
});
};
Edit 1
The error I am getting is:
Cannot read property 'name' of undefined
My model looks like this:
var mongoose = require('mongoose')
,Schema = mongoose.Schema;
var PackageSchema = new mongoose.Schema({
name: String,
prev_package: String,
featured: Boolean,
services: Array
});
module.exports = mongoose.model('Package', PackageSchema);
And I am using this model in another view, and everything is working like a champ.
var mongoOp = require("./models/mongo");
var async = require('async');
router.get("/",function(req,res){
var locals = {};
var userId = req.params.userId;
async.parallel([
//Load user data using Mangoose Model
function(callback) {
mongoOp.User.find({},function(err,user){
if (err) return callback(err);
locals.user = user;
callback();
});
},
//Load posts data using Mangoose Model
function(callback) {
mongoOp.Post.find({},function(err,posts){
if (err) return callback(err);
locals.posts = posts;
callback();
});
}
], function(err) { //This function gets called after the two tasks have called their "task callbacks"
if (err) return next(err); //If an error occurred, we let express handle it by calling the `next` function
//Here `locals` will be an object with `user` and `posts` keys
//Example: `locals = {user: ..., posts: [...]}`
res.render('index.ejs', {quotes: locals.user,userdata: locals.posts})
});
});
So, it looks like this was another asynchronous "gotcha". Putting this into a nested callback did the trick:
exports.locationdetail = function(req, res) {
Location.findById(req.params.id, function(err, result) {
Package.find(function (err, results) {
res.render('location-detail', { title: 'Location Details', location: result, packages: results });
});
});
};

Resources