From fdedcdcdffcc60dcd5bef5b9960ec25f36c452ca Mon Sep 17 00:00:00 2001 From: febbweiss Date: Thu, 1 Oct 2015 09:09:48 +0000 Subject: [PATCH] Feature: get user's accounts --- bower.json | 3 +- karma.conf.js | 1 + public/accounts/accounts.controller.js | 83 +++++++++ public/accounts/accounts.view.html | 49 +++++ public/index.html | 40 ++-- public/js/app.js | 24 ++- public/js/routes.js | 6 + public/js/services/accounts.service.js | 52 ++++++ public/js/services/authentication.service.js | 1 - public/login/login.controller.js | 2 +- public/login/login.view.html | 44 +++-- public/register/register.controller.js | 2 +- public/register/register.view.html | 60 +++--- server.js | 3 +- test/accounts.controller.spec.js | 184 +++++++++++++++++++ test/login.controller.spec.js | 2 +- test/register.controller.spec.js | 2 +- 17 files changed, 476 insertions(+), 82 deletions(-) create mode 100644 public/accounts/accounts.controller.js create mode 100644 public/accounts/accounts.view.html create mode 100644 public/js/services/accounts.service.js create mode 100644 test/accounts.controller.spec.js diff --git a/bower.json b/bower.json index bb9fc12..1d55bb6 100644 --- a/bower.json +++ b/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" diff --git a/karma.conf.js b/karma.conf.js index becc510..02d8fa5 100644 --- a/karma.conf.js +++ b/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', diff --git a/public/accounts/accounts.controller.js b/public/accounts/accounts.controller.js new file mode 100644 index 0000000..f0475de --- /dev/null +++ b/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); + }; + } +})(); \ No newline at end of file diff --git a/public/accounts/accounts.view.html b/public/accounts/accounts.view.html new file mode 100644 index 0000000..9e57d6f --- /dev/null +++ b/public/accounts/accounts.view.html @@ -0,0 +1,49 @@ +
+
+
+
+
+ +
+
+
+
+ + name is required +
+
+
+ + +
+
+
+ +
+
+ {{account.reference}} +
+
{{account.name}}
+
+
+ + + + + +
+ + + + + + +
+
+
\ No newline at end of file diff --git a/public/index.html b/public/index.html index 03b55df..c1cb949 100644 --- a/public/index.html +++ b/public/index.html @@ -5,7 +5,9 @@ CloudBudget - + + +
@@ -19,14 +21,9 @@
-
-
-
-
-
-
-
-
+ +
+

@@ -37,18 +34,21 @@

- - - + + + + - - - - - + + + + + + - - - + + + + \ No newline at end of file diff --git a/public/js/app.js b/public/js/app.js index 7391501..3e966ec 100644 --- a/public/js/app.js +++ b/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; diff --git a/public/js/routes.js b/public/js/routes.js index cf6debc..aff061f 100644 --- a/public/js/routes.js +++ b/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); diff --git a/public/js/services/accounts.service.js b/public/js/services/accounts.service.js new file mode 100644 index 0000000..315d509 --- /dev/null +++ b/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}; + }; + } + } +})(); \ No newline at end of file diff --git a/public/js/services/authentication.service.js b/public/js/services/authentication.service.js index 3a4951d..aae0625 100644 --- a/public/js/services/authentication.service.js +++ b/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() { diff --git a/public/login/login.controller.js b/public/login/login.controller.js index 10556b5..dc92eab 100644 --- a/public/login/login.controller.js +++ b/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; diff --git a/public/login/login.view.html b/public/login/login.view.html index ff834c3..7cb4d88 100644 --- a/public/login/login.view.html +++ b/public/login/login.view.html @@ -1,21 +1,27 @@ -
-

Login

-
{{vm.error}}
-
-
- - - Username is required -
-
- - - Password is required -
-
- - - Register +
+
+
+
+

Login

+
{{vm.error}}
+ +
+ + + Username is required +
+
+ + + Password is required +
+
+ + + Register +
+ +
- +
\ No newline at end of file diff --git a/public/register/register.controller.js b/public/register/register.controller.js index 7641bdf..0004372 100644 --- a/public/register/register.controller.js +++ b/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; diff --git a/public/register/register.view.html b/public/register/register.view.html index 369b6df..70e8cec 100644 --- a/public/register/register.view.html +++ b/public/register/register.view.html @@ -1,29 +1,35 @@ -
-

Register

-
{{vm.error}}
-
-
- - - Username is required -
-
- - - Password is required -
-
- - - Language is required +
+
+
+
+

Register

+
{{vm.error}}
+ +
+ + + Username is required +
+
+ + + Password is required +
+
+ + + Language is required +
+
+ + + Cancel +
+ +
-
- - - Cancel -
- +
\ No newline at end of file diff --git a/server.js b/server.js index cd69996..f22bbdb 100644 --- a/server.js +++ b/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'); diff --git a/test/accounts.controller.spec.js b/test/accounts.controller.spec.js new file mode 100644 index 0000000..b760706 --- /dev/null +++ b/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'); + })); + }); + +}); \ No newline at end of file diff --git a/test/login.controller.spec.js b/test/login.controller.spec.js index 31cc2f1..2034632 100644 --- a/test/login.controller.spec.js +++ b/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) { diff --git a/test/register.controller.spec.js b/test/register.controller.spec.js index 93f8e34..7fc6a83 100644 --- a/test/register.controller.spec.js +++ b/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) {