Sfoglia il codice sorgente

Feature: get user's accounts

febbweiss 10 anni fa
parent
commit
fdedcdcdff

+ 2 - 1
bower.json

@@ -7,7 +7,8 @@
     "angular-route": "~1.4.1",
     "animate.css": "~3.3.0",
     "bootstrap": "~3.3.5",
-    "font-awesome": "~4.4.0"
+    "font-awesome": "~4.4.0",
+    "angular-xeditable": "~0.1.9"
   },
   "devDependencies": {
     "angular-mocks": "~1.4.4"

+ 1 - 0
karma.conf.js

@@ -18,6 +18,7 @@ module.exports = function(config) {
       'public/libs/angular/angular.js',
       'public/libs/angular-route/angular-route.js',
       'public/libs/angular-cookies/angular-cookies.js',
+      'public/libs/angular-xeditable/dist/js/xeditable.min.js',
       'public/libs/angular-mocks/angular-mocks.js',
       'public/js/**/*.js',
       'public/**/*.controller.js',

+ 83 - 0
public/accounts/accounts.controller.js

@@ -0,0 +1,83 @@
+(function(){
+    'use strict';
+    
+    angular
+        .module('cloudbudget')
+        .controller('AccountsController', AccountsController);
+        
+    AccountsController.$inject = ['$scope', '$location', '$rootScope', 'FlashService', 'AccountsService'];
+    
+    function AccountsController($scope, $location, $rootScope, FlashService, AccountsService) {
+        var vm = this;
+        
+        vm.dataLoading = false;
+        vm.accounts = [];
+        vm.create = create;
+        vm.drop = drop;
+        vm.edit = edit;
+        vm.consult = consult;
+        
+        (function init() {
+            vm.dataLoading = true;
+            AccountsService.list()
+                .then(function(response) {
+                    if( response.success ) {
+                        vm.accounts = response.accounts;
+                    } else {
+                        FlashService.error(response.message);
+                    }
+                    vm.dataLoading = false;
+                })
+        })();
+        
+        function create() {
+            vm.dataLoading = true;
+            AccountsService.create(vm.account)
+                .then( function(response) {
+                    if( response.success) {
+                        vm.accounts.push(response.account);
+                    } else {
+                        FlashService.error(response.message);
+                    }
+                    
+                    vm.dataLoading = false;
+                });
+            vm.account = angular.copy({});
+            $scope.form.$setPristine();
+        };
+        
+        function drop(account) {
+            vm.dataLoading = true;
+            AccountsService.drop(account)
+                .then(function(response) {
+                    if( response.success ) {
+                        var index = vm.accounts.indexOf(account);
+                        vm.accounts.splice(index, 1);
+                    } else {
+                        FlashService.error( response.message );
+                    }
+                    vm.dataLoading = false;
+                });
+        };
+        
+        function edit(altered, origin) {
+            vm.dataLoading = true;
+            return AccountsService.edit(origin._id, altered)
+                .then( function(response) {
+                    if( response.success ) {
+                        var index = vm.accounts.map(function (item) {
+                                return item._id;
+                            }).indexOf(origin._id);
+                        vm.accounts[index] = response.account;
+                    } else {
+                        FlashService.error( response.message );
+                        return false;
+                    }
+                })
+        };
+        
+        function consult(account) {
+            $location.path('/account/' + account._id);
+        };
+    }
+})();

+ 49 - 0
public/accounts/accounts.view.html

@@ -0,0 +1,49 @@
+<div class="container-fluid">
+    <div class="row">
+        <form name="form" ng-submit="vm.create()" role="form">
+            <div class="col-sm-4">
+                <div class="form-group">
+                    <input name="reference" id="reference" class="form-control" placeholder="Reference" ng-model="vm.account.reference" />
+                </div>
+            </div>
+            <div class="col-sm-4">
+                <div class="form-group" ng-class="{'has-error': form.name.$dirty && form.name.$error.required}">
+                    <input name="name" id="name" class="form-control" ng-model="vm.account.name" placeholder="name" required/>                                    
+                    <span ng-show="form.name.$dirty && form.name.$error.required" class="help-block">name is required</span>
+                </div>
+            </div>
+            <div class="col-sm-4">
+                <button type="submit" class="btn btn-primary" ng-disabled="form.$invalid || vm.dataLoading">
+                    <i class="fa fa-fw fa-floppy-o"></i>
+                </button>
+                <img ng-if="vm.dataLoading" src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" />
+            </div>
+        </form>
+    </div>
+                
+    <div class="row" ng-repeat="account in vm.accounts">
+        <div class="col-sm-4">
+            <span e-form="rowform" e-name="reference" editable-text="account.reference">{{account.reference}}</span>
+        </div>
+        <div class="col-sm-4"><span e-form="rowform" e-name="name" editable-text="account.name" e-required>{{account.name}}</span></div>
+        <div class="col-sm-4">
+            <form editable-form name="rowform" onbeforesave="vm.edit($data, account)" ng-show="rowform.$visible" xclass="form-buttons form-inline" shown="inserted == account">
+                <button type="submit" ng-disabled="rowForm.$invalid || rowform.$waiting" title="Edit" class="btn btn-success">
+                    <i class="fa fa-fw fa-floppy-o"></i>
+                </button>
+                <button type="button" ng-disabled="rowform.$waiting" title="Cancel" ng-click="rowform.$cancel()" class="btn btn-default">
+                    <i class="fa fa-fw fa-ban"></i>
+                </button>
+                <a class="btn btn-danger" title="Delete" ng-disabled="rowForm.$waiting" ng-click="vm.drop(account)">
+                    <i class="fa fa-fw fa-trash"></i>
+                </a>
+            </form>
+            <a class="btn btn-success" ng-click="rowform.$show()" ng-show="!rowform.$visible">
+                <i class="fa fa-fw fa-pencil"></i>
+            </a>
+            <a class="btn btn-primary" ng-click="vm.consult(account)" ng-show="!rowform.$visible">
+                <i class="fa fa-fw fa-eye"></i>
+            </a>
+        </div>
+    </div>
+</div>

+ 20 - 20
public/index.html

@@ -5,7 +5,9 @@
         <base href="/" />
         
         <title>CloudBudget</title>
-        <link rel="stylesheet" href="/libs/bootstrap/dist/css/bootstrap.min.css" />
+        <link rel="stylesheet" href="/libs/bootstrap/dist/css/bootstrap.min.css" type="text/css" />
+        <link rel="stylesheet" href="/libs/font-awesome/css/font-awesome.min.css" type="text/css"/>
+        <link rel="stylesheet" href="/libs/angular-xeditable/dist/css/xeditable.css" type="text/css" />
     </head>
     <body>
         <div class="container">
@@ -19,14 +21,9 @@
                 </ul>
             </nav>
         </div>
-        <div class="jumbotron">
-            <div class="container">
-                <div class="col-sm-8 col-sm-offset-2">
-                    <div ng-class="{ 'alert': flash, 'alert-success' : flash.type === 'success', 'alert-danger': flash.type === 'error'}" ng-if="flash" ng-bind="flash.message"></div>
-                    <div ng-view></div>
-                </div>
-            </div>
-        </div>
+        
+        <div ng-class="{ 'alert': flash, 'alert-success' : flash.type === 'success', 'alert-danger': flash.type === 'error'}" ng-if="flash" ng-bind="flash.message"></div>
+        <div ng-view></div>
         
         <div class="credits text-center">
             <p>
@@ -37,18 +34,21 @@
             </p>
         </div>
         
-        <script src="/libs/angular/angular.js" ></script>
-        <script src="/libs/angular-route/angular-route.min.js"></script>
-        <script src="/libs/angular-cookies/angular-cookies.min.js"></script>
+        <script type="text/javascript" src="/libs/angular/angular.js" ></script>
+        <script type="text/javascript" src="/libs/angular-route/angular-route.min.js"></script>
+        <script type="text/javascript" src="/libs/angular-cookies/angular-cookies.min.js"></script>
+        <script type="text/javascript" src="/libs/angular-xeditable/dist/js/xeditable.min.js"></script>
         
-        <script src="/js/app.js"></script>
-        <script src="/js/routes.js"></script>
-        <script src="/js/services/authentication.service.js"></script>
-        <script src="/js/services/flash.service.js"></script>
-        <script src="/js/services/user.service.js"></script>
+        <script type="text/javascript" src="/js/app.js"></script>
+        <script type="text/javascript" src="/js/routes.js"></script>
+        <script type="text/javascript" src="/js/services/authentication.service.js"></script>
+        <script type="text/javascript" src="/js/services/flash.service.js"></script>
+        <script type="text/javascript" src="/js/services/user.service.js"></script>
+        <script type="text/javascript" src="/js/services/accounts.service.js"></script>
         
-        <script src="/home/home.controller.js"></script>
-        <script src="/login/login.controller.js"></script>
-        <script src="/register/register.controller.js"></script>
+        <script type="text/javascript" src="/home/home.controller.js"></script>
+        <script type="text/javascript" src="/login/login.controller.js"></script>
+        <script type="text/javascript" src="/register/register.controller.js"></script>
+        <script type="text/javascript" src="/accounts/accounts.controller.js"></script>
     </body>
 </html>

+ 15 - 9
public/js/app.js

@@ -1,21 +1,27 @@
 (function() {
     'use strict';
     
-    var HOST = 'http://cloudbudget.pavnay.fr/api';
+    var HOST = 'http://cloudbudget-febbweiss.c9.io/api';
     
     angular
-        .module('cloudbudget', ['ngRoute', 'routes', 'ngCookies'])
+        .module('cloudbudget', ['ngRoute', 'routes', 'ngCookies', 'xeditable'])
         .constant('apiRoutes', {
-            'host': HOST,
-            'port': "80",
-            'login': HOST + '/users/login',
-            'register': HOST + '/users',
-            'unregister': HOST + '/users/'
+            'host'          : HOST,
+            'port'          : "80",
+            'login'         : HOST + '/users/login',
+            'register'      : HOST + '/users',
+            'unregister'    : HOST + '/users/',
+            'accounts'      : HOST + '/accounts/'
         })
         .run(run);
 
-    run.$inject = ['$rootScope', '$location', '$cookieStore', '$http', '$filter'];
-    function run( $rootScope, $location, $cookieStore, $http, $filter) {
+    run.$inject = ['$rootScope', '$location', '$cookieStore', '$http', '$filter', 'editableThemes', 'editableOptions'];
+    function run( $rootScope, $location, $cookieStore, $http, $filter, editableThemes, editableOptions) {
+        
+        editableThemes.bs3.inputClass = 'input-sm';
+        editableThemes.bs3.buttonsClass = 'btn-sm';
+        editableOptions.theme = 'bs3';
+        
         $rootScope.globals = $cookieStore.get('globals') || {};
         if( $rootScope.globals.user && $rootScope.globals.user.token) {
             $http.defaults.headers.common['Authorization'] = 'JWT ' + $rootScope.globals.user.token;

+ 6 - 0
public/js/routes.js

@@ -27,6 +27,12 @@
                 controllerAs: 'vm'
             })
             
+            .when('/accounts', {
+                controller: 'AccountsController',
+                templateUrl: 'accounts/accounts.view.html',
+                controllerAs: 'vm'
+            })
+            
             .otherwise({redirectTo: '/login'});
             
         $locationProvider.html5Mode(true);

+ 52 - 0
public/js/services/accounts.service.js

@@ -0,0 +1,52 @@
+(function() {
+    'use strict';
+    
+    angular
+        .module('cloudbudget')
+        .factory('AccountsService', AccountsService);
+        
+    AccountsService.$inject =['$http', 'apiRoutes'];
+    
+    function AccountsService($http, apiRoute) {
+        
+        var service = {};
+        service.list = list;
+        service.create = create;
+        service.drop = drop;
+        service.edit = edit;
+        
+        return service;
+        
+        function list() {
+            return $http.get( apiRoute.accounts)
+                .then(function handleSuccess(response) {
+                    return {success: true, accounts: response.data};
+                }, handleError('Error during accounts listing'));
+        }
+        
+        function create(account) {
+            return $http.post( apiRoute.accounts, account)
+                    .then(handleSuccess, handleError('Error creating account'));
+        }
+        
+        function drop(account) {
+            return $http.delete(apiRoute.accounts + account._id)
+                    .then(handleSuccess, handleError('Error deleting account'));
+        }
+        
+        function edit(id, account) {
+            return $http.put(apiRoute.accounts + id, account)
+                    .then(handleSuccess, handleError('Error updating account'));
+        }
+        
+        function handleSuccess(response) {
+            return {success: true, account: response.data};
+        }
+        
+        function handleError(error) {
+            return function() {
+                return {success: false, message: error};
+            };
+        }
+    }
+})();

+ 0 - 1
public/js/services/authentication.service.js

@@ -45,7 +45,6 @@
             
             $http.defaults.headers.common['Authorization'] = 'JWT ' + user.token;
             $cookieStore.put('globals', $rootScope.globals);
-            console.log( $cookieStore.get('globals'));
         }
         
         function clearCredentials() {

+ 1 - 1
public/login/login.controller.js

@@ -21,7 +21,7 @@
             AuthenticationService.login(vm.username, vm.password).then( function(response) {
                 if( response.success ) {
                     AuthenticationService.setCredentials(response.user);
-                    $location.path('/');
+                    $location.path('/accounts');
                 } else {
                     FlashService.error(response.message);
                     vm.dataLoading = false;

+ 25 - 19
public/login/login.view.html

@@ -1,21 +1,27 @@
-<div class="col-md-6 col-md-offset-3">
-    <h2>Login</h2>
-    <div ng-show="vm.error" class="alert alert-danger">{{vm.error}}</div>
-    <form name="form" ng-submit="vm.login()" role="form">
-        <div class="form-group" ng-class="{'has-error': form.username.$dirty && form.username.$error.required}">
-            <label for="username">Username</label>
-            <input type="text" name="username" id="username" class="form-control" ng-model="vm.username" required />
-            <span ng-show="form.username.$dirty && form.username.$error.required" class="help-block">Username is required</span>
-        </div>        
-        <div class="form-group" ng-class="{'has-error': form.password.$dirty && form.password.$error.required}">
-            <label for="password">Password</label>
-            <input type="password" name="password" id="password" class="form-control" ng-model="vm.password" required />
-            <span ng-show="form.password.$dirty && form.password.$error.required" class="help-block">Password is required</span>
-        </div>        
-        <div class="form-actions">
-            <button type="submit" ng-disabled="form.$invalid || vm.dataLoading" class="btn btn-primary">Login</button>
-            <i ng-if="vm.dataLoading" class="fa fa-spinner fa-spin"></i>
-            <a href="/register" class="btn btn-link">Register</a>
+<div class="jumbotron">
+    <div class="container">
+        <div class="col-sm-8 col-sm-offset-2">
+            <div class="col-md-6 col-md-offset-3">
+                <h2>Login</h2>
+                <div ng-show="vm.error" class="alert alert-danger">{{vm.error}}</div>
+                <form name="form" ng-submit="vm.login()" role="form">
+                    <div class="form-group" ng-class="{'has-error': form.username.$dirty && form.username.$error.required}">
+                        <label for="username">Username</label>
+                        <input type="text" name="username" id="username" class="form-control" ng-model="vm.username" required />
+                        <span ng-show="form.username.$dirty && form.username.$error.required" class="help-block">Username is required</span>
+                    </div>        
+                    <div class="form-group" ng-class="{'has-error': form.password.$dirty && form.password.$error.required}">
+                        <label for="password">Password</label>
+                        <input type="password" name="password" id="password" class="form-control" ng-model="vm.password" required />
+                        <span ng-show="form.password.$dirty && form.password.$error.required" class="help-block">Password is required</span>
+                    </div>        
+                    <div class="form-actions">
+                        <button type="submit" ng-disabled="form.$invalid || vm.dataLoading" class="btn btn-primary">Login</button>
+                        <i ng-if="vm.dataLoading" class="fa fa-spinner fa-spin"></i>
+                        <a href="/register" class="btn btn-link">Register</a>
+                    </div>
+                </form>
+            </div>
         </div>
-    </form>
+    </div>
 </div>

+ 1 - 1
public/register/register.controller.js

@@ -20,7 +20,7 @@
                     if( response.success ) {
                         AuthenticationService.setCredentials(response.user);
                         FlashService.success('Registration successful', true);
-                        $location.path('/');
+                        $location.path('/accounts');
                     } else {
                         FlashService.error(response.message);
                         vm.dataLoading = false;

+ 33 - 27
public/register/register.view.html

@@ -1,29 +1,35 @@
-<div class="col-md-6 col-md-offset-3">
-    <h2>Register</h2>
-    <div ng-show="vm.error" class="alert alert-danger">{{vm.error}}</div>
-    <form name="form" ng-submit="vm.register()" role="form">
-        <div class="form-group" ng-class="{'has-error': form.username.$dirty && form.username.$error.required}">
-            <label for="username">Username</label>
-            <input type="text" name="username" id="username" class="form-control" ng-model="vm.user.username" required />
-            <span ng-show="form.username.$dirty && form.username.$error.required" class="help-block">Username is required</span>
-        </div>        
-        <div class="form-group" ng-class="{'has-error': form.password.$dirty && form.password.$error.required}">
-            <label for="password">Password</label>
-            <input type="password" name="password" id="password" class="form-control" ng-model="vm.user.password" required />
-            <span ng-show="form.password.$dirty && form.password.$error.required" class="help-block">Password is required</span>
-        </div>      
-        <div class="form-group" ng-class="{'has-error': form.language.$dirty && form.language.$error.required}">
-            <label for="language">Language</label>
-            <select id="language" name="language" ng-model="vm.user.language" required>
-                <option value="en">English</option>
-                <option value="fr">Français</option>
-            </select>
-            <span ng-show="form.language.$dirty && form.language.$error.required" class="help-block">Language is required</span>
+<div class="jumbotron">
+    <div class="container">
+        <div class="col-sm-8 col-sm-offset-2">
+            <div class="col-md-6 col-md-offset-3">
+                <h2>Register</h2>
+                <div ng-show="vm.error" class="alert alert-danger">{{vm.error}}</div>
+                <form name="form" ng-submit="vm.register()" role="form">
+                    <div class="form-group" ng-class="{'has-error': form.username.$dirty && form.username.$error.required}">
+                        <label for="username">Username</label>
+                        <input type="text" name="username" id="username" class="form-control" ng-model="vm.user.username" required />
+                        <span ng-show="form.username.$dirty && form.username.$error.required" class="help-block">Username is required</span>
+                    </div>        
+                    <div class="form-group" ng-class="{'has-error': form.password.$dirty && form.password.$error.required}">
+                        <label for="password">Password</label>
+                        <input type="password" name="password" id="password" class="form-control" ng-model="vm.user.password" required />
+                        <span ng-show="form.password.$dirty && form.password.$error.required" class="help-block">Password is required</span>
+                    </div>      
+                    <div class="form-group" ng-class="{'has-error': form.language.$dirty && form.language.$error.required}">
+                        <label for="language">Language</label>
+                        <select id="language" name="language" class="form-control" ng-model="vm.user.language" required>
+                            <option value="en">English</option>
+                            <option value="fr">Français</option>
+                        </select>
+                        <span ng-show="form.language.$dirty && form.language.$error.required" class="help-block">Language is required</span>
+                    </div>
+                    <div class="form-actions">
+                        <button type="submit" ng-disabled="form.$invalid || vm.dataLoading" class="btn btn-primary">Register</button>
+                        <img ng-if="vm.dataLoading" src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" />
+                        <a href="/login" class="btn btn-link">Cancel</a>
+                    </div>
+                </form>
+            </div>
         </div>
-        <div class="form-actions">
-            <button type="submit" ng-disabled="form.$invalid || vm.dataLoading" class="btn btn-primary">Register</button>
-            <img ng-if="vm.dataLoading" src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" />
-            <a href="/login" class="btn btn-link">Cancel</a>
-        </div>
-    </form>
+    </div>
 </div>

+ 2 - 1
server.js

@@ -10,7 +10,8 @@ router.get('*', function(req, res, next) {
   var dotIndex = req.path.lastIndexOf('.'),
       extension = dotIndex === - 1 ? '' : req.path.substr(dotIndex);
       
-      if( ['.js','.css','.html'].indexOf(extension) > -1 ) {
+      if( ['.js','.css','.html', 
+        '.woff', '.woff2', '.ttf', '.svg', '.eot', '.otf'].indexOf(extension) > -1 ) {
         next();
       } else {
         res.sendfile('./public/index.html');

+ 184 - 0
test/accounts.controller.spec.js

@@ -0,0 +1,184 @@
+describe('AccountsController', function() {
+    
+    var $location,
+        $rootScope,
+        $scope,
+        $timeout,
+        $httpBackend,
+        AccountsService,
+        FlashService,
+        createController,
+        apiRoutes,
+        shouldPass,
+        DEFAULT_ACCOUNT = {
+            "name": "test",
+            "reference": "1234567890",
+            "user_id": "55b78934d2a706265ea28e9c",
+            "_id": "560aa0e79633cd7c1495ff21"
+        };
+        
+    beforeEach(module('cloudbudget'));
+    
+    beforeEach(inject(function ( _$rootScope_, _$httpBackend_,  $controller, _$location_, _$timeout_, _AccountsService_, _FlashService_, _apiRoutes_) {
+        $location = _$location_;
+        $httpBackend = $httpBackend;
+        $rootScope = _$rootScope_.$new();
+        $scope = _$rootScope_.$new();
+        $scope.form = {
+          $valid: true,
+          $setPristine: function() {}
+        };
+        $timeout = _$timeout_;
+        AccountsService = _AccountsService_;
+        FlashService = _FlashService_;
+        apiRoutes = _apiRoutes_;
+        
+        createController = function() {
+            return $controller('AccountsController', {
+                '$scope': $scope,
+                '$location': $location,
+                '$rootScope': $rootScope,
+                FlashService: _FlashService_,
+                AccountsService: _AccountsService_,
+            });
+        };
+    }));
+    
+    describe('init()', function() {
+        it('should create successfully', inject(function($controller, $httpBackend) {
+            $httpBackend.expect('GET', apiRoutes.accounts)
+                .respond([DEFAULT_ACCOUNT]);
+    
+                
+            var accountsController = createController();
+            $httpBackend.flush();
+            $timeout.flush();
+            
+            accountsController.accounts.should.be.instanceof(Array).and.have.lengthOf(1);
+        }));
+    });
+    
+    describe('* create()', function() {
+        it('should create successfully', inject(function($controller, $httpBackend) {
+            $httpBackend.expect('GET', apiRoutes.accounts)
+                .respond([]);
+                
+            $httpBackend.expect('POST', apiRoutes.accounts)
+                .respond(DEFAULT_ACCOUNT);
+
+            
+            var accountsController = createController();
+            accountsController.account = {
+                name: 'test',
+                reference: '1234567890'
+            };
+            
+            accountsController.create();
+            $httpBackend.flush();
+            $timeout.flush();
+            
+            var account = accountsController.accounts[0];
+            account.name.should.be.equal('test');
+            account.reference.should.be.equal('1234567890');
+            should.exist(account._id);
+        }));
+        
+        it('should fail to create account', inject(function($controller, $httpBackend) {
+            $httpBackend.expect('GET', apiRoutes.accounts)
+                .respond([]);
+                
+            $httpBackend.expect('POST', apiRoutes.accounts)
+                .respond(400, [{"field":"name","rule":"required","message":"Path `name` is required."}]);
+
+            
+            var accountsController = createController();
+            accountsController.account = {
+                reference: '1234567890'
+            };
+            
+            accountsController.create();
+            $httpBackend.flush();
+            $timeout.flush();
+            
+            accountsController.accounts.should.be.instanceof(Array).and.have.lengthOf(0);
+        }));
+    });
+    
+    describe('* delete()', function() {
+        it('should delete successfully', inject(function($controller, $httpBackend) {
+            $httpBackend.expect('GET', apiRoutes.accounts)
+                .respond([DEFAULT_ACCOUNT]);
+                
+            $httpBackend.expect('DELETE', apiRoutes.accounts + '560aa0e79633cd7c1495ff21')
+                .respond(204);
+
+            
+            var accountsController = createController();
+            accountsController.drop({_id: '560aa0e79633cd7c1495ff21'});
+            $httpBackend.flush();
+            $timeout.flush();
+            
+            accountsController.accounts.should.be.instanceof(Array).and.have.lengthOf(0);
+        }));
+        
+        it('should fail to delete unknown account', inject(function($controller, $httpBackend) {
+            $httpBackend.expect('GET', apiRoutes.accounts)
+                .respond([DEFAULT_ACCOUNT]);
+                
+            $httpBackend.expect('DELETE', apiRoutes.accounts + 'fake_id')
+                .respond(404);
+
+            
+            var accountsController = createController();
+            accountsController.drop({_id: 'fake_id'});
+            $httpBackend.flush();
+            $timeout.flush();
+            
+            accountsController.accounts.should.be.instanceof(Array).and.have.lengthOf(1);
+        }));
+    });
+    
+    describe('* edit()', function() {
+        it('should edit successfully', inject(function($controller, $httpBackend) {
+            $httpBackend.expect('GET', apiRoutes.accounts)
+                .respond([DEFAULT_ACCOUNT]);
+                
+            $httpBackend.expect('PUT', apiRoutes.accounts + '560aa0e79633cd7c1495ff21')
+                .respond(200, {
+                    "name": "test updated",
+                    "reference": "1234567890",
+                    "user_id": "55b78934d2a706265ea28e9c",
+                    "_id": "560aa0e79633cd7c1495ff21"
+                });
+
+            
+            var accountsController = createController();
+            accountsController.edit({ name:"test updated"}, DEFAULT_ACCOUNT);
+            $httpBackend.flush();
+            $timeout.flush();
+            
+            accountsController.accounts.should.be.instanceof(Array).and.have.lengthOf(1);
+            var account = accountsController.accounts[0];
+            account.name.should.be.equal('test updated');
+        }));
+        
+        it('should fail to edit unknown account', inject(function($controller, $httpBackend) {
+            $httpBackend.expect('GET', apiRoutes.accounts)
+                .respond([DEFAULT_ACCOUNT]);
+                
+            $httpBackend.expect('PUT', apiRoutes.accounts + 'fake_id')
+                .respond(404);
+
+            
+            var accountsController = createController();
+            accountsController.edit({name:"test updated"}, {_id: 'fake_id'});
+            $httpBackend.flush();
+            $timeout.flush();
+            
+            accountsController.accounts.should.be.instanceof(Array).and.have.lengthOf(1);
+            var account = accountsController.accounts[0];
+            account.name.should.be.equal('test');
+        }));
+    });
+    
+});

+ 1 - 1
test/login.controller.spec.js

@@ -78,7 +78,7 @@ describe('LoginController', function() {
             loginController.login();
             $timeout.flush();
             
-            $location.path().should.be.equal('/');
+            $location.path().should.be.equal('/accounts');
         }));
         
         it('should fail to log', inject(function($controller, $location) {

+ 1 - 1
test/register.controller.spec.js

@@ -97,7 +97,7 @@ describe('RegisterController', function() {
             $httpBackend.flush();
             $timeout.flush();
             
-            $location.path().should.be.equal('/');
+            $location.path().should.be.equal('/accounts');
         }));
         
         it('should fail to register on bad parameter', inject(function($controller, $httpBackend, $location) {