Frosty CMS: Associating User Data With Comments & Posts

Intro

Continuing our series on Frosty CMS, where we are building a blog CMS from scratch. In the last post we refactored our routes into modules. This time we are going to work on associating users with comments. Currently you must be logged in to make a comment, but the authors name is input manually; a user could put anything in there. We have no way knowing who has posted what. Today we will change that by linking comments to usernames and displaying the usernames automatically.

Comment Associations

Current Comment Schema

Let’s start by taking a look at our current comment schema.

// Mongoose Schema for comments
const commentSchema = new mongoose.Schema({
    author: String,
    date: {type: Date, default: Date.now},
    content: String
});

As we can see the author is being input manually from the comment form as a string.

New Comment Schema

We want to change this to an object that captures the users id and username.

// Mongoose Schema for comments
const commentSchema = new mongoose.Schema({
    author: {
        id: {
            type: mongoose.Schema.Types.ObjectID,
            ref: "User"
        },
        username: String
    },
    date: {type: Date, default: Date.now},
    content: String
});

So we can see here that we have replaced the author string with an object which contains an id and username. And we have specified that the id is a mongoose schema object ID from the User schema.

We have created a place in our comments schema to save the username and id of the user. Now we have to go to the comment route and actually send that data through.

Old Comment Route

Here is the old comment route.

// new comment: receive and save to blog
router.post("/", isLoggedIn, (req,res) => {
    // sanitize inputs
    req.body.comment.author = req.sanitize(req.body.comment.author);
    req.body.comment.content = req.sanitize(req.body.comment.content);
        
    async function saveComment() {
        try {
            // lookup blog using ID
            let blog = await Blog.findById(req.params.id);
            // create new comment
            let comment = await Comment.create(req.body.comment);
            // connect new comment to blog
            blog.comments.push(comment);
            blog.save();
            console.log("New Comment Saved");
            // redirect    
            res.redirect("/blogs/" + blog._id);
        } catch(err) {
            console.log(err);
        }
    }
    saveComment();
});  

What we have to do is, before the comment is pushed to the blog, add the username and id to the comment and save it.

router.post("/", isLoggedIn, (req,res) => {
    // sanitize inputs
    req.body.comment.content = req.sanitize(req.body.comment.content);
        
    async function saveComment() {
        try {
            // lookup blog using ID
            let blog = await Blog.findById(req.params.id);
            // create new comment
            let comment = await Comment.create(req.body.comment);
            // add username and id to comment
            comment.author.id = req.user._id;
            comment.author.username = req.user.username
            // save comment
            comment.save();
            // connect new comment to blog
            blog.comments.push(comment);
            blog.save();
            console.log("New Comment: " + comment);
            // redirect    
            res.redirect("/blogs/" + blog._id);
        } catch(err) {
            console.log(err);
        }
    }
    saveComment();
});

And we can see that the username and id has been captured in the comment in an object called author.

comment username captured

Updating Comment Display Template

The last thing to do is that we need to update our comment display partial, which is the easiest part. We just add .username to the end of comment.author.

<!--Comments Loop -->
<% blog.comments.forEach((comment) => { %>
<p><strong><%= comment.author.username %></strong><em class="text-muted"> - <%= comment.date.toDateString() %></em></p>
<p><%= comment.content %></p>
<% }); %>

comment username displayed

That’s all done. Now we just need to follow the (basically) same process for new blogs.

Blogs

The primary difference here is how we handle the route. Here is the refactor of the new blog route. We have broken all the incoming pieces of information into a new temporary variable and then combined them all into one variable called newBlog which we then push into Blog.create.

// new blog: receive and save
router.post("/", isLoggedIn, (req, res) => {
    // sanitize inputs
    req.body.blog.title   = req.sanitize(req.body.blog.title);
    req.body.blog.short   = req.sanitize(req.body.blog.short);
    req.body.blog.content = req.sanitize(req.body.blog.content);
    
    // assign variables to incoming data
    let title   = req.body.blog.title,
        image   = req.body.blog.image,
        short   = req.body.blog.short,
        content = req.body.blog.content,
        date    = req.body.blog.date;
    
    // retriever user data
    let author = {
        id: req.user._id,
        username: req.user.username,
        firstname: req.user.firstname,
        lastname: req.user.lastname
    };
    
    // combine all data into new variable
    let newBlog = {title: title, image: image, short: short, content: content, date: date, author: author};
    
    // save combined data to new blog
    Blog.create(newBlog,(err, newDatabaseRecord) => {
        if(err){
            console.log("Failed to write post to database.");
        } else {
            console.log("Blog successfully saved to database.");
            console.log(newDatabaseRecord);
            // redirect back to blogs page
             res.redirect("/");
        }
    });
});
});

GitHub Repo

Ncoughlin: Frosty