Skip to content

Commit

Permalink
Add Current User to front end (#74)
Browse files Browse the repository at this point in the history
- Add navDataService methods to handle current user load
  - Add current user fecthed from real API throught Restangular
    decorator
- Update style when user is not logged in
- Update specs to match new functionality
  - Update LoginCtrl.spec
  - Update NavbarDirective.spec
  - Update navDataService.spec
  - Update BodyCtrl.spec

- Other changes
  - Fix responsive meta tag missing
  - Fix codestat issues
  • Loading branch information
MatiasComercio authored Feb 2, 2017
1 parent 77f5498 commit 68ea41d
Show file tree
Hide file tree
Showing 16 changed files with 307 additions and 63 deletions.
11 changes: 11 additions & 0 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,19 @@ engines:
exclude_fingerprints:
- f8ff5182bf5ef6e61d42e67f19c2f626
- f6bb76a706e7f9228733a0dce397756f
# app/scripts/services/navDataService.js:28
# app/scripts/services/navDataService.js:31
- 91551b4331243dfbdf688d307854fe64
# app/scripts/directives/sidebar.js:114
# app/scripts/directives/sidebar.js:128
- e328a668e91fa9068b8c4772406d32df
# app/scripts/services/Paths.js:121
# app/scripts/services/Paths.js:126
# app/scripts/services/Paths.js:156
- b80316107eb8426f812376a2b1db6edc
exclude_paths:
- "app/specs/"
- "Gruntfile.js"
config:
languages:
- javascript
Expand Down
5 changes: 4 additions & 1 deletion app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
<title translate='i18nWebAbbreviation'></title>
<meta name='description' content=''>

<!-- Responsive viewport -->
<meta name="viewport" content="width=device-width, initial-scale=1">

<!-- build:css(.tmp) styles/main.css -->
<link rel='stylesheet' href='styles/main.css'>
<!-- endbuild -->
Expand All @@ -18,7 +21,7 @@
<!-- \site navigation elements -->

<!-- dynamic content -->
<div class='container'>
<div class='container' ng-class="{'sidebar-hidden' : !controller.user}">
<!--[if lt IE 7]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
Expand Down
11 changes: 9 additions & 2 deletions app/scripts/controllers/BodyCtrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@
define(
['paw',
'directives/navbar',
'directives/sidebar'],
'directives/sidebar',
'services/navDataService'],
function(paw) {
paw.controller('BodyCtrl', [function() {
paw.controller('BodyCtrl', ['navDataService', function(navDataService) {
var _this = this;
var getUser = function() {
_this.user = navDataService.get('user');
};
navDataService.registerObserverCallback('user', getUser);
getUser();
}]);
}
);
9 changes: 6 additions & 3 deletions app/scripts/controllers/LoginCtrl.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
'use strict';

define(['paw', 'restangular', 'services/Authentication', 'services/Paths'], function(paw) {
paw.controller('LoginCtrl', ['Paths', '$log', 'Authentication',
function(Paths, $log, Authentication) {
define(['paw', 'restangular', 'services/Authentication',
'services/Paths', 'services/navDataService'],
function(paw) {
paw.controller('LoginCtrl', ['Paths', '$log', 'Authentication', 'navDataService',
function(Paths, $log, Authentication, navDataService) {
var _this = this;

this.login = function(user) {
// user would never be undefined as the form has to be valid before calling this method
// and that means that the user has to have some value defined
Authentication.login(user).then(function(authToken) {
Authentication.setToken(authToken);
navDataService.fetchUser();
Paths.get().index().go();
}, function(response) {
// here we should handle any issue and show a nice error message
Expand Down
27 changes: 16 additions & 11 deletions app/scripts/directives/navbar.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
'use strict';

define(['paw', 'services/navDataService', 'services/Paths'], function(paw) {
paw.directive('xnavbar', ['navDataService', 'Paths', function(navDataService, Paths) {
define(['paw', 'services/navDataService', 'services/Paths'],
function(paw) {
paw.directive('xnavbar', ['navDataService', 'Paths',
function(navDataService, Paths, NavbarService) {
function controller() {
this.user = {
fullName: 'Matías Nicolás Comercio Vázquez asdasdasdasdasdasdasdasdadasdasdas',
profileUrl: '/users/1',
authorities: {
admins: true,
students: true,
courses: true
}
var _this = this;

var fetchUser = function () {
_this.user = navDataService.get('user');
};

navDataService.set('user', this.user);
// If the user might change, we fetch it again
navDataService.registerObserverCallback('user', fetchUser);

// Try to force navDataService to fetch the user
navDataService.fetchUser();

// Actually fetch the user
fetchUser();
};

return {
Expand Down
64 changes: 59 additions & 5 deletions app/scripts/services/navDataService.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,61 @@
'use strict';

define(['paw', 'services/memoryFactory'], function(paw) {
paw.service('navDataService', ['memoryFactory', function(memoryFactory) {
return memoryFactory.newMemory();
}]);
});
define(
['paw',
'services/memoryFactory',
'services/Authentication',
'services/AuthenticatedRestangular'],
function(paw) {
paw.service('navDataService',
['memoryFactory',
'Authentication',
'AuthenticatedRestangular',
'Paths',
function(memoryFactory, Authentication, AuthenticatedRestangular, Paths) {
var memory = memoryFactory.newMemory();
var userKey = 'user';

var rest = AuthenticatedRestangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.extendModel('user', function(user) {
user.fullName = user.firstName + ' ' + user.lastName;

user.authorities = {
students: true,
courses: true
};

if (user.role === 'ADMIN') {
user.authorities.admins = true;
user.profileUrl = Paths.get().admins(user).path;
} else { // role === 'STUDENT'
user.authorities.admins = false;
user.profileUrl = Paths.get().students(user).path;
};

return user;
});
});

memory.fetchUser = function() {
var user = memory.get(userKey);
if (user !== undefined || !Authentication.isLoggedIn()) {
// user already fetched or not yet logged in => nothing to fetch
return;
}

// user === undefined but it is logged in ==> we should fetch the user for the first time
//
// This case can be reached when the user closes all navigation tabs, but token cookie
// is still stored, and the user directly access an endpoint rather than '/login'

// fetch the user
rest.one('user').get().then(function(fetchedUser) {
// and set it on the navDataService
memory.set(userKey, fetchedUser);
});
};

return memory;
}]);
}
);
25 changes: 25 additions & 0 deletions app/specs/apiResponses.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,31 @@ define(['paw'], function(paw) {
'firstName': 'Matias Nicolas',
'lastName': 'Comercio Vazquez'
};

this.currentAdmin = {
'birthday': '1995-06-10',
'dni': 38457013,
'email': 'a38457012@bait.edu.ar',
'firstName': 'Matias Nicolas',
'genre': 'M',
'lastName': 'Comercio Vazquez',
'role': 'ADMIN',
'address': {
'city': 'CABA',
'country': 'Argentina',
'door': '',
'neighborhood': 'Puerto Madero',
'number': 399,
'street': 'E. Madero'
},
'authorities': {
students: true,
courses: true,
admins: true
},
profileUrl: '#!/admins/38457013'
};
this.currentAdmin.fullName = this.currentAdmin.firstName + ' ' + this.currentAdmin.lastName;
}
]);
});
24 changes: 20 additions & 4 deletions app/specs/controllers/BodyCtrl.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,36 @@

define(['paw',
'angular-mocks',
'api-responses',
'services/navDataService',
'controllers/BodyCtrl'],
function() {
describe('Body Controller', function() {
beforeEach(module('paw'));

var $controller, controller;
var $controller, $rootScope, controller, navDataServiceService, expectedUser;

beforeEach(inject(function(_$controller_) {
beforeEach(inject(function(_$controller_, _$rootScope_, apiResponses, navDataService) {
$controller = _$controller_;
$rootScope = _$rootScope_;
expectedUser = apiResponses.currentAdmin;
navDataServiceService = navDataService;
controller = $controller('BodyCtrl');
}));

it('exists', function() {
expect(controller).toBeDefined();
describe('when checking subscriptions', function() {
var expecteduser;

beforeEach(function() {
$rootScope.$apply(function() {
controller.user = undefined; // clean any possible data
navDataServiceService.set('user', expectedUser);
});
});

it('is subscribed to user changes event', function() {
expect(controller.user).toEqual(expectedUser);
});
});
});
});
14 changes: 12 additions & 2 deletions app/specs/controllers/LoginCtrl.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,30 @@ define(['paw',
'spec-utils',
'api-responses',
'services/Paths',
'services/navDataService',
'controllers/LoginCtrl'],
function() {
describe('Login Ctrl', function() {
beforeEach(module('paw'));

var $controller, $rootScope, $location, $log, controller,
specUtilsService, apiResponsesService, AuthenticationService;
specUtilsService, apiResponsesService, AuthenticationService,
navDataServiceService;

beforeEach(inject(
function(_$controller_, _$rootScope_, _$location_, _$log_, specUtils, apiResponses, Authentication) {
function(_$controller_, _$rootScope_, _$location_,
_$log_, specUtils, apiResponses, Authentication,
navDataService) {
$controller = _$controller_;
$rootScope = _$rootScope_;
$location = _$location_;
$log = _$log_;
specUtilsService = specUtils;
apiResponsesService = apiResponses;
AuthenticationService = Authentication;
navDataServiceService = navDataService;
controller = $controller('LoginCtrl');
spyOn(navDataServiceService, 'fetchUser');
}));

describe('when logging in', function() {
Expand All @@ -54,6 +60,10 @@ function() {
expect(AuthenticationService.setToken).toHaveBeenCalledWith(expectedToken);
});

it('calls fetchs the user on the navDataService', function() {
expect(navDataServiceService.fetchUser).toHaveBeenCalled();
});

it('redirects to index', inject(function(Paths) {
expect($location.path).toHaveBeenCalledWith(Paths.get().index().absolutePath());
}));
Expand Down
49 changes: 31 additions & 18 deletions app/specs/directives/navbar.spec.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
'use strict';

define(['paw', 'angular-mocks', 'directives/navbar', 'navbar-template'], function() {
define([
'paw',
'angular-mocks',
'directives/navbar',
'navbar-template',
'api-responses',
'services/navDataService'], function() {
describe('NavbarDirective', function() {
beforeEach(module('paw'));
beforeEach(module('directive-templates'));

var $compile, $rootScope, scope, navbar, controller;
var $compile, $rootScope, scope, navbar, controller, navDataServiceService,
apiResponsesService;
var element = angular.element('<xnavbar></xnavbar>');

// for now, hardcoded data (when API integration is ready ==> replace this)
var user = {
fullName: 'Matías Nicolás Comercio Vázquez asdasdasdasdasdasdasdasdadasdasdas',
profileUrl: '/users/1',
authorities: {
admins: true,
students: true,
courses: true
}
};

beforeEach(inject(function(_$compile_, _$rootScope_) {
beforeEach(inject(function(_$compile_, _$rootScope_, navDataService, apiResponses) {
$compile = _$compile_;
$rootScope = _$rootScope_;
navDataServiceService = navDataService;
apiResponsesService = apiResponses;
spyOn(navDataServiceService, 'fetchUser');
scope = $rootScope.$new();
navbar = compileDirective(scope);
controller = navbar.controller('xnavbar');
Expand All @@ -41,10 +40,20 @@ define(['paw', 'angular-mocks', 'directives/navbar', 'navbar-template'], functio
expect(navbar.find('.top-nav')[0]).toBeDefined();
});

it('correctly fetch the user', function() {
expect(controller.user).toEqual(user);
});
describe('when testing how the user is fetched', function() {
var expectedUser;
beforeEach(function() {
expectedUser = apiResponsesService.currentAdmin;
scope.$apply(function() {
controller.user = undefined;
navDataServiceService.set('user', expectedUser);
});
});

it('fetchs the user from a callback on navDataService', function() {
expect(controller.user).toEqual(expectedUser);
});
});

describe('when user does not exist on controller', function() {
beforeEach(function() {
Expand All @@ -56,12 +65,16 @@ define(['paw', 'angular-mocks', 'directives/navbar', 'navbar-template'], functio
it('does not show the user menu', function() {
expect(navbar.find('.top-nav').hasClass('ng-hide')).toBe(true);
});

it('does not show the sandwich-icon', function() {
expect(navbar.find('.sandwich-icon').hasClass('ng-hide')).toBe(true);
});
});

describe('when user exists on controller', function() {
beforeEach(function() {
scope.$apply(function() {
controller.user = user;
controller.user = apiResponsesService.currentAdmin;
});
});

Expand Down
Loading

0 comments on commit 68ea41d

Please sign in to comment.