Building A Content Management System Using The MEAN Stack - 3 (Create Controller Modules 2)

in #utopian-io6 years ago (edited)

Repository

https://github.com/nodejs/node

What Will I Learn

The codebase for this tutorial is based on MEANie an open source content management system by Jason Watmore.

This is the third tutorial in the series of tutorials on building a content management system using the MEAN technology.

In the first two tutorials we created the backend server, some helper and controller modules for the application.

In this tutorial we are going to work on the remaining controller modules for the application features including

  1. Contact Controller
  2. Pages Controller
  3. Posts Controller
  4. Redirects Controller
  5. Users Controller

N.B;- LINK TO THE EARLIER TUTORIALS IN THIS SERIES CAN BE FOUND AT THE END OF THIS POST

Requirements

Difficulty

  • Intermediate

Tutorial Contents

Contact Controller

In the application we would need a contact feature so the non-admin users will be able to get in touch with the admin through e-mail.

To create a controller for our contact feature, first of all create a new directory in the controllers directory we created in the last tutorial.

Name the new directory api. Inside the new directory, add a new file contact.controller.js.

In the file we have

var  config  =  require('config.json');

var  nodemailer  =  require('nodemailer');

var  express  =  require('express');

var  router  =  express.Router();

  

// routes

router.post('/', send);

  

module.exports  =  router;

  

function  send(req, res) {

    // email data and options

    var  mailOptions  = {

        from:  req.body.email,

        to:  config.contactEmail,

        subject:  req.body.subject,

        text:  req.body.message

    };

  

    // send mail

    var  transporter  =  nodemailer.createTransport();

    transporter.sendMail(mailOptions, function (err) {

        if (err) {

            console.log('error sending email', err);

            return  res.status(400).send(err);

        }

  

        res.sendStatus(200);

    });

}

var nodemailer = require('nodemailer'); will import nodemailera popular JavaScript module for sending emails in Node.js applications.

router.post('/', send); is the route that handles the email body and it has a callback function send.

In the function send the first variable mailOptions is a JavaScript object containing the data required to initialize the email for sending.

from: req.body.email holds the email address of the sender.

to: config.contactEmail holds the address the email is sent to, the actual value for this can be found in the config.json file. To know more about the config.json check the first tutorial.

subject: req.body.subject holds the title/header for the email to be sent

text: req.body.message holds the actual email message.

var transporter = nodemailer.createTransport() should contain the required details for the email provider account where the email is set to be received.

The value for this section is relative, read more about this from nodemailer documentation.

transporter.sendMail() will handle the actual sending of the email given the parameter from the variable mailOptions and a callback function that handles any error that might arise during the email transfer.

Pages Controller

Creata a new file pages.controller.js, it will contain the following.

var  config  =  require('config.json');

var  _  =  require('lodash');

var  express  =  require('express');

var  jwt  =  require('express-jwt')({ secret:  config.secret });

var  router  =  express.Router();

var  pageService  =  require('services/page.service');

  

// routes

router.get('/', getAll);

router.get('/slug/:slug', getBySlug);

router.get('/:_id', jwt, getById);

router.post('/', jwt, create);

router.put('/:_id', jwt, update);

router.delete('/:_id', jwt, _delete);

  

module.exports  =  router;

  

function  getAll(req, res) {

    pageService.getAll()

        .then(function (pages) {

            // if admin user is logged in return all pages, otherwise return only published pages

            if (req.session.token) {

                res.send(pages);

            } else {

                res.send(_.filter(pages, { 'publish':  true }));

            }

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

  

function  getBySlug(req, res) {

    pageService.getBySlug(req.params.slug)

        .then(function (page) {

            // return page if it's published or the admin is logged in

            if (page.publish  ||  req.session.token) {

                res.send(page);

            } else {

                res.status(404).send('Not found');

            }

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

  

function  getById(req, res) {

    pageService.getById(req.params._id)

        .then(function (page) {

            res.send(page);

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

    }

  

function  create(req, res) {

    pageService.create(req.body)

        .then(function () {

            res.sendStatus(200);

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

  

function  update(req, res) {

    pageService.update(req.params._id, req.body)

        .then(function () {

            res.sendStatus(200);

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

  

function  _delete(req, res) {

    pageService.delete(req.params._id)

        .then(function () {

            res.sendStatus(200);

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

var jwt = require('express-jwt')({ secret: config.secret }); will import the jsonwebtoken module and the secret token required for the authentication.

We set routes for all the page operations, the ones with jwt appearing before its callback function will require authentication before execution.

The first function getAll() will check if the user is logged in as admin, if that is true the function returns the list of all the created pages from the database.

If the condition is false the function returns the list of published pages only.

The getBySlug() function gets a page that matches the provided slug, the function checks if the page requested has been published or the admin is logged in, if either returns true the page is sent back as a response else a 404 status is returned.

create() helps in the addition of a new page, it grabs the body of the request(page contents), if the operation is successful it returns a 200 OK status code.

update() will update the contents of an existing page, it gets the idand body of the requested page, upon successful operation it returns a 200 OK status code.

_delete() deletes an existing page by grabbing the page id and returning a 200 status code upon successful operation.

Posts Controller

Create a new file posts.controller.js in the api directory to handle operations for blog posts.

The code for the posts controller

var  config  =  require('config.json');

var  _  =  require('lodash');

var  express  =  require('express');

var  jwt  =  require('express-jwt')({ secret:  config.secret });

var  router  =  express.Router();

var  postService  =  require('services/post.service');

  

// routes

router.get('/', getAll);

router.get('/:year/:month/:day/:slug', getByUrl);

router.get('/:_id', jwt, getById);

router.post('/', jwt, create);

router.put('/:_id', jwt, update);

router.delete('/:_id', jwt, _delete);

  

module.exports  =  router;

  

function  getAll(req, res) {

    postService.getAll()

        .then(function (posts) {

            // if admin user is logged in return all posts, otherwise return only published posts

            if (req.session.token) {

                res.send(posts);

            } else {

                res.send(_.filter(posts, { 'publish':  true }));

            }

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

  

function  getByUrl(req, res) {

    postService.getByUrl(req.params.year, req.params.month, req.params.day, req.params.slug)

        .then(function (post) {

            // return post if it's published or the admin is logged in

            if (post.publish  ||  req.session.token) {

            res.send(post);

            } else {

                res.status(404).send('Not found');

            }

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

  

function  getById(req, res) {

    postService.getById(req.params._id)

        .then(function (post) {

            res.send(post);

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

  

function  create(req, res) {

    postService.create(req.body)

        .then(function () {

            res.sendStatus(200);

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

  

function  update(req, res) {

    postService.update(req.params._id, req.body)

        .then(function () {

            res.sendStatus(200);

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

  

function  _delete(req, res) {

    postService.delete(req.params._id)

        .then(function () {

            res.sendStatus(200);

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

This file also imports the jsonwebtoken module to handle authentication var jwt = require('express-jwt')({ secret: config.secret }); just like it is with the pages controller module.

getAll() returns a list of all posts for logged in users(admin), if the user is not logged in it only returns the published posts.

getByUrl() returns a post matching the provided url. The url for each post in question includes the date and slug associated with the post.

getById() returns a post matching the provided post id.

update() edits and updates an existing post and returns a 200/OK status code after successfully updating the post.

_delete() removes an existing post from the list of all available posts. It uses the id of the post contained in the request to identify the right post to delete.

Redirects Controller

There are instances in the application where users are redirected after completing an operation.

The next block contains code that performs functions pertaining to the redirect controller.

Add a new file in the api folder redirects.controller.js

Code for the redirects file

var  config  =  require('config.json');

var  _  =  require('lodash');

var  express  =  require('express');

var  jwt  =  require('express-jwt')({ secret:  config.secret });

var  router  =  express.Router();

var  redirectService  =  require('services/redirect.service');

  

// routes

router.get('/', jwt, getAll);

router.get('/:_id', jwt, getById);

router.post('/', jwt, create);

router.put('/:_id', jwt, update);

router.delete('/:_id', jwt, _delete);

  

module.exports  =  router;

  

function  getAll(req, res) {

    redirectService.getAll()

        .then(function (redirects) {

            res.send(redirects);

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

  

function  getById(req, res) {

    redirectService.getById(req.params._id)

        .then(function (redirect) {

            res.send(redirect);

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

  

function  create(req, res) {

    redirectService.create(req.body)

        .then(function () {

            res.sendStatus(200);

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

  

function  update(req, res) {

    redirectService.update(req.params._id, req.body)

        .then(function () {

            res.sendStatus(200);

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

  

function  _delete(req, res) {

    redirectService.delete(req.params._id)

        .then(function () {

            res.sendStatus(200);

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

All functions and operations in this file require authentication.

getAll() gets a list of all available redirects from the database and sends it as a response.

getById gets a single redirect matching the provided idfrom the request and sends it back as a response.

create() adds a new redirect and returns 200/OK status code upon successful creation.

update() edits and updates an existing redirect. The function identifies the redirect to be updated by matching the id and body in the request to existing ones in the database.

delete() removes an existing redirect and returns a 200/OK status code upon successful removal.

Users Controller

We are going to add a controller file to handle user operations and data.

Create a file in the api directory, users.controller.js. Add the following code

var  config  =  require('config.json');

var  express  =  require('express');

var  jwt  =  require('express-jwt')({ secret:  config.secret });

var  router  =  express.Router();

var  userService  =  require('services/user.service');

  

// routes

router.post('/authenticate', authenticateUser);

router.get('/current', jwt, getCurrentUser);

router.get('/:_id', jwt, getById);

router.put('/:_id', jwt, updateUser);

router.delete('/:_id', jwt, deleteUser);

  

module.exports  =  router;

  

function  authenticateUser(req, res) {

    userService.authenticate(req.body.username, req.body.password)

        .then(function (token) {

            if (token) {

                // authentication successful

                res.send({ token:  token });

            } else {

                // authentication failed

                res.status(401).send('Username or password is incorrect');

            }

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

  

function  getCurrentUser(req, res) {

    userService.getById(req.user.sub)

        .then(function (user) {

            if (user) {

                res.send(user);

            } else {

                res.sendStatus(404);

            }

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

  

function  getById(req, res) {

    userService.getById(req.params._id)

        .then(function (user) {

            if (user) {

                res.send(user);

            } else {

                res.sendStatus(404);

            }

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

  

function  updateUser(req, res) {

    var  userId  =  req.user.sub;

    if (req.params._id  !==  userId) {

        // can only update own account

        return  res.status(401).send('You can only update your own account');

    }

  

    userService.update(userId, req.body)

        .then(function () {

            res.sendStatus(200);

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

  

function  deleteUser(req, res) {

    var  userId  =  req.user.sub;

    if (req.params._id  !==  userId) {

        // can only delete own account

        return  res.status(401).send('You can only delete your own account');

    }

  

    userService.delete(userId)

        .then(function () {

            res.sendStatus(200);

        })

        .catch(function (err) {

            res.status(400).send(err);

        });

}

authenticateUser() requests for a username and a password. If both parameters match one existing in the database the function returns a secret token which will be supplied upon future requests for that session.

If the username or password is incorrect the function returns a 401 status and send a string alongside 'Username or password is incorrect'.

getCurrentUser() requests for the details of the current online user and compares it with the details of users existing in the database.

If a user with the details exist then the operation is successful else the function returns a 404/Not Found Error status.

getById() request for the user account that matches the provided id, if the user exists in the database the function returns the user.

updateUser() will help edit current user details and update it with new details.

The function requests the current user details and stores the value in a variable userId.

The function the checks the id from the HTTP request and compares it with the value of userId, if they don't match then it returns a 401 status with the string 'You can only update your own account'.

If they do match userService.update() runs and it updates the account matching userid with new details provided in the request body.

deleteUser() will help remove current user details from the database.

The function requests the current user details and stores the value in a variable userId.

The function the checks the id from the HTTP request and compares it with the value of userId, if they don't match then it returns a 401 status with the string 'You can only delete your own account'.

If they do match userService.delete() runs and it removes the account matching useridfrom the database.

We have come to the end of this tutorial, in the next tutorial we will add all service modules required for communication with the database.

  1. Building A Content Management System Using The MEAN Stack - 1 (Create Server, Config File and Helper Modules)

  2. Building A Content Management System Using The MEAN Stack - 2(Create Controller Modules 1)

Proof Of Work Done
https://github.com/olatundeee/mean-cms

Sort:  

Thank you for your contribution.
After analyzing your tutorial we suggest the following:

  • Your code has enough spaces between each line, making it difficult to read and understand the code. Ident your code.

Looking forward to your upcoming tutorials.

Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.
https://review.utopian.io/#modal1
To view those questions and the relevant answers related to your post, click here.


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Thank you for your review, @portugalcoin!

So far this week you've reviewed 16 contributions. Keep up the good work!

Hello! Your post has been resteemed and upvoted by @ilovecoding because we love coding! Keep up good work! Consider upvoting this comment to support the @ilovecoding and increase your future rewards! ^_^ Steem On!

Reply !stop to disable the comment. Thanks!

Hi @gotgame!

Your post was upvoted by @steem-ua, new Steem dApp, using UserAuthority for algorithmic post curation!
Your post is eligible for our upvote, thanks to our collaboration with @utopian-io!
Feel free to join our @steem-ua Discord server

Hey, @gotgame!

Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Get higher incentives and support Utopian.io!
Simply set @utopian.pay as a 5% (or higher) payout beneficiary on your contribution post (via SteemPlus or Steeditor).

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

Congratulations @gotgame! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :

Award for the number of upvotes

Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word STOP

Do not miss the last post from @steemitboard:

SteemFest³ - SteemitBoard support the Travel Reimbursement Fund.

Support SteemitBoard's project! Vote for its witness and get one more award!

Coin Marketplace

STEEM 0.35
TRX 0.12
JST 0.040
BTC 70733.96
ETH 3563.16
USDT 1.00
SBD 4.76