Skip to main content

Frosty CMS: Edit and Destroy Comments πŸ’¬

Nested Routes​

The nested routes that require two different ID’s are really the only part of this that require any new knowledge. So that being said, we need to make the actual edit comment template, create a link to edit comments that follows our REST formula, create a route that handles the new edit comment REST route, and then another route that handles the saving of the comment.

REST Route​

If we follow our proper convention the route will look like this:

/blogs/:id/comments/:id/edit

Keeping in mind that we cannot actually use :id twice. So it will actually look like this:

/blogs/:id/comments/:comment_id/edit

And then we can write a route that looks like this.

// edit comment form
// /blogs/:id/comments/...
router.get("/:comment_id/edit",isLoggedIn, (req, res) => {
Comment.findById(req.params.comment_id, (err,foundComment)=>{
if(err){
console.log(err);
} else {
res.render('editComment.ejs', {blog_id: req.params.id, comment: foundComment});
}
});
});

This exact example is followed in the document above, so download that and follow the colors if you are confused on the variables passing between layers.

Edit Comment Form​

That route will take us to our edit comment form.

<div class="container-lg mt-5">
<h1 class="settings-title">Edit Comment</h1>
<h4> Author: <strong><%= comment.author.username %></strong></h4>
<!--input for title, image, content -->
<form action="/blogs/<%= blog_id %>/comments/<%= comment._id %>?_method=PUT" method="POST">
<div class="form-group">
<label for="contentInput">
<p><strong>Your Thoughts...</strong></p>
</label>
<textarea class="form-control" id="contentInput" rows="3" name="comment[content]"><%= comment.content %></textarea>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>

Save Updated Comment​

Which then submits to this route.

// save updated comment
router.put('/:comment_id', isLoggedIn, (req,res)=>{
Comment.findByIdAndUpdate(req.params.comment_id, req.body.comment, (err, updatedComment)=>{
if(err){
console.log(err);
res.redirect('back');
} else {
res.redirect('/blogs/' + req.params.id);
}
});
});

Delete Comments​

There is nothing new here, but here is the delete route. I’ll skip the front end stuff.

// delete comments
router.delete("/:comment_id",isLoggedIn,(req, res) => {
Comment.findByIdAndRemove(req.params.comment_id,(err) => {
if(err){
console.log("failed to .findByIdAndRemove Comment object");
} else {
console.log("Comment with ID:" + req.params.comment_id + " has been deleted");
res.redirect('/blogs/' + req.params.id);
}
});
});

Restrict Access​

Of course we don’t want anybody to be able to edit any comment. So once again we go through our process to restrict a users ability to edit this based on ownership and role.

Our comments are available in two places. Directly on the blog post pages, and also in settings>comments. So we can add an Async function to the individual blog post route that checks a bunch of things for us. We want to check if the user is logged in for starters. If not they definitely can’t edit and we can skip all other checks. Then we can check the users role. If the user qualifies to edit we create a variable called editPermission that evaluates to true, and then we pass that variable in on the route along with the blog data.

router.get("/blog/:id",(req, res) => {

// evaluate if the user should be able to edit
function editorCheck(){
return new Promise((resolve, reject)=>{
// First check if user is logged in
if(!req.isAuthenticated()){
resolve(false);
// If user role = Editor || Admin
// let editAllow = true;
} else if (req.user.role === "Administrator" || req.user.role === "Editor"){
resolve(true);
// if the user is any other role
} else {
resolve(false);
}
});
}

// check if user has blanket permission to edit a comment before loading page.
async function checkForEditOrAdmin(){
try {
const editPermission = await editorCheck();
console.log("User is Admin or Editor: " + editPermission);
// Find Blog by ID and populate comments
Blog.findById(req.params.id).
// populate comments
populate("comments").
exec((err, dbData) => {
if(err){
console.log("error finding blog data by ID");
} else {
// render single post template with that post data
res.render("singleBlog.ejs", {blog: dbData, editPermission: editPermission});
console.log("Article: " + dbData.title + " has loaded.");
}
});
} catch(err) {
console.log(err);
}
}
checkForEditOrAdmin();
});

And then we just wrap our buttons on our template in some logic that checks for authorship and our editPermission variable.

<!-- Only author, admin or editor can modify content -->
<% if(currentUser && comment.author.id.equals(currentUser._id) || editPermission === true){ %>

Note 7/7/20: Looking back on this this code doesn't actually protect the route, it just hides/displays the edit button based on the user role and author. That is a good thing to do, but I believe that after I wrote this I went back and actually protected the routes themselves properly. Check repo for samples.

Comments

Recent Work

Free desktop AI Chat client, designed for developers and businesses. Unlocks advanced model settings only available in the API. Includes quality of life features like custom syntax highlighting.

Learn More

BidBear

bidbear.io

Bidbear is a report automation tool. It downloads Amazon Seller and Advertising reports, daily, to a private database. It then merges and formats the data into beautiful, on demand, exportable performance reports.

Learn More