Skip to content

Commit

Permalink
Add restangular for app usage (#59)
Browse files Browse the repository at this point in the history
- Add grunt-connect-proxy npm dependency
  - Update Gruntfile to use this dependency.
  - API must be served at localhost:7070 during development so as to be
    able to use livereload of angular changes and use a static version
    (war) of the API.
  - Solve Cross Origin request problem with browsers
- Add Students service extending Restangular
- Add Courses service extending Restangular
- Add example of Students and Courses services usage on HomeCtrl
- Add spec utils service
- Add api responses service for testing purposes only (working as
  cassettes for api calls)
- Add all test cases

- Add simple authentication management
  - Add Authentication service
  - Add $cookies service to handle user session
  - Update login endpoint (back) to match the API path
  - Add token header to services using restangular
  - Add new spec cases for added components
- Add AuthenticatedRestangular factory
- Rename BodyController -> BodyCtrl

- Notes
  - Authentication should be improved, but was implemented as to be
    able to deploy and test the app.
  - Login is served at the /login path and should be completed in order to
    use the rest of the app's requests.
  • Loading branch information
MatiasComercio authored Jan 26, 2017
1 parent 18ef123 commit dca63d2
Show file tree
Hide file tree
Showing 29 changed files with 949 additions and 21 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,4 @@ dist/
.tmp/

coverage/
logs/
20 changes: 19 additions & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ module.exports = function (grunt) { // eslint-disable-line strict

require('time-grunt')(grunt);

var proxySnippet = require('grunt-connect-proxy/lib/utils').proxyRequest;

var appConfig = {
app: 'app',
dist: 'dist'
Expand All @@ -19,14 +21,29 @@ module.exports = function (grunt) { // eslint-disable-line strict
hostname: 'localhost',
livereload: 35729
},
server: {
proxies: [
{
context: '/api/v1',
host: 'localhost',
port: 7070,
https: false,
changeOrigin: true,
rewrite: {
'^/api/v1': '/grupo1/api/v1'
}
}
]
},
livereload: {
options: {
open: false,
middleware: function (connect) {
return [
connect.static('.tmp'),
connect().use('/bower_components', connect.static('./bower_components')),
connect.static(appConfig.app)
connect.static(appConfig.app),
proxySnippet
];
}
}
Expand Down Expand Up @@ -467,6 +484,7 @@ module.exports = function (grunt) { // eslint-disable-line strict
'concurrent:server',
'autoprefixer',
'bower',
'configureProxies:server',
'connect:livereload',
'watch'
]);
Expand Down
2 changes: 1 addition & 1 deletion app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<!-- endbuild -->

</head>
<body ng-controller='BodyController as controller'>
<body ng-controller='BodyCtrl as controller'>

<!-- site navigation elements -->
<xnavbar></xnavbar>
Expand Down
25 changes: 22 additions & 3 deletions app/scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require.config({
alert: '../../bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/alert',
angular: '../../bower_components/angular/angular',
'angular-route': '../../bower_components/angular-route/angular-route',
'angular-cookies': '../../bower_components/angular-cookies/angular-cookies',
'angular-translate': '../../bower_components/angular-translate/angular-translate',
'angular-mocks': '../../bower_components/angular-mocks/angular-mocks',
button: '../../bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/button',
Expand All @@ -31,19 +32,27 @@ require.config({
'angular-bootstrap': '../../bower_components/angular-bootstrap/ui-bootstrap-tpls',
'jquery-mousewheel': '../../bower_components/jquery-mousewheel/jquery.mousewheel',
'angular-material': '../../bower_components/angular-material/angular-material',
'angular-aria': '../../bower_components/angular-aria/angular-aria'
'angular-aria': '../../bower_components/angular-aria/angular-aria',
lodash: '../../bower_components/lodash/dist/lodash',
restangular: '../../bower_components/restangular/dist/restangular'
},
shim: {
angular: {
deps: [
'jquery'
]
],
exports: 'angular'
},
'angular-route': {
deps: [
'angular'
]
},
'angular-cookies': {
deps: [
'angular'
]
},
'angular-animate': {
deps: [
'angular'
Expand Down Expand Up @@ -84,6 +93,15 @@ require.config({
deps: [
'angular'
]
},
lodash: {
exports: '_'
},
restangular: {
deps: [
'angular',
'lodash'
]
}
},
packages: [
Expand All @@ -99,8 +117,9 @@ if (paths) {

require([
'angular',
'restangular',
'paw',
'controllers/BodyController'
'controllers/BodyCtrl'
],
function() {
angular.bootstrap(document, ['paw']);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ define(
'directives/navbar',
'directives/sidebar'],
function(paw) {
paw.controller('BodyController', [function() {
paw.controller('BodyCtrl', [function() {
}]);
}
);
47 changes: 43 additions & 4 deletions app/scripts/controllers/HomeCtrl.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,49 @@
'use strict';

define(['paw', 'services/navDataService'], function(paw) {
define(['paw',
'services/navDataService',
'services/Students',
'services/Courses'],
function(paw) {
paw.controller('HomeCtrl',
['navDataService',
function(navDataService) {
['navDataService', 'Students', 'Courses',
function(navDataService, Students, Courses) {
this.courses = [];
this.students = [];

var _this = this;

this.toggleCourses = function() {
if (_this.courses.length === 0) {
Courses.getList().then(function(courses) {
_this.courses = _this.courses.concat(courses);
});
} else {
_this.courses = [];
}
};

this.toggleCourse = function(course) {
course.get().then(function(courseData) {
_this.course = courseData;
});
};

this.toggleStudents = function() {
if (_this.students.length === 0) {
Students.getList().then(function(students) {
_this.students = _this.students.concat(students);
});
} else {
_this.students = [];
}
};

this.toggleStudent = function(student) {
student.get().then(function(studentData) {
_this.student = studentData;
});
};

this.fetchCourse = function() {
return {
Expand Down Expand Up @@ -91,7 +131,6 @@ define(['paw', 'services/navDataService'], function(paw) {
};
};

var _this = this;
function fetchData() {
return {
// admin: _this.fetchAdmin()
Expand Down
20 changes: 20 additions & 0 deletions app/scripts/controllers/LoginCtrl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict';

define(['paw', 'restangular', 'services/Authentication'], function(paw) {
paw.controller('LoginCtrl', ['$location', '$log', 'Authentication',
function($location, $log, Authentication) {
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);
$location.path('/');
}, function(response) {
// here we should handle any issue and show a nice error message
$log.info('Response status: ' + response.status);
});
};
}]);
});
8 changes: 7 additions & 1 deletion app/scripts/paw.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ define(['routes',
'i18n/i18nLoader!',
'angular',
'angular-route',
'angular-cookies',
'angular-animate',
'bootstrap',
'angular-translate',
Expand All @@ -14,9 +15,11 @@ define(['routes',
function(config, dependencyResolverFor, i18n) {
var paw = angular.module('paw', [
'ngRoute',
'ngCookies',
'ngAnimate',
'ngMaterial',
'ui.bootstrap',
'restangular',
'pascalprecht.translate'
]);
paw
Expand All @@ -27,7 +30,8 @@ define(['routes',
'$filterProvider',
'$provide',
'$translateProvider',
function($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide, $translateProvider) {
'RestangularProvider',
function($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide, $translateProvider, RestangularProvider) {

paw.controller = $controllerProvider.register;
paw.directive = $compileProvider.directive;
Expand All @@ -47,6 +51,8 @@ define(['routes',
$translateProvider.translations('preferredLanguage', i18n);
$translateProvider.preferredLanguage('preferredLanguage');
$translateProvider.useSanitizeValueStrategy('escape');

RestangularProvider.setBaseUrl('/api/v1/');
}]);
return paw;
}
Expand Down
4 changes: 4 additions & 0 deletions app/scripts/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ define([], function() {
'/': {
templateUrl: '/views/home.html',
controller: 'HomeCtrl'
},
'/login': {
templateUrl: '/views/login.html',
controller: 'LoginCtrl'
}
/* ===== yeoman hook ===== */
/* Do not remove these commented lines! Needed for auto-generation */
Expand Down
23 changes: 23 additions & 0 deletions app/scripts/services/AuthenticatedRestangular.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict';

define(['paw', 'restangular', 'services/Authentication'], function(paw) {
paw.factory('AuthenticatedRestangular', ['Restangular', 'Authentication',
function(Restangular, Authentication) {
return Restangular.withConfig(function(RestangularConfigurer) {
// cannot use RestangularConfigurer.setDefaultHeaders() as it is called
// only once and before the token is set
// This requests the token each time a request is made
// and in this instance it should already be set
RestangularConfigurer.addFullRequestInterceptor(
function(element, operation, route, url, headers, params, httpConfig) {
return {
element: element,
params: params,
headers: _.extend(headers, Authentication.getHeader()),
httpConfig: httpConfig
};
}
);
});
}]);
});
33 changes: 33 additions & 0 deletions app/scripts/services/Authentication.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict';

define(['paw', 'restangular'], function(paw) {
paw.service('Authentication', ['Restangular', '$cookies',
function(Restangular, $cookies) {
var _this = this;
var tokenKey = 'ur';

var rest = Restangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.addResponseInterceptor(
function(data, operation, what, url, response, deferred) {
return response.headers()['x-auth-token'];
}
);
});

this.setToken = function(token) {
$cookies.put(tokenKey, token);
};

this.getToken = function(token) {
return $cookies.get(tokenKey);
};

this.getHeader = function() {
return {'x-auth-token': _this.getToken()};
};

this.login = function(user) {
return rest.all('login').customPOST(user);
};
}]);
});
26 changes: 26 additions & 0 deletions app/scripts/services/Courses.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict';

define(['paw', 'services/AuthenticatedRestangular'], function(paw) {
paw.service('Courses', ['AuthenticatedRestangular',
function(AuthenticatedRestangular) {
// this is needed as an array is expected from Restangular own methods
// not needed if we are going to use Restangular's custom* methods
var rest = AuthenticatedRestangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.addResponseInterceptor(
function(data, operation, what, url, response, deferred) {
return operation === 'getList' ? data.courses : data;
}
);
RestangularConfigurer.setRestangularFields({
id: 'courseId'
});
});

// add own methods as follows
rest.getList = function() {
return rest.all('courses').getList();
};

return rest;
}]);
});
30 changes: 30 additions & 0 deletions app/scripts/services/Students.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict';

define(['paw', 'services/AuthenticatedRestangular'], function(paw) {
paw.service('Students', ['AuthenticatedRestangular',
function(AuthenticatedRestangular) {
// this is needed as an array is expected from Restangular own methods
// not needed if we are going to use Restangular's custom* methods
var rest = AuthenticatedRestangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.addResponseInterceptor(
function(data, operation, what, url, response, deferred) {
return operation === 'getList' ? data.students : data;
}
);
RestangularConfigurer.setRestangularFields({
id: 'docket'
});
RestangularConfigurer.extendModel('students', function(student) {
student.fullName = student.firstName + ' ' + student.lastName;
return student;
});
});

// add own methods as follows
rest.getList = function() {
return rest.all('students').getList();
};

return rest;
}]);
});
Loading

0 comments on commit dca63d2

Please sign in to comment.