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

View this thread on: d.buzz | hive.blog | peakd.com | ecency.com
·@gotgame·
0.000 HBD
Building A Content Management System Using The MEAN Stack - 2 (Create Controller Modules 1)
### Repository

[https://github.com/nodejs/node](https://github.com/nodejs/node )

#### What Will I Learn

The codebase for this tutorial is based on the [MEANie](https://github.com/cornflourblue/meanie) an open source content management system by [Jason Watmore](http://jasonwatmore.com/).

In the first tutorial for this series we covered the creation of the web application server and all helper modules required for this application plus the database config file.

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

1. Admin Controller
2. Blog Controller
3. Install Controller
4. Login Controller

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

### Requirements

-    [NodeJS and NPM](https://nodejs.org/en/download/package-manager/),
-   [Angular](https://angular.io/)
-  [MongoDB](https://www.mongodb.com/)
-   Text Editor

### Difficulty

-   Intermediate

### Tutorial Contents

We already made reference to all controller modules in the `server.js` file, the controllers used in this application include all in the following list. 

-   Install Controller
-   Login Controller
-   Admin Controller
-   Blog Controller
-   Contact Form Controller
-   Pages Controller
-   Posts Controller
-   Redirect Controller
-   Users Controller

In this tutorial we will cover the first four and conclude the remainder in later tutorials.

To create the controller modules, in the server directory create a new directory with the name `controllers`.

#### 1. Admin Controller

The first controller we'll work on is the `admin` controller. In the `controllers` directory create a new file `admin.controller.js`.

In our newly created file add the following code

```
var  express  =  require('express');

var  router  =  express.Router();

var  path  =  require('path');

var  multer  =  require('multer');

var  slugify  =  require('helpers/slugify');

var  fileExists  =  require('helpers/file-exists');

  

router.use('/', ensureAuthenticated);

router.post('/upload', getUpload().single('upload'), upload); // handle file upload

router.use('/', express.static('../client/admin')); // serve admin front end files from '/admin'

  

module.exports  =  router;

  

/* ROUTE FUNCTIONS

---------------------------------------*/

  

function  upload(req, res, next) {

	// respond with ckeditor callback

	res.status(200).send(

		'<script>window.parent.CKEDITOR.tools.callFunction('  +  req.query.CKEditorFuncNum  +  ', "/_content/uploads/'  +  req.file.filename  +  '");</script>'

);

}

  
  

/* MIDDLEWARE FUNCTIONS

---------------------------------------*/

  

function  ensureAuthenticated(req, res, next) {

	// use session auth to secure the front end admin files

	if (!req.session.token) {

		return  res.redirect('/login?returnUrl='  +  encodeURIComponent('/admin'  +  req.path));

}

  

	next();

}

  

/* HELPER FUNCTIONS

---------------------------------------*/

  

function  getUpload() {

	// file upload config using multer

	var  uploadDir  =  '../client/blog/_content/uploads';

  

	var  storage  =  multer.diskStorage({

		destination:  uploadDir,

		filename:  function (req, file, cb) {

		var  fileExtension  =  path.extname(file.originalname);

		var  fileBase  =  path.basename(file.originalname, fileExtension);

		var  fileSlug  =  slugify(fileBase) +  fileExtension;

  

		// ensure file name is unique by adding a counter suffix if the file exists

		var  fileCounter  =  0;

		while (fileExists(path.join(uploadDir, fileSlug))) {

		fileCounter  +=  1;

		fileSlug  =  slugify(fileBase) +  '-'  +  fileCounter  +  fileExtension;

}

  

	cb(null, fileSlug);

}

});

	var  upload  =  multer({ storage:  storage });

  

	return  upload;

}
```

The admin controller handles all requests and returns the appropriate responses for each request made by the blog administrator.

The first part of this file `imports` all required dependencies for this controller module.

We import express using the `var  express  =  require('express');`.

To import the express router for app routing we add the following line of code

`var  router  =  express.Router();`

We need to use the path module also so we import that using `var  path  =  require('path');`.

To handle file uploads we need to import the `multer` dependency and the line `var  multer  =  require('multer');` will help us in that aspect.

On the next line we also import one of the helper modules `slugify` and we do that using the following line `var  slugify  =  require('helpers/slugify');`.

Finally we have another helper module `fileExists` imported using the `var  fileExists  =  require('helpers/file-exists');`.

To implement the express router module we added the block 

```
router.use('/', ensureAuthenticated);

router.post('/upload', getUpload().single('upload'), upload); // handle file upload

router.use('/', express.static('../client/admin')); // serve admin front end files from '/admin'
```
The first line `router.use('/', ensureAuthenticated);` ensures that the router is put to use in the admin area and the middleware function `ensureAuthenticated` which handles user authentication is implemented whenever a user tries to access the `admin` area of the application.

The next line `router.post('/upload', getUpload().single('upload'), upload);` ensures that every file uploaded by the admin passes  through the `/upload` router and is .

For each file uploaded through the `/upload` router, the `getUpload()` method is implemented. We'll go into the details of the `getUpload()` method in a bit.

The method `single(upload)` is a multer middleware that allows only one file to be uploaded per upload.

The last parameter `upload` is defined at the end of the  function.

`router.use('/', express.static('../client/admin'));` will set the `client/admin` directory as the front end folder for the area.

We also export the router module `module.exports  =  router;`.

We need to add the route functions for the admin area, to do that we create a new function `upload`.

The function has three parameters `req` which is the request object, `res` which is the response object and `next`  which helps to execute the next middleware after the current one.

Whenever the function accepts any request it returns a response. 

```
res.status(200).send(

	'<script>window.parent.CKEDITOR.tools.callFunction('  +  req.query.CKEditorFuncNum  +  ', "/_content/uploads/'  +  req.file.filename  +  '");</script>'

);
```
If the request is accepted and runs without any error it returns a status code of 200 and then proceed to send the response using the `send()` method.

The method sends an `API` call enclosed in a `<script></script>` tag. The call is performed on the CKEDITOR tool which is a text editor that is used in the application for adding, posting and uploading pages, posts and media contents.

This call is responsible for setting the upload route for uploaded images.

Earlier we made reference to a function `ensureAuthenticated` which uses session authentication to secure front end admin files

in the function we have an `if` statement with the condition `!req.session.token` which is actually interpreted as **if  there is no authentication token returned for the session**, the code in the curly braces will run. 

That is, the user is redirected to the login page whenever the authentication token runs.

The function `getUpload` accepts no parameters but it contains the configuration for each file upload process using the `multer` dependency.

In the `getUpload()` function  we have a variable `uploadDir` which sets the directory where the uploaded media will be saved.

We set up `multer`  by adding a new variable in the function `storage`. The variable is assigned an object value with an associated method `multer.diskStorage({})`.

In this method we set `destination` of the uploaded image to the value of `uploadDir` which was defined earlier.

Since multer doesn't handle addition of file extension automatically that has to be done manually.

This leads us to `filename` function with three parameters `req, file, cb` which will provide data for the file naming and extension.

We use the `path` module to define some of the required characteristics for naming and setting file extensions for our uploads.

To set these characteristics we add a first variable `fileExtension` whose value is the extension from the value of `file.originalname`. We make use of the method `path.extname()` in this case.

`var fileBase` is assigned a method `path.basename()`which will extract the uploaded file name from the upload path.

The extracted file name is then assigned to the method `slugify()` and concatenated with the value of `fileExtension`to create a unique SEO friendly name for the uploaded image.

We also need to make sure that each uploaded image has a unique name identified with only one image. 

In such cases where more than one image shares the same name we need to add a block of code that adds a counter suffix at the end of the file name for the later uploaded images in order to differentiate between the files.

For the purpose of differentiating described above, we add a new variable `fileCounter = 0`.

The while loop below this variable uses the `fileExists()` module to determine if a file with specified file name exists in the upload directory.

If it returns `true`, the value of `fileCounter` increases by one for that particular image.

The `fileSlug` variable for that image then gets a new value added after the file name, just before adding the `fileExtension`.

The counter and file name is also separated by the dash symbol.

The third variable parameter for `filename` i.e `cb` which is a callback function then accepts and returns the value `null` and the new value for the variable `fileSlug`.

A new variable is then created `upload` which stores the value of the `storage` variable created earlier in a property also known as `storage`.

The value of `upload` variable is returned by using `return upload` which brings us to the end of the `admin.controller.js` file.



#### 2. Blog Controller

The next controller we'll work on is the blog controller which will help us intercept  and disburse all requests from the blog section of this application.

To add our blog controller, we create a new file and call it `blog.controller.js`

In our file we have the following code

```
var  express  =  require('express');

var  _  =  require('lodash');

var  moment  =  require('moment');

var  path  =  require('path');

var  router  =  express.Router();

var  request  =  require('request');

var  fs  =  require('fs');

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

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

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

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

var  slugify  =  require('helpers/slugify');

var  pager  =  require('helpers/pager');

  

var  basePath  =  path.resolve('../client/blog');

var  indexPath  =  basePath  +  '/index';

var  metaTitleSuffix  =  "";

var  oneWeekSeconds  =  60  *  60  *  24  *  7;

var  oneWeekMilliseconds  =  oneWeekSeconds  *  1000;

  

/* STATIC ROUTES

---------------------------------------*/

  

router.use('/_dist', express.static(basePath  +  '/_dist'));

router.use('/_content', express.static(basePath  +  '/_content', { maxAge:  oneWeekMilliseconds }));

  

/* MIDDLEWARE

---------------------------------------*/

  

// check for redirects

router.use(function (req, res, next) {

	var  host  =  req.get('host');

	var  url  =  req.url.toLowerCase();

  

	// redirects entered into cms

	redirectService.getByFrom(url)

		.then(function (redirect) {

		if (redirect) {

		// 301 redirect to new url

		return  res.redirect(301, redirect.to);

		}

  

		next();

})

.catch(function (err) {

	vm.error  =  err;

	res.render(indexPath, vm);

});

});

  

// add shared data to vm

router.use(function (req, res, next) {

	var  vm  =  req.vm  = {};

  

	vm.loggedIn  =  !!req.session.token;

	vm.domain  =  req.protocol  +  '://'  +  req.get('host');

	vm.url  =  vm.domain  +  req.path;

  

	postService.getAll()

		.then(function (posts) {

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

		vm.posts  =  vm.loggedIn  ?  posts  :  _.filter(posts, { 'publish':  true });

  

		// add urls to posts

		vm.posts.forEach(function (post) {

		post.url  =  '/post/'  +  moment(post.publishDate).format('YYYY/MM/DD') +  '/'  +  post.slug;

		post.publishDateFormatted  =  moment(post.publishDate).format('MMMM DD YYYY');

});

  

	loadYears();

	loadTags();

  

	next();

})

.catch(function (err) {

	vm.error  =  err;

	res.render(indexPath, vm);

});

  

// load years and months for blog month list

function  loadYears() {

	vm.years  = [];

  

	// get all publish dates

	var  dates  =  _.pluck(vm.posts, 'publishDate');

  

// loop through dates and create list of unique years and months

	_.each(dates, function (dateString) {

	var  date  =  moment(dateString);

  

	var  year  =  _.findWhere(vm.years, { value:  date.format('YYYY') });

	if (!year) {

		year  = { value:  date.format('YYYY'), months: [] };

		vm.years.push(year);

}

  

	var  month  =  _.findWhere(year.months, { value:  date.format('MM') });

	if (!month) {

		month  = { value:  date.format('MM'), name:  moment(date).format('MMMM'), postCount:  1 };

		year.months.push(month);

} else {

	month.postCount  +=  1;

}

});

}

  

function  loadTags() {

	// get unique array of all tags

	vm.tags  =  _.chain(vm.posts)

		.pluck('tags')

		.flatten()

		.uniq()

		.sort()

		.filter(function (el) { return  el; }) // remove undefined/null values

		.map(function (tag) {

	return { text:  tag, slug:  slugify(tag) };

})

	.value();

}

});

  

/* ROUTES

---------------------------------------*/

  

// home route

router.get('/', function (req, res, next) {

	var  vm  =  req.vm;

  

	var  currentPage  =  req.query.page  ||  1;

	vm.pager  =  pager(vm.posts.length, currentPage);

	vm.posts  =  vm.posts.slice(vm.pager.startIndex, vm.pager.endIndex  +  1);

  

	render('home/index.view.html', req, res);

});

  

// post by id route (permalink used by disqus comments plugin)

router.get('/post', function (req, res, next) {

	var  vm  =  req.vm;

  

	if (!req.query.id) return  res.status(404).send('Not found');

  

	// find by post id or disqus id (old post id)

	var  post  =  _.find(vm.posts, function (p) {

	return  p._id.toString() ===  req.query.id;

});

  

	if (!post) return  res.status(404).send('Not found');

  

	// 301 redirect to main post url

	var  postUrl  =  '/post/'  +  moment(post.publishDate).format('YYYY/MM/DD') +  '/'  +  post.slug;

	return  res.redirect(301, postUrl);

});

  

// post details route

router.get('/post/:year/:month/:day/:slug', function (req, res, next) {

	var  vm  =  req.vm;

  

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

		.then(function (post) {

			if (!post) return  res.status(404).send('Not found');

  

			post.url  =  '/post/'  +  moment(post.publishDate).format('YYYY/MM/DD') +  '/'  +  post.slug;

			post.publishDateFormatted  =  moment(post.publishDate).format('MMMM DD YYYY');

			post.permalink  =  vm.domain  +  '/post?id='  +  post._id;

			vm.post  =  post;

  

	// add post tags and tag slugs to viewmodel

	vm.postTags  =  _.map(post.tags, function (tag) {

		return { text:  tag, slug:  slugify(tag) };

});

  

	// meta tags

	vm.metaTitle  =  vm.post.title  +  metaTitleSuffix;

	vm.metaDescription  =  vm.post.summary;

  

	render('posts/details.view.html', req, res);

})

	.catch(function (err) {

	vm.error  =  err;

	res.render(indexPath, vm);

});

});

  

// posts for tag route

router.get('/posts/tag/:tag', function (req, res, next) {

	var  vm  =  req.vm;

  

	// filter posts by specified tag

	vm.posts  =  _.filter(vm.posts, function (post) {

	if (!post.tags)

	return  false;

  

	// loop through tags to find a match

	var  tagFound  =  false;

	_.each(post.tags, function (tag) {
	
	var  tagSlug  =  slugify(tag);

	if (tagSlug  ===  req.params.tag) {

	// set vm.tag and title here to get the un-slugified version for display

	vm.tag  =  tag;

	tagFound  =  true;

  

	// meta tags

	vm.metaTitle  =  'Posts tagged "'  +  vm.tag  +  '"'  +  metaTitleSuffix;

	vm.metaDescription  =  'Posts tagged "'  +  vm.tag  +  '"'  +  metaTitleSuffix;

}

});

  

	return  tagFound;

});

  

// redirect to home page if there are no posts with tag

	if (!vm.posts.length)

	return  res.redirect(301, '/');

  

	render('posts/tag.view.html', req, res);

});

  

// posts for month route

router.get('/posts/:year/:month', function (req, res, next) {

	var  vm  =  req.vm;

  

	vm.year  =  req.params.year;

	vm.monthName  =  moment(req.params.year  +  req.params.month  +  '01').format('MMMM');

  

// filter posts by specified year and month

	vm.posts  =  _.filter(vm.posts, function (post) {

	return  moment(post.publishDate).format('YYYYMM') ===  req.params.year  +  req.params.month;

});

  

	// meta tags

	vm.metaTitle  =  'Posts for '  +  vm.monthName  +  ' '  +  vm.year  +  metaTitleSuffix;

	vm.metaDescription  =  'Posts for '  +  vm.monthName  +  ' '  +  vm.year  +  metaTitleSuffix;

  

	render('posts/month.view.html', req, res);

});

  

// page details route

router.get('/page/:slug', function (req, res, next) {

	var  vm  =  req.vm;

  

	pageService.getBySlug(req.params.slug)

		.then(function (page) {

			if (!page) return  res.status(404).send('Not found');

  

			vm.page  =  page;

  

			// meta tags

			vm.metaTitle  =  vm.page.title  +  metaTitleSuffix;

			vm.metaDescription  =  vm.page.description  +  metaTitleSuffix;

  

			render('pages/details.view.html', req, res);

})

.catch(function (err) {

	vm.error  =  err;

	res.render(indexPath, vm);

});

});

  

// archive route

router.get('/archive', function (req, res, next) {

	var  vm  =  req.vm;

  

	// meta tags

	vm.metaTitle  =  'Archive'  +  metaTitleSuffix;

	vm.metaDescription  =  'Archive'  +  metaTitleSuffix;

  

	render('archive/index.view.html', req, res);

});

  


/* PRIVATE HELPER FUNCTIONS

---------------------------------------*/

  

// render template

function  render(templateUrl, req, res) {

	var  vm  =  req.vm;

  

	vm.xhr  =  req.xhr;

	vm.templateUrl  =  templateUrl;

  

// render view only for ajax request or whole page for full request

	var  renderPath  =  req.xhr  ?  basePath  +  '/'  +  vm.templateUrl  :  indexPath;

	return  res.render(renderPath, vm);

}

  

// proxy file from remote url for page speed score

function  proxy(fileUrl, filePath, req, res) {

	// ensure file exists and is less than 1 hour old

	fs.stat(filePath, function (err, stats) {

	if (err) {

		// file doesn't exist so download and create it

		updateFileAndReturn();

	} else {

		// file exists so ensure it's not stale

		if (moment().diff(stats.mtime, 'minutes') >  60) {

		updateFileAndReturn();

	} else {

		returnFile();

}

}

});

  

// update file from remote url then send to client

function  updateFileAndReturn() {

	request(fileUrl, function (error, response, body) {

	fs.writeFileSync(filePath, body);

	returnFile();

});

}

  

// send file to client

function  returnFile() {

	res.set('Cache-Control', 'public, max-age='  +  oneWeekSeconds);

	res.sendFile(filePath);

}

}
```

So in our file we first of all import all of the dependencies that is required for this module. 

We import `express` first which is a generally required dependency module.

We import `lodash` to help perform special utility functions.

The `moment` module can be used to wrap a JavaScript date object into a moment object. It will come in handy while calculating date and time of blog posts.

We also import the `path` module for file path handling.

We add the Express Router by adding a variable `router`.

To make the handling of HTTP  requests easier we import a the NodeJS `request` module which does a better work than the traditional `http`.

We use the `fs` module to check for the existence of files in our application database, hence it is imported by assigning it to the variable `fs`.

We'll be needing the `config.json` file we crated in the earlier tutorials so we import that also.

Three variables `pageService` ,  `postService` and `redirectService` are also imported, all set the set the service files for pages, posts and redirects in our application as requirements in this module.

Both files are yet to be created, that will be covered in a later tutorial along with other `services` needed in the application.

We also import two of our helper files in the name of `slugify` and `pager`.

The variable `basePath` will set an absolute path for the blog section using the `path.resolve('../client/blog')` method.

`indexPath` uses the value of `basePath` concatenated with the with the string `/index` to specify a path to the subdirectory `index` in the blog section of the application.

```
router.use('/_dist', express.static(basePath  +  '/_dist'));

router.use('/_content', express.static(basePath  +  '/_content', { maxAge:  oneWeekMilliseconds }));
```

The block above will tell the server where the static front end files for the blog section is located.

In both methods `express.static()` uses the value of the variable `basePath` concatenated with `'/_dist'` and `'/_content'` respectively to set the path to the `'/_dist'` and `'/_content'` sub-directories.

We add a new function that helps us check for redirects in the application, the following block helps us get that done

```
router.use(function (req, res, next) {

	var  host  =  req.get('host');

	var  url  =  req.url.toLowerCase();

  

	// redirects entered into cms

	redirectService.getByFrom(url)

		.then(function (redirect) {

			if (redirect) {

			// 301 redirect to new url

			return  res.redirect(301, redirect.to);

}

  

	next();

})

	.catch(function (err) {

		vm.error  =  err;

		res.render(indexPath, vm);

});

});
```

What the above block does is that whenever a request is made from a page, the full `url` of the request page is gotten from the request body and is converted to lower case.

Once the full url has been gotten, it is then passed as parameter through a function created in the redirect service for our application `redirectService.getByFrom(url)`.

After the method must have run its course, then another function checks if the request is a redirect, this is determined from the result of running `redirectService.getByFrom(url)`.

If it returns true, the function returns a response consisting a `301` redirect and another value `redirect.to` which provides the destination being redirected to.

If it returns false the function returns an error.

Next we need to add shared data to the vm so the blog and admin can view published and all(published and unpublished) posts respectively.

```
router.use(function (req, res, next) {

	var  vm  =  req.vm  = {};

  

	vm.loggedIn  =  !!req.session.token;

	vm.domain  =  req.protocol  +  '://'  +  req.get('host');

	vm.url  =  vm.domain  +  req.path;

	vm.googleAnalyticsAccount  =  config.googleAnalyticsAccount;

  

	postService.getAll()

		.then(function (posts) {

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

		vm.posts  =  vm.loggedIn  ?  posts  :  _.filter(posts, { 'publish':  true });

  

		// add urls to posts

		vm.posts.forEach(function (post) {

			post.url  =  '/post/'  +  moment(post.publishDate).format('YYYY/MM/DD') +  '/'  +  post.slug;

			post.publishDateFormatted  =  moment(post.publishDate).format('MMMM DD YYYY');

});

  

	loadYears();

	loadTags();

  

	next();

})

	.catch(function (err) {

		vm.error  =  err;

		res.render(indexPath, vm);

});

  

// load years and months for blog month list

function  loadYears() {

	vm.years  = [];

  

	// get all publish dates

	var  dates  =  _.pluck(vm.posts, 'publishDate');

  

	// loop through dates and create list of unique years and months

	_.each(dates, function (dateString) {

		var  date  =  moment(dateString);

  

		var  year  =  _.findWhere(vm.years, { value:  date.format('YYYY') });

		if (!year) {

			year  = { value:  date.format('YYYY'), months: [] };

			vm.years.push(year);

}

  

	var  month  =  _.findWhere(year.months, { value:  date.format('MM') });

	if (!month) {

		month  = { value:  date.format('MM'), name:  moment(date).format('MMMM'), postCount:  1 };

		year.months.push(month);

	} else {

		month.postCount  +=  1;

}

});

}

  

function  loadTags() {

	// get unique array of all tags

	vm.tags  =  _.chain(vm.posts)

		.pluck('tags')

		.flatten()

		.uniq()

		.sort()

		.filter(function (el) { return  el; }) // remove undefined/null values

		.map(function (tag) {

			return { text:  tag, slug:  slugify(tag) };

})

.value();

}

});
```

We first of all set the parameters for the view model of the blog section of the application.

We set patterns for the view models of logged in users/admin, domain and url of the blog.

In order to collate all the posts stored in the database we make reference to a method created in the `postService` module `getAll()`. This method returns an array of all posts stored in the database.

After executing the `getAll()` function another function is run which filters the posts that can be seen by certain, if the user is logged in all posts are returned, otherwise only published posts are returned.

In order to set a pattern for each blog post url the `forEach()` function is implemented.

Given an array of posts, for each post we execute a function that sets the pattern for the post url which comprises of the string `/post/` added to the date the post was published followed by the post slug.

If any error occurs in the process, it is caught and rendered through the view.

Next is setting the routes for the blog section. the first route included is the home route 

```
router.get('/', function (req, res, next) {

	var  vm  =  req.vm;

  

	var  currentPage  =  req.query.page  ||  1;

	vm.pager  =  pager(vm.posts.length, currentPage);

	vm.posts  =  vm.posts.slice(vm.pager.startIndex, vm.pager.endIndex  +  1);

  

	render('home/index.view.html', req, res);

});
```

Using the router we set the home route using the `get()` method. The above function will render the file `home/index.view.html` as the home page.

```
// post by id route (permalink used by disqus comments plugin)

router.get('/post', function (req, res, next) {

	var  vm  =  req.vm;

  

	if (!req.query.id) return  res.status(404).send('Not found');

  

	// find by post id or disqus id (old post id)

	var  post  =  _.find(vm.posts, function (p) {

	return  p._id.toString() ===  req.query.id;

});

  

	if (!post) return  res.status(404).send('Not found');

  

	// 301 redirect to main post url

	var  postUrl  =  '/post/'  +  moment(post.publishDate).format('YYYY/MM/DD') +  '/'  +  post.slug;

	return  res.redirect(301, postUrl);

});
```

The block above will attempt to find a post through its unique `id`, if it happens that the post id doesn't exist in the database it returns a `404` error status.

Else, if the post exists the user is redirected using a 301 redirect to the post url where they can view the full contents of the post.

We also attempt to get the details for each post by getting the post date and slug and render the post detail through `posts/details.view.html` file.

The block below will help get that done

```
router.get('/post/:year/:month/:day/:slug', function (req, res, next) {

	var  vm  =  req.vm;

  

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

		.then(function (post) {

			if (!post) return  res.status(404).send('Not found');

  

			post.url  =  '/post/'  +  moment(post.publishDate).format('YYYY/MM/DD') +  '/'  +  post.slug;

			post.publishDateFormatted  =  moment(post.publishDate).format('MMMM DD YYYY');

			post.permalink  =  vm.domain  +  '/post?id='  +  post._id;

			vm.post  =  post;

  

			// add post tags and tag slugs to viewmodel

			vm.postTags  =  _.map(post.tags, function (tag) {

				return { text:  tag, slug:  slugify(tag) };

});

  

// meta tags

			vm.metaTitle  =  vm.post.title  +  metaTitleSuffix;

			vm.metaDescription  =  vm.post.summary;

  

			render('posts/details.view.html', req, res);

})

			.catch(function (err) {

				vm.error  =  err;

				res.render(indexPath, vm);

});

});
```

Using the post service the server requests for the year, month, day and slug for the post and use these values to execute a callback function.

The function tries to verify if the post in question exists, if it doesn't a `404` status error is returned else the function returns post url, the publish date, post permalink, post tags, post title and summary.

If an error occurs the post catches the error and renders it.

Just like we tried to find a post through its unique `id` we would like to find and filter posts by returning post with identical tags. 

```
router.get('/posts/tag/:tag', function (req, res, next) {

	var  vm  =  req.vm;

  

	// filter posts by specified tag

	vm.posts  =  _.filter(vm.posts, function (post) {

		if (!post.tags)

		return  false;

  

		// loop through tags to find a match

		var  tagFound  =  false;

		_.each(post.tags, function (tag) {

			var  tagSlug  =  slugify(tag);

			if (tagSlug  ===  req.params.tag) {

				// set vm.tag and title here to get the un-slugified version for display

			vm.tag  =  tag;

			tagFound  =  true;

  

			// meta tags

			vm.metaTitle  =  'Posts tagged "'  +  vm.tag  +  '"'  +  metaTitleSuffix;

			vm.metaDescription  =  'Posts tagged "'  +  vm.tag  +  '"'  +  metaTitleSuffix;

}

});

  

		return  tagFound;

});
```

For the tag filtering we set a route which renders posts after executing a callback function.

For each provided tag or tags, if there is no corresponding post the function returns as `false`. 

If there are posts matching the specified tags, the function loops through the tags and returns a list of post displaying the post title and summary.

The list is rendered on the front end through the `posts/tag.view.html` file.

Furthermore, if there are no posts with the specified the user is redirected to the homepage.

```
router.get('/posts/:year/:month', function (req, res, next) {

	var  vm  =  req.vm;

  

	vm.year  =  req.params.year;

	vm.monthName  =  moment(req.params.year  +  req.params.month  +  '01').format('MMMM');

  

	// filter posts by specified year and month

	vm.posts  =  _.filter(vm.posts, function (post) {

	return  moment(post.publishDate).format('YYYYMM') ===  req.params.year  +  req.params.month;

});

  

	// meta tags

	vm.metaTitle  =  'Posts for '  +  vm.monthName  +  ' '  +  vm.year  +  metaTitleSuffix;

	vm.metaDescription  =  'Posts for '  +  vm.monthName  +  ' '  +  vm.year  +  metaTitleSuffix;

  

	render('posts/month.view.html', req, res);

});
```

The block above will filter through all available posts and return the ones matching the specified month.

The route `/posts/:year/:month` indicates that the posts returned are from a specific year and month.

The callback function executed after will loop through a list of all posts and display the title and summary for the posts from a specified month which will be rendered through the `posts/month.view.html` file.

There is also the route for rendering the page details for each page. The code below handles that

```
router.get('/page/:slug', function (req, res, next) {

	var  vm  =  req.vm;

  

	pageService.getBySlug(req.params.slug)

		.then(function (page) {

			if (!page) return  res.status(404).send('Not found');

  
			vm.page  =  page;

  

			// meta tags

			vm.metaTitle  =  vm.page.title  +  metaTitleSuffix;

			vm.metaDescription  =  vm.page.description  +  metaTitleSuffix;

  

			render('pages/details.view.html', req, res);

})
		.catch(function (err) {

		vm.error  =  err;

		res.render(indexPath, vm);

});

});
```

In the callback function above, we use a method from the page service module `pageService.getBySlug(req.params.slug)` which returns the slug for the page in question.

If a page matching the slug is non-existent the function returns a 404 error. If it corresponds, the function returns the page, title and summary and renders it through the `pages/details.view.html`.

```
router.get('/archive', function (req, res, next) {

	var  vm  =  req.vm;

  

	// meta tags

	vm.metaTitle  =  'Archive'  +  metaTitleSuffix;

	vm.metaDescription  =  'Archive'  +  metaTitleSuffix;

  

	render('archive/index.view.html', req, res);

});
```

We use the block above to set the route for blog post archive. The callback function returns the page title and description for all published blog posts which is rendered through the `archive/index.view.html` file.

#### Install Controller

On first installation of our application, the initial user would need a login username and password to gain access to the admin area.

We need to create a controller module for that purpose, in our controller directory we will add a new file `install.controller.js`.

In the file we have, 

```
var  express  =  require('express');

var  router  =  express.Router();

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

var  fs  =  require("fs");

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

  

router.get('/', function (req, res) {

	if (config.installed) {

		return  res.sendStatus(401);

}

  

	return  res.render('install/index');

});

  

router.post('/', function (req, res) {

	if (config.installed) {

		return  res.sendStatus(401);

}

  

	// create user

	userService.create(req.body)

		.then(function () {

			// save installed flag in config file

			config.installed  =  true;

			fs.writeFileSync('./config.json', JSON.stringify(config));

  

			// return to login page with success message

			req.session.success  =  'Installation successful, you can login now.';

			return  res.redirect('/login');

})

		.catch(function (err) {

		return  res.render('install/index', { error:  err });

});

});

  

module.exports  =  router;
```

In this file the we  utilize the following dependencies `express`, `express router`, `fs`.

We also require the `config` and `userService` module to achieve our objectives.

```
router.get('/', function (req, res) {

	if (config.installed) {

		return  res.sendStatus(401);

}

  

	return  res.render('install/index');

});
```

The block above checks if `config.installed` equals true. If `true` the function returns a `401` unauthorized status code.

The function renders the `install/index.html` page.

```
userService.create(req.body)

	.then(function () {

		// save installed flag in config file

		config.installed  =  true;

		fs.writeFileSync('./config.json', JSON.stringify(config));

  

		// return to login page with success message

		req.session.success  =  'Installation successful, you can login now.';

		return  res.redirect('/login');

})

		.catch(function (err) {

		return  res.render('install/index', { error:  err });

});
```

If `config.installed` equals false and a new user is created the function changes `config.installed = true`.

The function then redirects the user to the login pageFor .

#### Login Controller

Admin would need to login to the login area of our application, the login feature would also require a controller feature. 

In the controller directory, create a new file `login.controller.js`. In the file add the following code

```
var  express  =  require('express');

var  router  =  express.Router();

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

  

router.get('/', function (req, res) {

	// log user out

	delete  req.session.token;

  

	// move success message into local variable so it only appears once (single read)

	var  viewData  = { success:  req.session.success };

	delete  req.session.success;

  

	res.render('login/index', viewData);

});

  

router.post('/', function (req, res) {

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

		.then(function (token) {

			// authentication is successful if the token parameter has a value

			if (token) {

				// save JWT token in the session to make it available to the angular app

				req.session.token  =  token;

  

// redirect to returnUrl

				var  returnUrl  =  req.query.returnUrl  &&  decodeURIComponent(req.query.returnUrl) ||  '/admin';

				return  res.redirect(returnUrl);

			} else {

				return  res.render('login/index', { error:  'Username or password is incorrect', username:  req.body.username });

}

})

		.catch(function (err) {

			console.log('error on login', err);

			return  res.render('login/index', { error:  err });

});

});

  

module.exports  =  router;
```

We import the `express` dependency, use the `express` router and the user service module to reach our objectives in this file.

The callback function in our router handles thwe admin login and logout the application.

The line `delete  req.session.token;` will log any logged in user out of the admin area.

The user is returned to the login page and out of the admin area upon successful logout.

The processes described above are made possible by the block below

```
router.get('/', function (req, res) {

	// log user out

	delete  req.session.token;

  

	// move success message into local variable so it only appears once (single read)

	var  viewData  = { success:  req.session.success };

	delete  req.session.success;

  

	res.render('login/index', viewData);

});
```

Whenever a user attempts to login to the admin area, the block of code below is implemented.

```
router.post('/', function (req, res) {

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

		.then(function (token) {

			// authentication is successful if the token parameter has a value

			if (token) {

				// save JWT token in the session to make it available to the angular app

				req.session.token  =  token;

  

				// redirect to returnUrl

				var  returnUrl  =  req.query.returnUrl  &&  decodeURIComponent(req.query.returnUrl) ||  '/admin';

			return  res.redirect(returnUrl);

		} else {

			return  res.render('login/index', { error:  'Username or password is incorrect', username:  req.body.username });

}

})

		.catch(function (err) {

			console.log('error on login', err);

			return  res.render('login/index', { error:  err });

});

});
```

From the `userService` module the function is authenticated to confirm the provided username and password.

Upon successful authentication the function checks if `token`parameter has a value.

If the `token` parameter statement returns true the user is redirected to the admin area else the user receives an error message on the login page `'Username or password is incorrect'`.

We have come to the end of this tutorial, in the next tutorial we will continue with and conclude all controller modules needed for our application.

#### Curriculum

1.  [Building A Content Management System Using The MEAN Stack - 1 (Create Server, Config File and Helper Modules)](https://steemit.com/utopian-io/@gotgame/5b98i4-building-a-content-management-system-using-the-mean-stack-1-create-server-config-file-and-helper-modules)
2.  [Simple Shopping Cart Using Vue.js and Materialize - 2](https://steemit.com/utopian-io/@gotgame/simple-shopping-cart-using-vue-js-and-materialize-2)
3.  [Simple Shopping Cart Using Vue.js and Materialize - 1](https://steemit.com/utopian-io/@gotgame/simple-shopping-cart-using-vue-js-and-materialize)

#### Proof Of Work Done

[https://github.com/olatundeee/mean-cms](https://github.com/olatundeee/mean-cms )
👍 , , , , , , , , , , , , , , , , , , , , , , ,