Sunday, September 20, 2015

Creating an application with SailsJS and OrientDB on your Mac

Pre-Conditions

This article has been created for:

  • SailsJS 0.11.0
  • sails-orientdb 0.10.55
  • OrientDB 2.0.12

This article will not be updated!

Pre-requisites

  • Homebrew installed on Mac
  • OrientDB installed on Mac
  • A database called "testdb" and a user "apple" with password "test" and "admin" role

Part 1: Install and test sails

Install NodeJS

brew install node

Install SailsJS

sudo npm -g install sails

If you receive an error such as "sh: node: command not found" then try without the "sudo" command

Create new Sails application

sails new testProject

Change in to the testProject directory:

cd testProject

Create first API

sails generate api profile

Test the setup

Start sails.

sails lift

During start you'll be told that you don't have a project wide migrate setting configured yet. Choose "2" which means that the db will be altered if there are any changes.

1. safe  - never auto-migrate my database(s). I will do it myself (by hand) 
 2. alter - auto-migrate, but attempt to keep my existing data (experimental)
 3. drop  - wipe/drop ALL my data and rebuild models every time I lift Sails

What would you like Sails to do?

info: To skip this prompt in the future, set `sails.config.models.migrate`.
info: (conventionally, this is done in `config/models.js`)

warn: ** DO NOT CHOOSE "2" or "3" IF YOU ARE WORKING WITH PRODUCTION DATA **

prompt: ?:  2

Once sails has started try to reach the main page. You should see the main index page.

http://localhost:1337/

Then connect to the profile list page. You should see brackets as no data has been added yet "[]".

http://localhost:1337/profile

Try to add some data

http://localhost:1337/profile/create?age=22&haircolor=Brown
http://localhost:1337/profile/create?age=40&haircolor=Black

Go back to the profile list page and now you should see both entries in the output of the web page. Sails is using a local disk db by default.

http://localhost:1337/profile

Output in webpage

[
  {
    "age": "22",
    "haircolor": "Brown",
    "createdAt": "2015-07-17T17:00:10.403Z",
    "updatedAt": "2015-07-17T17:00:10.403Z",
    "id": 1
  },
  {
    "age": "40",
    "haircolor": "Black",
    "createdAt": "2015-07-17T17:01:02.251Z",
    "updatedAt": "2015-07-17T17:01:02.251Z",
    "id": 2
  }
]

Part 2: Adding the OrientDb Adapter

Please make sure that you have a database called "testdb" in OrientDB and added a user called "apple" with password "test" and the "admin" role.

Install the adapter

npm install sails-orientdb --save

Configure Sails to use the OrientDB Adapter

Configure config/connections.js

Add the orientDbServer1 related configuration in to connections.js and don't forget to add an additional comma after the already existing somePostgresqlServer configuration entry.

  /***************************************************************************
  *                                                                          *
  * PostgreSQL is another officially supported relational database.          *
  * http://en.wikipedia.org/wiki/PostgreSQL                                  *
  *                                                                          *
  * Run: npm install sails-postgresql                                        *
  *                                                                          *
  *                                                                          *
  ***************************************************************************/
  somePostgresqlServer: {
    adapter: 'sails-postgresql',
    host: 'YOUR_POSTGRES_SERVER_HOSTNAME_OR_IP_ADDRESS',
    user: 'YOUR_POSTGRES_USER',
    password: 'YOUR_POSTGRES_PASSWORD',
    database: 'YOUR_POSTGRES_DB'
  },


  /***************************************************************************
  *                                                                          *
  * More adapters: https://github.com/balderdashy/sails                      *
  *                                                                          *
  ***************************************************************************/
  orientDbServer1: {
   adapter: 'sails-orientdb',
   host: 'localhost',
   port: 2424, 
   database: 'testdb',
   options: {
     databaseType: 'graph',
     databaseUser: 'apple',
     databasePassword: 'test'
   }
 }

Configure config/models.js

Uncomment the connection and the migrate configuration options and change the name of the connection attribute to orientDbServer1. This will tell Sails to use the orientDbServer1 configuration from connections.js.

module.exports.models = {

  /***************************************************************************
  *                                                                          *
  * Your app's default connection. i.e. the name of one of your app's        *
  * connections (see `config/connections.js`)                                *
  *                                                                          *
  ***************************************************************************/
  connection: 'orientDbServer1',

  /***************************************************************************
  *                                                                          *
  * How and whether Sails will attempt to automatically rebuild the          *
  * tables/collections/etc. in your schema.                                  *
  *                                                                          *
  * See http://sailsjs.org/#!/documentation/concepts/ORM/model-settings.html  *
  *                                                                          *
  ***************************************************************************/
  migrate: 'alter'

};

Configure the database attributes in the profile model

In this step we will add the database fields. Open up api/models/Profile.js.

/**
* Profile.js
*
* @description :: TODO: You might write a short summary of how this model works and what it represents here.
* @docs        :: http://sailsjs.org/#!documentation/models
*/

module.exports = {

  attributes: {
    age: {
      type: 'integer'
    },
    gender: {
      type: 'string',
      enum: ['Male', 'Female']
    },
    haircolor: {
      type: 'string',
      enum: ['Black', 'Blonde', 'Brown', 'Red', 'Blue', 'Green', 'Bold', 'Other']
    }
  }
};

Test setup

Start the sails console

This time we will start sails in console mode, so that we can execute some commands to test the database queries. During the start of Sails the database parameters will be added to the database. Once the console is running you can check if the schema contains the "profile" class and the attributes are present over the OrientDb web interface.

sails console

Execute test queries

Create a new entry to the profile class.

sails> Profile.create({age:22,gender:'Male',haircolor:'Blonde'}).exec(console.log)
undefined
sails> 
null { '@type': 'd',
  '@class': 'profile',
  age: 22,
  gender: 'Male',
  haircolor: 'Blonde',
  createdAt: '2015-07-17T17:49:09.000Z',
  updatedAt: '2015-07-17T17:49:09.000Z',
  id: '#12:0' }

undefined

Check if the entry exists.

sails> Profile.find().exec(console.log)
undefined
sails> 
null [ { age: 22,
    gender: 'Male',
    haircolor: 'Blonde',
    createdAt: '2015-07-17T17:49:09.000Z',
    updatedAt: '2015-07-17T17:49:09.000Z',
    id: '#12:0' } ]

undefined

Part 3: Install Authentication Module

Install Waterlock

If you receive an error executing the commands below try without "sudo".

sudo npm install waterlock
sudo npm install waterlock-local-auth

Generate all files

./node_modules/.bin/waterlock generate all

Configure some test methods

open api/controllers/ProfileController.js

/**
 * ProfileController
 *
 * @description :: Server-side logic for managing profiles
 * @help        :: See http://sailsjs.org/#!/documentation/concepts/Controllers
 */

module.exports = {
    // This action can only be reached when logged in
    restricted: function(req, res) {
        return res.ok("If you can see this you are authenticated!");
    },
    // This action can always be reached without login
    open: function(req, res) {
        return res.ok("This action is open!");
    },
    // This action is used to test parsing the json web token
    jwt: function(req, res) {
        return res.ok("You have a JSON web token!");
    }
};

Configure access

Open config/policies.js.

  /***************************************************************************
  *                                                                          *
  * Default policy for all controllers and actions (`true` allows public     *
  * access)                                                                  *
  *                                                                          *
  ***************************************************************************/

   '*': true,
    
    ProfileController: {
        restricted: ['sessionAuth'],
        open: true,
        jwt: ['hasJsonWebToken']
    }

Here a video tutorial that does basically the same.

https://www.youtube.com/watch?v=2z575YFh9R8

Part 4: Uploading a file

Create the uploadfile html page

Create a new file views/uploadfile.ejs

<!-- enctype="multipart/form-data" -->
<form id="uploadForm"
    enctype="multipart/form-data"
    action="/file/upload"
    method="post">
    <input type="file" name="uploadFile" />
    <input type="submit" value="submit"/>
</form>

Configure the route to uploadfile.ejs

Open config/routes.js. Add the route called /upload-file. The view 'uploadfile.ejs' will be loaded.

module.exports.routes = {

  /***************************************************************************
  *                                                                          *
  * Make the view located at `views/homepage.ejs` (or `views/homepage.jade`, *
  * etc. depending on your default view engine) your home page.              *
  *                                                                          *
  * (Alternatively, remove this and add an `index.html` file in your         *
  * `assets` directory)                                                      *
  *                                                                          *
  ***************************************************************************/

    '/': {
        view: 'homepage'
    },
    
    '/upload-file': {
        view: 'uploadfile'
    }

  /***************************************************************************
  *                                                                          *
  * Custom routes here...                                                    *
  *                                                                          *
  * If a request to a URL doesn't match any of the custom routes above, it   *
  * is matched against Sails route blueprints. See `config/blueprints.js`    *
  * for configuration options and examples.                                  *
  *                                                                          *
  ***************************************************************************/

};

Create upload controller

The code below will create a controller called FileController.

sails generate controller file

Create code in FileControler

Add code to api/controllers/FileController.js.

module.exports = {
upload: function  (req, res) {
    if(req.method === 'GET') {
        return res.json({'status':'GET not allowed'});
    }
    
    // Call to /upload via GET is error
    var uploadFile = req.file('uploadFile');
    console.log(uploadFile);
    
    uploadFile.upload(function onUploadComplete (err, files) {
            // Files will be uploaded to .tmp/uploads
            if (err) return res.serverError(err);
            // IF ERROR Return and send 500 error with error
                      
            console.log(files);
            res.json({status:200,file:files});
        });
    }
};

Reference

http://maangalabs.com/blog/2014/08/12/uploading-a-file-in-sails/

More to follow

This documentation is still under development

Installing AngularJS

Install AngularJS with YeoMan on MacOS X

Pre-Conditions

This tutorial is written explicitly for following versions of AngularJS and NodeJS:

  • AngularJS 1.2.16
  • NodeJS 0.10.30

This tutorial might not work for other versions and will not be updated!

Check if NodeJS is installed:

node --version && npm --version

Install NodeJS

Download and Install NodeJS from following website: http://nodejs.org/

Check if GIT is installed

git --version

Install GIT

Download and Install GIT from following website: http://git-scm.com/

Install yo

sudo npm install --global yo

Install grunt

sudo npm install grunt --save-dev

Re-Install NPM

sudo npm install

Check if everything was installed

yo --version && bower --version && grunt --version

Install AngularJS generator

sudo npm install --global generator-angular

Adjust permissions

sudo echo prefix = ~/.node >> ~/.npmrc

Create project folder

mkdir testProject
cd testProject

Create Angular Project

yo angular

You'll be asked what you want to choose. It's usually a good idea to add all libraries.

Start server

grunt serve

Now a website should open and display the initial page.

Sunday, December 1, 2013

AngularJS and select statements using resources

I would expect from a good framework like AngularJS that it integrates their methods to play together nicely to solve problems such as the issue with the select statement that only supports referential equality which is not working with resources ($resource) as data source. In the edit case where an object in the ng-model is matched to the resource object it will fail. It took me about 4 hours analyze the problem and then find the ngyn select directive that solves the problem with an own select directive. I'd like to thank the authors for this nice plugin and hope it will find it's way in to the AngularJS code eventually.

Select Extensions

The AngularJS select directive lacks the capability to select an existing option based on anything other than referential equality. The ngynSelectKey module extends select with the capability to specify how items should be compared. This makes it trivial to match an item in the select list with a value being returned, for example from a query.

Usage is simple, just supply a value within a key attribute, this will typically be a property name but it can be anything which will resolve using $scope.$eval(), such as a function on each attribute.

HTML
<select ng-model="user.role" 
        ng-options="r.name for r in user.availableRoles" 
        key="id">
</select>
JavaScript
  $scope.user = {};
  $scope.user.role = { id: 1, name: 'Administrator' };
  $scope.user.availableRoles = Roles.query();
  • You can find the description here
  • You can find the source here

Integrate the plugin

To integrate the plugin I have downloaded the select-key.js file and added it to my lib directory in which also the AngularJS libraries are located.

Then I added the file to my index.html page.

<script src="../lib/angular120/angular.js"></script>
<script src="../lib/angular120/angular-route.js"></script>
<script src="../lib/angular120/angular-resource.js"></script>
<script src="../lib/ngyn/select-key.js"></script>

And as last step added the module in to my app (app.js) in my case.

angular.module('myApp', ['ngRoute',
        'ngynSelectKey',
        'errorHelper',
        'formHelper',
        'fileHelper',
        'eaValidators',
        'user',
        'vendor',
        'card'])

Tuesday, November 5, 2013

Configure Fedora 19 Desktop

Word in advance

After several years I wanted to try Linux as a desktop again and was quite amazed how good I got along.

I have tried Ubuntu but I soon discovered that the evil of marketing got over them and they are turning Ubuntu in to a Windows Clone. Not only that. They are sending all queries made with their Unitiy search to Amazon and other third parties. ALL queries. Even those made on the file system. Do you really want Amazon to see what you are searching on the file system? After trying to turn the feature off by deinstalling certain packages it killed my desktop and I had to re-install it over the console. Additionally I had problems with sound drivers and the Unity Desktop is far from being user friendly. So what is the conclusion of all this?

As soon the money is invested in to Marketing instead of Product Management the quality of products drops dramatically. The revenue and maybe also the profit will rise short term but many of these products will somewhen hit the ground hard.

I tried with Fedora and the Gnome Desktop and just stuck to it as it is running rock solid and has good driver support. The only lack are the Wireless network drivers. But this is the case for all Linux distributions.

Install Vim

sudo yum install vim

Install dconf-editor

Install the dconf-editor over the Software Center.

Set Logout Link

You can define that the logout link is displayed in the menu on the top of the right hand side. Open the dconf-editor and choose org -> gnome -> shell. Then tick always-show-logout.

Installing Flash

http://fedoraproject.org/wiki/Flash

Install Flash Browser Plugin

Open a terminal window and install flash.
sudo yum install http://linuxdownload.adobe.com/adobe-release/adobe-release-x86_64-1.0-1.noarch.rpm -y
Add flash plugin to use with browsers.
sudo rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-adobe-linux
sudo yum install flash-plugin -y

Install Shutter

Shutter is a screenshot application for gnome. You can install it over the Software Center.

Transparent Terminal Window

By default the gnome terminal window does not support transparent background. To add this feature again you can use devilspie.
sudo yum install devilspie
mkdir ~/.devilspie
vim ~/.devilspie/terminal-opacity.ds
(if
  (matches (window_name) "Terminal")
  (opacity 85)
) 

Add devilspie to Gnome Startup

Open a terminal window and start gnome-session-properties.
gnome-session-properties
Click on to the Add button to create a new session startup application and add devilspie as displayed on the picture.

Resources

Install Google Chrome

Firefox is my most beloved Browser. But sometimes I have problems opening content such as the Swiss News Videos. For this I use Chrome that is working in such cases. Install Chrome over the Chrome Website.

Install Wuala

Wuala is a secure cloud storage service such as Dropbox. The great thing about it is that it splits the files in chunks, encrypts the chunks before uploading and uploads the chunks to servers in Switzerland, Germany and England. So it at least looks pretty secure. You can install Wuala over the Wuala Website. To start Wuala when you login you can add Wuala to the startup applications.

Name: Wuala
Command: wuala -nogui login <your login> <your password>: "$@"
Description: Wuala

Install MongoDB for Dev

tar -xvzf mongodb-linux-x86_64-2.4.7.tgz
sudo mv mongodb-linux-x86_64-2.4.7 /opt/mongodb

Add MongoDB as Development Database to Gnome Startup

Click on to the Add button to create a new session startup application and add MongoDB with following parameters:

Name: MongoDB
Command: /opt/mongodb/bin/mongod --dbpath /tmp --setParameter textSearchEnabled=true
Description: MongoDB


Add MongoDB bin directory to PATH

Edit /etc/profile and add following line before the general export command:
PATH=$PATH:/opt/mongodb/bin # <- Add this line
export PATH USER LOGNAME MAIL HOSTNAME HISTSIZE HISTCONTROL

Install Oracle Java 7 SDK

http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html
cd /etc/alternatives/
sudo rm java
sudo rm javaws
sudo rm javac
sudo rm jar
sudo rm libnpjp2.so

sudo ln -s /usr/java/latest/bin/java .
sudo ln -s /usr/java/latest/bin/javaws .
sudo ln -s /usr/java/latest/bin/javac .
sudo ln -s /usr/java/latest/bin/jar . 
sudo ln -s /usr/java/latest/jre/lib/amd64/libnpjp2.so .
java -version
java version "1.7.0_45"
Java(TM) SE Runtime Environment (build 1.7.0_45-b18)
Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)
vim /etc/profile
export JAVA_HOME="/usr/java/latest"

Install Apache Maven

tar -xvzf apache-maven-3.1.1-bin.tar.gz
sudo mv apache-maven-3.1.1-bin /opt/apache-maven
vim /etc/profile
export M2_HOME=/opt/apache-maven

Install Intellij Idea

tar -xvzf ideaIU-12.1.6.tar.gz
sudo mv ideaIU-12.1.6 /opt/idea

Create .desktop file

Create a file in ~/.local/share/applications/idea.desktop:
[Desktop Entry]
Comment=Intellij Idea
Terminal=false
Name=Idea
Exec=/opt/idea/bin/idea.sh
Type=Application
Icon=/opt/idea/bin/idea.png

Install HPLip

Download hplip http://hplipopensource.com/hplip-web/downloads.html restart Install hplip-gui

Install KeePass 2

Install Mono
sudo yum -y install mono-core
sudo yum -y install mono-devel
Download the KeePass Zipfile (not the installer) from http://keepass.info/download.html in to ~/Downloads.
sudo yum install wget
cd ~/Downloads
mkdir keepass
mv KeePass-2.24.zip keepass
cd keepass
unzip KeePass-2.24.zip
wget http://upload.wikimedia.org/wikipedia/commons/1/19/KeePass_icon.png
cd ..
sudo mv keepass /opt/
Create keepass.desktop file /usr/share/applications/
sudo vim keepass.desktop
[Desktop Entry]
Name=KeePass
Exec=mono /opt/keepass/KeePass.exe
Icon=/opt/keepass/KeePass_icon.png
Categories=Other
Type=Application

Sunday, September 22, 2013

Creating a global http error handler for your AngularJS app

Please note that this is deprecated:

See https://github.com/angular/angular.js/blob/master/CHANGELOG.md#1.1.4 and search for responseInterceptors, its deprecated.

If you're pissed like me about frameworks changing their methods every 6 months don't use Angular!



If you are using AngularJS with REST then it comes in handy to have a global http error handler that will intercept the http status message and display a message on the page accordingly. Therefore I have tweaked and created following error handler based on some examples found on the net.

As you can see the showMessage function will display the messages. The first parameter that can be parsed is the error message, the second parameter is a css class name and the third parameter is the time in miliseconds to display the message.

$httpProvider contains http defaults and is also able to intercept all http responses. So push a function to this interceptor which will listen on http return status messages. If there is a return status message we will trigger a message to be displayed on our Angularjs index.html page.

lib/xx/xx-http-error-handling.js
/**
 * @ngdoc overview
 * @name xx-http-error-handling
 * @description
 *
 * Module that provides global http error handling for apps.
 *
 * Usage:
 * Hook the file in to your index.html: <script src="lib/xx/xx-http-error-handling.js"></script>
 * Add <div class="messagesList" app-messages></div> to the index.html at the position you want to
 * display the error messages.
 */
(function() {
  'use strict';
  angular.module('xx-http-error-handling', [])
    .config(function($provide, $httpProvider, $compileProvider) {
      var elementsList = $();

      // this message will appear for a defined amount of time and then vanish again
      var showMessage = function(content, cl, time) {
        $('<div/>')
          .addClass(cl)
          .hide()
          .fadeIn('fast')
          .delay(time)
          .fadeOut('fast', function() { $(this).remove(); })
          .appendTo(elementsList)
          .text(content);
        };

        // push function to the responseInterceptors which will intercept 
        // the http responses of the whole application
        $httpProvider.responseInterceptors.push(function($timeout, $q) {
          return function(promise) {
            return promise.then(function(successResponse) {
              // if there is a successful response on POST, UPDATE or DELETE we display
              // a success message with green background
              if (successResponse.config.method.toUpperCase() != 'GET') {
                showMessage('Success', 'xx-http-success-message', 5000);
                return successResponse;
              }
            },
            // if the message returns unsuccessful we display the error 
            function(errorResponse) {
              switch (errorResponse.status) {
                case 400: // if the status is 400 we return the error
                  showMessage(errorResponse.data.message, 'xx-http-error-message', 6000);
                  // if we have found validation error messages we will loop through
                  // and display them
                  if(errorResponse.data.errors.length > 0) {
                    for(var i=0; i<errorResponse.data.errors.length; i++) {
                      showMessage(errorResponse.data.errors[i], 
                        'xx-http-error-validation-message', 6000);
                    }
                  }
                  break;
                case 401: // if the status is 401 we return access denied
                  showMessage('Wrong email address or password!', 
                    'xx-http-error-message', 6000);
                  break;
                case 403: // if the status is 403 we tell the user that authorization was denied
                  showMessage('You have insufficient privileges to do what you want to do!', 
                    'xx-http-error-message', 6000);
                  break;
                case 500: // if the status is 500 we return an internal server error message
                  showMessage('Internal server error: ' + errorResponse.data.message, 
                    'xx-http-error-message', 6000);
                  break;
                default: // for all other errors we display a default error message
                  showMessage('Error ' + errorResponse.status + ': ' + errorResponse.data.message, 
                    'xx-http-error-message', 6000);
              }
              return $q.reject(errorResponse);
            });
          };
        });

        // this will display the message if there was a http return status
        $compileProvider.directive('httpErrorMessages', function() {
          return {
            link: function(scope, element, attrs) {
              elementsList.push($(element));
            }
          };
        });
      });
})();
You probably want to style the color of your message. If the message is an error it will be displayed in red. If the message is a success it will be displayed in green.

css/xx-http-error-handling.css
/* display error message styled in red */
.xx-http-error-message {
    background-color: #fbbcb1;
    border: 1px #e92d0c solid;
    font-size: 12px;
    font-family: arial;
    padding: 10px;
    width: 702px;
    margin-bottom: 1px;
}

.xx-http-error-validation-message {
    background-color: #fbbcb1;
    border: 1px #e92d0c solid;
    font-size: 12px;
    font-family: arial;
    padding: 10px;
    width: 702px;
    margin-bottom: 1px;
}

/* display success message styled in green */
.xx-http-success-message {
    background-color: #adfa9e;
    border: 1px #25ae09 solid;
    font-size: 12px;
    font-family: arial;
    padding: 10px;
    width: 702px;
    margin-bottom: 1px;
}
Now all that is left is to add the tag defined by the directive to your index.html file.

index.html
<!doctype html>
<html lang="en" ng-app="myApp">
  <head>
    <meta charset="utf-8">
    <title>YourApp</title>
    <link rel="stylesheet" href="css/app.css"/>
    <link rel="stylesheet" href="css/style.css"/>
    <link rel="stylesheet" href="css/xx-http-error-handling.css"/>
  </head>
  <body>

    <!-- Display top tab menu -->
    <ul class="menu">
      <li><a href="#/user">Users</a></li>
      <li><a href="#/vendor">Vendors</a></li>
      <li><xx-logout-link/></li>
    </ul>

    <!-- Display errors from xx-http-error-handling.js -->
    <div class="http-error-messages" http-error-messages></div>

    <!-- Display partial pages -->
    <div ng-view></div>

    <!-- Include all the js files. In production use min.js should be used -->
    <script src="lib/jquery203/jquery-2.0.3.js"></script>
    <script src="lib/angular114/angular.js"></script>
    <script src="lib/angular114/angular-resource.js"></script>
    <script src="lib/xx/xx-http-error-handling.js"></script>
    <script src="js/app.js"></script>
    <script src="js/services.js"></script>
    <script src="js/controllers.js"></script>
    <script src="js/filters.js"></script>
  </body>
</html>
Last but not least you'll have to hook the module in to your app module so that it will be noticed by AngularJS.

js/app.js
angular.module('myApp', ['xx-http-error-handling'])
  // Define routing
  .config(['$routeProvider', function ($routeProvider) {
    $routeProvider.when('/login', {templateUrl: 'partials/login.html'});
    $routeProvider.when('/user', {templateUrl: 'partials/user.html', controller: UserCtrl});
    $routeProvider.when('/vendor', {templateUrl: 'partials/vendor.html', controller: VendorCtrl});
    $routeProvider.otherwise({redirectTo: '/login'});
  }]);
Here is the JSON output of an error message in case there was an error. As you can see there is an "errors" array that will display validation errors. If there are validation errors and the status is 400 then the validation errors will be displayed each by a single "showMessage" function.

Basically the validation errors should never pop up because we check the validation on client side already. But it is easy for someone to bypass the client side validation and therefore the validation also has to happen on the server side. The messages can help you in case you have forgotten to implement a client validation method. You will then be presented with the error from the server side.

{
  "status":400,
  "code":0,
  "message":"Bad Request",
  "developerMessage":"Validation failed for argument at index 0 in method: ...",
  "moreInfo":null,
  "errors":["Firstname can't be empty", "E-Mail Adress is invalid"]
}

Creating re-usable libraries for your project with Angularjs

There are several ways to structure your code in AngularJS and there is no right or wrong. When you start working with AngularJS I assume that you start by checking out the Angular Seed Project or using Yeoman. However at some point you will want to create libraries or widgets that you want to store in one place for re-use in other projects. For example the "lib" directory.

Examples could include:
  • Global error handler (lib/xx/xx-global-error-handler.js)
  • Authorization (lib/xx/xx-authorization.js)
  • File Upload (lib/xx/xx-file-upload.js)
You can then include these files in to your index.html like this:
<script src="lib/angular.js"></script>
<script src="lib/xx/xx-global-error-handler.js"></script>
<script src="lib/xx/xx-authorization.js"></script>
<script src="lib/xx/xx-file-upload.js"></script>
It is considered as good practice to use a two letter or more letter prefix. For example the starting letters of your surname and name or the first and last letter of your company name. In my case displayed as "xx". This helps to prevent clashes with other files and variables or names you use in your project. I also add the prefix to the name of my controllers, directives and variables.

lib/xx/xx-my-module-name.js
/**
 * @ngdoc overview
 * @name xx-my-module-name
 * @description
 *
 * Your module description goes here
 */
(function() {
  'use strict';
  angular.module('xx-module-name', [])
    .config(['$http',function($http) {
    }])
    .controller('XXMyCtrl', ['$http',function($http) {
    }])
    .directive('xx-my-directive', ['$http', function($http) {
    }]);
})();
Once your module is finished and you added it to the index.html file you'll also have to link it to your main app.js.
angular.module('myApp', ['xx-module-name'])
  // Define routing
  .config(['$routeProvider', function ($routeProvider) {
    $routeProvider.when('/login', {templateUrl: 'partials/login.html'});
    $routeProvider.when('/user', {templateUrl: 'partials/user.html', controller: UserCtrl});
    $routeProvider.when('/vendor', {templateUrl: 'partials/vendor.html', controller: VendorCtrl});
    $routeProvider.otherwise({redirectTo: '/login'});
  }]);
Like this you can start to plugin your own library sets as required.

General Tips on structuring your code

Personally I have started to also put normal code in to a similar fashion. If I for example need code to handle a Vendor then I'll put everything such as controllers, directives, filters and so on that are related to the vendor in to a file called vendor.js. First of all it is very easy to find what you are looking for and you don't fill up a file with several different controllers or directives in which you have to search for the right controller or directive again.

Monday, July 1, 2013

Spring & MongoDB Repository

Different ways of using spring-data mongodb repository queries

Basic autogenerated queries

With spring-data you can use basic queries such as displayed below:

@Repository
public interface UserRepositoryIf extends MongoRepository {
  User findByEmail(String email);
  List<User> findByEmailLike(String email);
  List<User> findByEmailOrLastName(String email, String lastName);
}
Spring will automatically generate a JSON query based on the Syntax use use. More information about basic queries can be found in the Reference Manual. These queries are ok for basic queries and CRUD functionality. But when it comes to advanced queries then you'll see that you'll end up in a dead end very quickly.

Using queries with @Query annotation

One possibility to do advanced queries is to add the @Query annotation and work with JSON based queries. This enables you to use the full power of the MongoDB Json query language. You can parse parameters as ?0 (for first parameter), ?1 (for second parameter). In this example ?0 will contain the contents of the variable "searchText".

@Repository
public interface UserRepositoryIf 
      extends MongoRepository<User, String> {

  @Query("{\"$or\" : [" + 
    "{ \"email\" : " + 
      "{ \"$regex\" : ?0, \"$options\" : \"i\"}} , " +
    "{ \"firstName\" : " +
      "{ \"$regex\" : ?0, \"$options\" : \"i\"}}, " +
    "{ \"lastName\" : " +
      "{ \"$regex\" : ?0, \"$options\" : \"i\"}}]}")
  List findByEmailOrFirstNameOrLastNameLike(String searchText);
}
Using this we can create complex queries and have the full power of the MongoDB JSON language. However it is not type safe (mistakes in the syntax can happen very easy) and it is bound to the JSON query language of MongoDB. If you want to use another database you'll have to adjust quite a lot of code.

Using MongoTemplate

A question on Stackoverflow when to use mongoTemplate or Repositories actually resulted in a reply I liked. One of the developers of Spring pointed out that it is possible to mix queries based on the requirements by adding custom interfaces and implementations. He suggested that one should start with basic queries and as soon the queries get more complex add a custom interface and implementation to enhance the functionality with MongoTemplate. Here is the Stackoverflow question.
So if you for example have a repository called UserRepositoryIf you would add an interface called UserRepositoryIfCustom and an UserRepositoryIfImpl class. Note that the name of the interface has to be "Custom" at the end of the custom interface and for the implementation class it is expected that it is named "Impl" at the end of the class. If you use other endings it might fail.

@Repository
public interface UserRepositoryIf 
      extends MongoRepository<User, String>, 
          UserRepositoryIfCustom {
  // Basic queries
  User findByEmail(String email);
  List findByEmailLike(String email);
  List findByEmailOrLastName(String email, String lastName);
  List findByEmailOrFirstNameLike(String email, String firstName);
}
public interface UserRepositoryIfCustom {
  // Advanced query
  List<User> 
    searchByEmailOrFirstNameOrLastName(String searchPattern);
}
public class UserRepositoryIfImpl 
      implements UserRepositoryIfCustom {

  @Autowired
  private MongoTemplate mongoTemplate;
  private Class<User> clazz = User.class;

  @Override
  // Advanced query implementation
  public List<User> 
      searchByEmailOrFirstNameOrLastName(String searchPattern) {
    Query query = new Query();
    query.addCriteria(new Criteria().orOperator(
      Criteria.where("email").regex(searchPattern,"i"),
      Criteria.where("firstName").regex(searchPattern,"i"),
      Criteria.where("lastName").regex(searchPattern,"i")
    ));
    return mongoTemplate.find(query, clazz);
  }
}
As you can see like this you have a clear separation between the basic queries and the more advanced MongoTemplate queries. This is what I suggest to use and is also suggested by Spring. More information regarding MongoTemplate can be found in the Reference Manual.

QueryDSL

QueryDSL is quite advanced and totally type safe. It is another possibility how you can query MongoDB. And as you might already guess you can also extend the UserRepository interface. Even if this is quite advanced and supports different databases as backends I do not use it mainly because additional dependencies are necessary and it makes use of APT. If you want to use it please refer to the Reference Manual.