Click "Preferences..."

Expand "Languages & Frameworks", choose "Code Sniffer", click "...." button.

Enter your PHP Code Sniffer path, then click "Validate"

Expand "Editor", choose "Inspections", check "PHP Code Sinffer validation", click "OK"

Open any php file and check:

PHP_CodeSniffer is a set of two PHP scripts; the main phpcs script that tokenizes PHP, JavaScript and CSS files to detect violations of a defined coding standard, and a second phpcbf script to automatically correct coding standard violations. PHP_CodeSniffer is an essential development tool that ensures your code remains clean and consistent.



Download PEAR
$ curl -O http://pear.php.net/go-pear.phar
$ sudo php -d detect_unicode=0 go-pear.phar

Below is a suggested file layout for your new PEAR installation.  To change individual locations, type the number in front of the directory.  Type 'all' to change all of them or simply press Enter to
accept these locations.
 1. Installation base ($prefix): /usr/local/pear
 2. Temporary directory for processing: /tmp/pear/install
 3. Temporary directory for downloads: /tmp/pear/install
 4. Binaries directory: /usr/local/pear/bin
 5. PHP code directory ($php_dir) : /usr/local/pear/share/pear
 6. Documentation directory: /usr/local/pear/docs
 7. Data directory: /usr/local/pear/data
 8. User-modifiable configuration files directory : /usr/local/pear/cfg
 9. Public Web Files directory: /usr/local/pear/www
10. System manual pages directory: /usr/local/pear/man
11. Tests directory: /usr/local/pear/tests
12. Name of configuration file: /private/etc/pear.conf

After download, install and configure, you should check the version by the command:
$ pear version

Run the following command to install PHP_CodeSniffer
$ sudo pear install PHP_CodeSniffer

To verify phpcs works:
$ phpcs example.php

If you receive the following error:
Warning: include_once(PHP/CodeSniffer/autoload.php): failed to open stream: No such file or directory in /usr/local/bin/phpcs on line 14
Warning: include_once(): Failed opening 'PHP/CodeSniffer/autoload.php' for inclusion (include_path='.:') in /usr/local/bin/phpcs on line 14
Fatal error: Class 'PHP_CodeSniffer\Runner' not found in /usr/local/bin/phpcs on line 17

Please add to php.ini
$ sudo vim /etc/php.ini
this line:
include_path = ".:/usr/local/pear/share/pear"


The Tor software protects you by bouncing your communications around a distributed network of relays run by volunteers all around the world: it prevents somebody watching your Internet connection from learning what sites you visit, it prevents the sites you visit from learning your physical location, and it lets you access sites which are blocked.

Tor Browser lets you use Tor on Microsoft Windows, Apple MacOS, or GNU/Linux without needing to install any software. It can run off a USB flash drive, comes with a pre-configured web browser to protect your anonymity, and is self-contained (portable).


To install it on Ubuntu via PPA, run the following commands:
$ sudo add-apt-repository ppa:webupd8team/tor-browser
$ sudo apt-get update
$ sudo apt-get install tor-browser
Get Microsoft's Core Fonts for the Web and Cleartype Fonts

For Redhat, Fedora, Centos, SUSE, Mandrake, Yellowdog, and any linux distro that uses RPM

The Fonts

Go to wikipedia for more info: http://en.wikipedia.org/wiki/Core_fonts_for_the_Web
Microsoft's webpage on this: http://www.microsoft.com/typography/fonts/web.aspx
Microsoft's webpage on the ClearType fonts: http://www.microsoft.com/typography/ClearTypeFonts.mspx

How to INSTALL

Make sure the prereqs are there.  Except for cabextract, these prereqs are likely already installed on your system.

$ yum install curl cabextract xorg-x11-font-utils fontconfig

Install the fonts
$ rpm -i https://downloads.sourceforge.net/project/mscorefonts2/rpms/msttcore-fonts-installer-2.6-1.noarch.rpm

Read more: http://mscorefonts2.sourceforge.net/
RawGit serves raw files directly from GitHub with proper Content-Type headers.

In production


  • No traffic limits or throttling. Files are served via StackPath's super fast global CDN.
  • Use a specific tag or commit hash in the URL (not a branch). Files are cached permanently based on the URL. Query strings are ignored.
  • The catch: this is a free service, so there are no uptime or support guarantees.

For development


  • New changes you push to GitHub will be reflected within minutes.
  • Excessive traffic will be throttled and blacklisted.
  • If excessive traffic continues, RawGit will display a prominent error message on your website to try to get your attention.

Why is this necessary? Can't I just load files from GitHub directly?

When you request certain types of files (like JavaScript, CSS, or HTML) from raw.githubusercontent.com or gist.githubusercontent.com, GitHub serves them with a Content-Type header set to text/plain. As a result, most modern browsers won't actually interpret these files as JavaScript, CSS, or HTML and will instead just display them as text.

GitHub does this because serving raw files from a git repo is inefficient and they want to discourage people from using their GitHub repos for static file hosting.

RawGit acts as a caching proxy. It forwards requests to GitHub, caches the responses, and relays them to your browser with an appropriate Content-Type header based on the extension of the file that was requested. The caching layer ensures that minimal load is placed on GitHub, and you get quick and easy static file hosting right from a GitHub repo. Everyone's happy!

Read more http://rawgit.com/

Display the message from system

In part 2, we already logged the message `[DISPLAY NAME] has joined.`
In the HTML, we have 3 fields: "Display name", "Create at" and "Body":

so we update the source code:

I don't want to load the page after user clicks "Join now" button, so I copy the code from chat.html to index.html (of course, remove dynamic content). I use Moment.js and Mustache.js

  • Moment.js: parse, validate, manipulate, and display dates and times in JavaScript.
  • Mustache.js: a logic-less template syntax. It can be used for HTML, config files, source code - anything. It works by expanding tags in a template using values provided in a hash or object.


Update on newMessage code:

Open browser and test, Join with name "Alice":

Now, let's dynamic name of the chat room!

Dynamic name of the chat room




Display the message from users

On form submit, we send the message to server:


Server listen and generate message:

We create "users" variable to store all users for each room.

Use class or not, it depends on yourself.

Daisy joins room #KIDS???
==> room #MOM doesn't take care :D

Show "people in chat room"

When a user joined, the server sends the "people in chat room" list to client:


and when user disconnected, the server also sends the "people in chat room" list to client:

On client side:



Open your browser and test:


We already had some issues with the scroll bar when received new messages and we didn't check the unique display name. We will do in part 4. Now, get the latest code for today:

server.js
var express=require('express');const path=require('path');const http=require('http');const socketIO=require('socket.io');const _=require('lodash');const validator=require('validator');const port=process.env.PORT||3000;var app=express();app.use(express.static(path.join(__dirname,'./public')));var server=http.createServer(app);var io=socketIO(server);generateMessage=(from,body)=>{return{from,body,createdAt:new Date().getTime()}};class Users{constructor(){this.users=[];} addUser(id,display_name,room){var user={id,display_name,room};this.users.push(user);return user;} getUser(id){return this.users.filter((user)=>user.id===id)[0]} removeUser(id){var user=this.getUser(id);if(user){this.users=this.users.filter((user)=>user.id!==id);} return user;} getUsers(room){var users=this.users.filter((user)=>user.room===room);return users.map((user)=>user.display_name);}} var users=new Users();io.on('connection',(socket)=>{console.log('New user connected');socket.on('join',(params,callback)=>{var isRoomValid=_.isString(params.room)&&validator.isIn(params.room,['mom','kids','sell_off']);var isDisplaynameValid=_.isString(params.display_name)&&!validator.isEmpty(params.display_name.trim())&&validator.isAlpha(params.display_name)&&validator.isLength(params.display_name,{min:3,max:15});if(!isRoomValid){return callback('Please choose a room from dropdown list.');} if(!isDisplaynameValid){return callback('Please enter a valid display name (3-15 alphabetic characters).');} socket.join(params.room);users.removeUser(socket.id);users.addUser(socket.id,params.display_name,params.room);io.to(params.room).emit('updateUserList',users.getUsers(params.room));io.to(params.room).emit('newMessage',generateMessage('System',`${params.display_name}has joined.`));callback();});socket.on('createMessage',(message,callback)=>{var user=users.getUser(socket.id);if(user&&!validator.isEmpty(message.trim())&&validator.isLength(message,{min:3,max:160})){io.to(user.room).emit('newMessage',generateMessage(user.display_name,message));} callback();});socket.on('disconnect',()=>{var user=users.removeUser(socket.id);if(user){io.to(user.room).emit('updateUserList',users.getUsers(user.room));io.to(user.room).emit('newMessage',generateMessage('System',`${user.display_name}has left.`));}});});server.listen(port);

index.html
<!DOCTYPE html><html><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Join</title><link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"><link rel="stylesheet" href="/css/style.css"></head><body><div class="join-wrapper" id="join-wrapper"><form class="form-horizontal" id="frm-join"><h2 class="text-center">Please Join</h2><div id="join-error-msg" class="alert alert-danger hidden" role="alert"></div><div class="form-group"> <label for="txt-display-name" class="col-sm-4 control-label">Display name</label><div class="col-sm-8"> <input type="text" required class="form-control" id="txt-display-name"></div></div><div class="form-group"> <label for="sl-room" class="col-sm-4 control-label">Room</label><div class="col-sm-8"> <select class="form-control" id="sl-room"><option value="mom">Mom</option><option value="kids">Kids</option><option value="sell_off">Sell off</option> </select></div></div><div class="form-group"><div class="col-sm-offset-4 col-sm-8"> <button type="submit" class="btn btn-primary">Join now</button></div></div></form></div><div class="page-wrapper hidden" id="chat-room-wraper"><div class="container"><div class="row"><div class="col-md-9 col-sm-9"><div class="green-bg"><div class="title main" id="chat-room-name"></div><div id="messages"></div><div id="chat-box"><form id="frm-chat"><div class="input-group"> <input type="text" id="txt-message" class="form-control"> <span class="input-group-btn"> <button class="btn btn-primary" type="submit">Send</button> </span></div></form></div></div></div><div class="col-md-3 col-sm-3 sidebar"><div class="green-bg"><div class="title"> People in chatroom</div><ul class="list-group" id="users"></ul></div></div></div></div></div> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/2.3.0/mustache.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script> <script type="text/javascript" src="/socket.io/socket.io.js"></script> <script type="text/template" id="message-template"><div class="item"><p class="author">{{from}}<span class="date">({{createdAt}})</span></p><p>{{body}}</p></div></script> <script type="text/template" id="users-template"><li class="list-group-item">{{name}}</li></script> <script>var socket=io();socket.on('newMessage',function(message){var html=Mustache.render($('#message-template').html(),{from:message.from,body:message.body,createdAt:moment(message.createdAt).format('MMM Do, YYYY HH:mm')});$('#messages').append(html);});var selectedRoom;$(function(){$('#frm-join').on('submit',function(e){e.preventDefault();selectedRoom=$('#sl-room option:selected').val();socket.emit('join',{room:selectedRoom,display_name:$('#txt-display-name').val()},function(err){if(err){$('#join-error-msg').text(err);$('#join-error-msg').removeClass('hidden');} else{$('#chat-room-name').text('#'+$('#sl-room option:selected').text());$('#join-wrapper').remove();$('#chat-room-wraper').removeClass('hidden');}});});$('#frm-chat').on('submit',function(e){e.preventDefault();socket.emit('createMessage',$('#txt-message').val(),function(){$('#txt-message').val('');});});socket.on('updateUserList',function(users){$('#users').html('');users.forEach(function(user){var html=Mustache.render($('#users-template').html(),{name:user});$('#users').append(html);});});});</script> </body></html>

See you then!

Discover Guake Terminal

Guake is a top-down terminal for Gnome, and is highly inspirated by the famous terminal used in Quake .

You can instantaneously show and hide your terminal with a single key stroke, execute a command, and then go back to your previous task without breaking your workflow.

What does Guake look like?

Your Terminal Anywhere On Your Desktop, Just A Shortcut Away!

Imagine you are working in your favorite text editor and want to execute some commands, like execute the unit test of your code, check a man page, or edit some configuration file. You can do it at lightning speed without leaving your keyboard. Just press your predefined "Show Guake" hotkey, execute your command, and repress it to hide the terminal and go back to your work.

Tell me More!

Simple Easy, Elegant

Smooth integration of the terminal into the GNOME environment, with Compiz Transparency and show animation.

Multi-monitor

Guake supports Multimonitor setup. Open it on the monitor where your mouse is, or in a dedicated screen.

Multiple Tabs

Use Several named tabs, with names automatically set from the running command, or easily customized.

Get instant access to your tabs with custom shortcut.

Quick Open

See a filename in your terminal output? Simply click on it and it will be opened directly in your favorite Text Editor. It can even be scroll at the very specific line and column if your editor support it!

Autoconfigure Guake

Start Guake automatically at login, and define a script that will be executed on Guake launch, in order to configure Guake tabs.

This is temporary solution waiting for the save session feature.

Plenty of Color palettes

More than 130 predefined palettes are provided! You can easily visualize them prior to choosing the color that is best for you.

Read more: https://github.com/Guake/guake
At first, install Lodash and Validator packages, run the following command:
$ npm install lodash validator --save

Read more http://blog.alphaplus.vn/2017/09/build-simple-blog-with-nodejs-express-mongodb-part-6.html


The error message will be:

and the success message will be:
(Alice receives the message when Clara joined because they are in the same room #mom)

To do this, we add the id to the form, add div tag to show the error message, change the button type to submit, and add jQuery.

Add the following to index.html (before closing body tag):

<!DOCTYPE html><html><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Join</title><link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"><link rel="stylesheet" href="/css/style.css"></head><body><div class="join-wrapper"><form class="form-horizontal" id="frm-join"><h2 class="text-center">Please Join</h2><div id="join-error-msg" class="alert alert-danger hidden" role="alert"></div><div class="form-group"> <label for="txt-display-name" class="col-sm-4 control-label">Display name</label><div class="col-sm-8"> <input type="text" required class="form-control" id="txt-display-name"></div></div><div class="form-group"> <label for="sl-room" class="col-sm-4 control-label">Room</label><div class="col-sm-8"> <select class="form-control" id="sl-room"><option value="mom">Mom</option><option value="kids">Kids</option><option value="sell_off">Sell off</option> </select></div></div><div class="form-group"><div class="col-sm-offset-4 col-sm-8"> <button type="submit" class="btn btn-primary">Join now</button></div></div></form></div> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> <script type="text/javascript" src="/socket.io/socket.io.js"></script> <script>var socket=io();socket.on('newMessage',function(message){console.log(message);});var selectedRoom;$(function(){$('#frm-join').on('submit',function(e){e.preventDefault();selectedRoom=$('#sl-room option:selected').val();console.log('Selected room:',selectedRoom);socket.emit('join',{room:selectedRoom,display_name:$('#txt-display-name').val()},function(err){if(err){$('#join-error-msg').removeClass('hidden');$('#join-error-msg').text(err);}});});});</script> </body></html>

and to server.js

var express=require('express');const path=require('path');const http=require('http');const socketIO=require('socket.io');const _=require('lodash');const validator=require('validator');const port=process.env.PORT||3000;var app=express();app.use(express.static(path.join(__dirname,'./public')));var server=http.createServer(app);var io=socketIO(server);io.on('connection',(socket)=>{console.log('New user connected');socket.on('join',(params,callback)=>{var isRoomValid=_.isString(params.room)&&validator.isIn(params.room,['mom','kids','sell_off']);var isDisplaynameValid=_.isString(params.display_name)&&!validator.isEmpty(params.display_name.trim())&&validator.isAlpha(params.display_name)&&validator.isLength(params.display_name,{min:3,max:15});if(!isRoomValid){return callback('Please choose a room from dropdown list.');} if(!isDisplaynameValid){return callback('Please enter a valid display name (3-15 alphabetic characters).');} socket.join(params.room);io.to(params.room).emit('newMessage',`${params.display_name}has joined.`);callback();});});server.listen(port);

In part 3, we will integrate with HTML to display as the screenshot:

See you then!

A delightful community-driven (with 1,000+ contributors) framework for managing your zsh configuration. Includes 200+ optional plugins (rails, git, OSX, hub, capistrano, brew, ant, php, python, etc), over 140 themes to spice up your morning, and an auto-update tool so that makes it easy to keep up with the latest updates from the community. http://ohmyz.sh/

Oh-My-Zsh is an open source, community-driven framework for managing your ZSH configuration. It comes bundled with a ton of helpful functions, helpers, plugins, themes, and a few things that make you shout...

Sounds boring. Let's try again.

Oh My Zsh will not make you a 10x developer...but you might feel like one.

Once installed, your terminal shell will become the talk of the town or your money back! With each keystroke in your command prompt, you'll take advantage of the hundreds of powerful plugins and beautiful themes. Strangers will come up to you in cafés and ask you, "that is amazing! are you some sort of genius?"

Finally, you'll begin to get the sort of attention that you have always felt you deserved. ...or maybe you'll use the time that you're saving to start flossing more often.

Basic Installation

Oh My Zsh is installed by running one of the following commands in your terminal. You can install this via the command-line with either curl or wget.

via curl
sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

via wget
sh -c "$(wget https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)"

At first, we need to install Node.js, Express, nodemon.
Then we create a chat-room application (same as create a blog application).
Read more http://blog.alphaplus.vn/2017/08/build-simple-blog-with-nodejs-express-mongodb-part-1.html

In this tutorial, we use Socket.IO package https://www.npmjs.com/package/socket.io.
Socket.IO enables real-time bidirectional event-based communication. It consists in:

  • a Node.js server (this repository)
  • a Javascript client library for the browser (or a Node.js client)

Run the following command to install:
$ npm install socket.io --save

We also use http and path packages. Run the following command to install:
$ npm install path http --save

We will build a web application with two screens:

One to join the room:

and one to chat:

Let's build the simple HTML code for these screens or use the following:


HTML
index.html
<!DOCTYPE html><html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Join</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> <link rel="stylesheet" href="/css/style.css"> </head> <body> <div class="join-wrapper"> <form class="form-horizontal"> <h2 class="text-center">Please Join</h2> <div class="form-group"> <label for="txt-display-name" class="col-sm-4 control-label">Display name</label> <div class="col-sm-8"> <input type="text" required class="form-control" id="txt-display-name"> </div></div><div class="form-group"> <label for="sl-room" class="col-sm-4 control-label">Room</label> <div class="col-sm-8"> <select class="form-control" id="sl-room"> <option value="mom">Mom</option> <option value="kids">Kids</option> <option value="sell_off">Sell off</option> </select> </div></div><div class="form-group"> <div class="col-sm-offset-4 col-sm-8"> <button type="submit" class="btn btn-primary">Join now</button> </div></div></form> </div></body></html>

chat.html
<!DOCTYPE html><html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Chat</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> <link rel="stylesheet" href="/css/style.css"> </head> <body> <div class="page-wrapper"> <div class="container"> <div class="row"> <div class="col-md-9 col-sm-9"> <div class="green-bg"> <div class="title main">#Mom</div><div id="messages"> <div class="item"> <p class="author"> Alice <span class="date">(Sep 4th, 2017 15:00)</span> </p><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p></div><div class="item"> <p class="author"> Barbara <span class="date">(Sep 4th, 2017 15:01)</span> </p><p>Vestibulum gravida sit amet orci vitae tincidunt.</p></div><div class="item"> <p class="author"> Clara <span class="date">(Sep 4th, 2017 15:02)</span> </p><p>Nam sed risus et urna vulputate rhoncus a id nisi.</p></div><div class="item"> <p class="author"> Daisy <span class="date">(Sep 4th, 2017 15:03)</span> </p><p>Nullam massa mi, ultricies et fermentum a, laoreet sed lorem.</p></div><div class="item"> <p class="author"> Elizabeth <span class="date">(Sep 4th, 2017 15:04)</span> </p><p>Ut quis elit sit amet lacus tincidunt maximus sit amet eu massa.</p></div><div class="item"> <p class="author"> Becky <span class="date">(Sep 4th, 2017 15:05)</span> </p><p>Sed sollicitudin convallis tellus, a interdum magna ullamcorper nec.</p></div></div><div id="chat-box"> <div class="input-group"> <input type="text" class="form-control"> <span class="input-group-btn"> <button class="btn btn-primary" type="button">Send</button> </span> </div></div></div></div><div class="col-md-3 col-sm-3 sidebar"> <div class="green-bg"> <div class="title"> People in chatroom </div><ul class="list-group" id="users"> <li class="list-group-item">Alice</li><li class="list-group-item">Alison</li><li class="list-group-item">Annabelle</li><li class="list-group-item">Barbara</li><li class="list-group-item">Becky</li><li class="list-group-item">Britney</li><li class="list-group-item">Carol</li><li class="list-group-item">Christina</li><li class="list-group-item">Clara</li><li class="list-group-item">Daisy</li><li class="list-group-item">Dorothy</li><li class="list-group-item">Elizabeth</li><li class="list-group-item">Ellen</li><li class="list-group-item">Emily</li></ul> </div></div></div></div></div></body></html>

CSS
style.css
body{background-color: #eee;}.join-wrapper{position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); min-width: 320px; background: #fefefe; padding: 15px; border-radius: 5px; box-shadow: 6px 6px 5px #888888;}.page-wrapper{position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);}.page-wrapper ul{margin-bottom: 0;}.page-wrapper .title{color: #31708f; padding: 10px 15px; margin: 0; font-weight: 600; font-size: 24px;}.page-wrapper .title.main{text-align: center; color: #fff; background-color: #337ab7; border: 1px solid #337ab7; text-transform: uppercase;}.page-wrapper .green-bg{background-color: #d9edf7; border: 1px solid #ddd;}.page-wrapper #messages{height: 316px; overflow: scroll; background: #fff; color: #000; padding: 15px;}.page-wrapper #messages .item{padding-bottom: 15px;}.page-wrapper #messages .item p{margin-bottom: 0; font-size: 15px; word-wrap: break-word;}.page-wrapper #messages .item .author{font-weight: 600;}.page-wrapper #messages .item .author .date{font-size: 11px; font-style: italic; color: #898989; font-weight: 400;}.page-wrapper .sidebar #users{height: 350px; overflow-y: scroll;}

You can use this website to unminify JS, CSS, HTML http://unminify.com/

After the connection has been opened, we use "emit" method to send and "on" method to listen.

For example when the connection has been opened, the server send (emit method) the message "Hello!":

and the client listen (on method), then write log to console:

The result on the browser:



"on" and "emit" methods can be used on both client and server.



That's it for today.

Part 2: http://blog.alphaplus.vn/2017/09/build-simple-chat-room-with-nodejs-socketio-and-express-part-2.html
See you then!

At first, I want to introduce with you Lodash package https://www.npmjs.com/package/lodash

Lodash is a modern JavaScript utility library delivering modularity, performance & extras.

Why Lodash?

Lodash makes JavaScript easier by taking the hassle out of working with arrays, numbers, objects, strings, etc.
Lodash’s modular methods are great for:
  • Iterating arrays, objects, & strings
  • Manipulating & testing values
  • Creating composite functions
Read the docs https://lodash.com/docs

Apply lodash to our project

Run the following command to install lodash:
$ npm install lodash --save

We use _.pick(object, [paths]) to create an object composed of the picked object properties.

Add the following to server.js:
const _ = require('lodash');

Modify the code for POST and PATCH methods.
Change:
to:
var post = new Post(_.pick(req.body, ['title', 'content']));
and:
$set: _.pick(req.body, ['title', 'content'])

We'll also use lodash in other cases later. Now, I want to assign some tasks:

  1. Each post has the status published or unpublished (maybe more statuses in the future), default is unpublished.
    1. Only display a list of published posts for "GET /posts"
    2. Only display a specific published post for "GET /posts/:id"
  2. Only owner can use the "POST /posts", "PATCH /posts/:id" and "DELETE /posts/:id" routes.

Each post has the status published or unpublished

Add status key to Post:
status: {
  type: Number,
  enum: [0, 1],
  default: 0
}


  • enum: Array, creates a validator that checks if the value is in the given array. In the future, we will add more status to "enum"; now only 0 (unpublished) and 1 (published).
  • default: Any or function, sets a default value for the path. If the value is a function, the return value of the function is used as the default.
Update "POST /posts", "PATCH /posts/:id" routes:


then use Postman to test:

and use Robomongo to check the data:

It's the preparation for our tasks. Now, let's do them.

Display a list of published posts

It's easy, inside find(), add {status: 1}

Use Postman to test:

Display a specific published post

It's easy, inside findOne(), add "status: 1"

Use Postman to test:

Only owner can use the "POST /posts", "PATCH /posts/:id" and "DELETE /posts/:id"

To resolve this task, we will attach "X-CSRF-Token" to header when call each route.
To create and verify token we use jsonwebtoken package https://www.npmjs.com/package/jsonwebtoken
Run the following command to install:
$ npm install jsonwebtoken --save

We will create "POST /users/login" with email and password. If authenticated, we will receive the token to attach to header.
To validate email (and others in the future), we use validator package https://www.npmjs.com/package/validator
Run the following command to install:
$ npm install validator --save

The library to help you hash passwords in this tutorial is bcryptjs https://www.npmjs.com/package/bcryptjs.
Run the following command to install:
$ npm install bcrypt --save

Add the following to server.js:
const validator = require('validator');
const bcryptjs = require('bcryptjs');
const jwt = require('jsonwebtoken');


and the Schema for User:
var UserSchema = new mongoose.Schema({
  email: {
    type: String,
    trim: true,
    required: true,
    unique: true,
    minlength: 3,
    validate: {
      validator: validator.isEmail,
      message: '{VALUE} is not a valid email'
    }
  },
  salt: {
    type: String,
    trim: true,
    required: true
  },
  password: {
    type: String,
    trim: true,
    required: true,
    minlength: 6
  },
  tokens: [
    {
      token: {
        type: String,
        required: true
      },
      expired: {
        type: Number
      }
    }
  ]
});
var User = mongoose.model('User', UserSchema);


Look at the code above:
  • We use Schema, don't like the way with Post => We will use UserSchema.statics.* and Middleware in this tutorial
  • We use "validator.isEmail" to check the email valid or not
  • salt: random password salt string for each user, it means "password" field may have different values when 2 members have the same password
  • tokens field is an array, because we can login in desktop browser, mobile or desktop app, laptop, tablet, etc at the same time. Each device or browser will have the unique token string and expired time.
  • expired: we will use cronjob to delete the expired token in the future, don't care about this now.

We want to create "POST /users/login" with email and password. If authenticated, we will receive the token to attach to header. But we don't have any users to get the token now, so we create "POST /users" to create a new user (for security purposes, you can remove this route when go live or use "Http authentication" popup to prevent create a new user on your production).

Same as create a new post, we will have the following code:
app.post('/users', (req, res) => {
  var user = new User(_.pick(req.body, ['email', 'password']));
  user.save().then((doc) => {
    res.send(doc);
  }, (err) => {
    res.send('An error has been occurred while creating an user.');
  });
});


But in fact we never store password as plain text, we always store the hash, so we add before the line:
var User = mongoose.model('User', UserSchema);

the following code:
UserSchema.pre('save', function (next) {
  var user = this;
  if (user.isModified('password')) {
    bcryptjs.genSalt(10, (err, salt) => {
      user.salt = salt;
      bcryptjs.hash(user.password, salt, (err, hash) => {
        user.password = hash;
        next();
      });
    });
  } else {
    next();
  }
});


Let's open postman and test:

Open Robomongo to check:


Now, call route "POST /users/login" with email and password to get the token!
Add the following code:
UserSchema.statics.findByCredentials = function (email, password) {
  var User = this;
  return User.findOne({email}).then((user) => {
    if (!user) {
      return Promise.reject();
    }
    return new Promise((resolve, reject) => {
      bcryptjs.compare(password, user.password, (err, res) => {
        if (res) {
          resolve(user);
        } else {
          reject();
        }
      });
    });
  });
};
UserSchema.methods.generateCsrfToken = function () {
  var user = this;
  var expired = Math.floor(Date.now() / 1000) + 20 * 60;
  var token = jwt.sign({_id: user._id.toHexString(), iat: expired}, user.salt).toString();
  user.tokens.push({expired, token});
  return user.save().then(() => {
    return {token, expired};
  });
};


before:
var User = mongoose.model('User', UserSchema);

Then we can write "POST /users/login" route:
app.post('/users/login', (req, res) => {
  User.findByCredentials(req.body.email, req.body.password).then((user) => {
    user.generateCsrfToken().then((result) => {
      res.send({
        'X-CSRF-Token': result.token,
        expired: result.expired
      });
    });
  }).catch((e) => {
    res.send({
      status: 0,
      msg: 'The email address or password that you entered is not valid.'
    });
  });
});


Open postman and test:

Then open Robomongo to check:


Now, update our source code with the token to make sure only owner can use the "POST /posts", "PATCH /posts/:id" and "DELETE /posts/:id"

Add the following code:
UserSchema.statics.findByToken = function (token) {
  var User = this;
  var decoded;
  return User.findOne({'tokens.token': token}).then((user) => {
    try {
      decoded = jwt.verify(token, user.salt);
    } catch (e) {
      return Promise.reject();
    }
    return user;
  });
};


before:
var User = mongoose.model('User', UserSchema);

Add this function to server.js
var authenticate = (req, res, next) => {
  var token = req.header('X-CSRF-Token');
  User.findByToken(token).then((user) => {
    if (!user) {
      return Promise.reject();
    }
    req.user = user;
    req.token = token;
    next();
  }).catch((e) => {
    res.send({
      status: 0,
      msg: 'Invalid token.'
    });
  });
};


then update "POST /posts", "PATCH /posts/:id" and "DELETE /posts/:id"

Open Postman, then test with correct X-CSRF-Token:
and incorrect or empty X-CSRF-Token:

We will optimize code later, but now that's it.

See you then!