Skip to content

Commit

Permalink
CS-5680 - Authorization via API using Angular JS
Browse files Browse the repository at this point in the history
  • Loading branch information
takeit committed Feb 19, 2015
1 parent b7f22d8 commit e5af3ec
Show file tree
Hide file tree
Showing 10 changed files with 626 additions and 162 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ class PlaylistsController extends Controller
*/
public function indexAction()
{
return $this->render('NewscoopNewscoopBundle:Playlists:index.html.twig', array());
$preferencesService = $this->get('preferences');
$em = $this->get('em');
$clientName = 'newscoop_'.$preferencesService->SiteSecretKey;
$client = $em->getRepository('\Newscoop\GimmeBundle\Entity\Client')->findOneByName($clientName);

return $this->render('NewscoopNewscoopBundle:Playlists:index.html.twig', array(
'clientId' => $client ? $client->getPublicId() : '',
));
}
}
185 changes: 92 additions & 93 deletions newscoop/src/Newscoop/NewscoopBundle/Menu/Builder.php

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

#playlist-id {
font-size:12px;
display:none;
width:50px
}

Expand All @@ -27,20 +26,6 @@
font-size: 12px;
}

.selectBig {
width: 250px;
}

#drag-here-to-add-to-list {
border: 2px #CCCCCC dashed;
height: 45px;
width: 439px;
margin: 4px 9px;
padding: 20px 5px 0px 26px;
color: #CCCCCC;
font-size: 20px;
}

.content .actions {
margin: 0 auto;
width: 500px;
Expand All @@ -52,7 +37,6 @@

#playlist-name-label, #playlist-id-label {
float: left;
display: none;
padding: 0 10px;
line-height: 25px;
}
Expand All @@ -61,17 +45,10 @@
height: 565px;
width: 46.9%;
}
#context-box {
padding: 10px;
height: 680px;
width: 1100px;
background: #FFF;
margin: 0 auto;
}
#drag-here-to-add-to-list {
border: 2px #CCCCCC dashed;
height: 70px;
width: 463px;
width: 96%;
margin: 4px 9px;
padding: 20px 5px 0px 26px;
color: #CCCCCC;
Expand All @@ -89,10 +66,13 @@
.context-item-summary {
color: #007fb3;
}
.context-search .context-item {
border-bottom: none;
}
ul#context_list {
display:block;
height: 433px;
width: 495px;
width: 100%;
overflow-y:auto;
overflow-x:hidden;
padding: 36px 0px 0px 0px;
Expand All @@ -102,18 +82,31 @@ ul#context_list {
-webkit-transition: opacity .2s linear;
transition: opacity .2s linear;
}
.second-menu {
height: 40px;
float: right;
margin-right: 20px;
}

.first-menu {
margin: 0 auto;
width: 1100px;
padding-left: 30px;
}

.context-content {
float: left;
.system_pref h3 {
border: none;
}
.loading-container {
position: relative;
}
.loading-container .loading:before, .loading-container .loading:after {
content: " ";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
min-height: 50px;
min-width: 50px;
}
.loading-container .loading:before {
background-color: white;
opacity: 0.5;
}
.context-item-header {
margin-bottom: 5px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
(function() {
'use strict';
var app = angular.module('playlistsApp', ['ngSanitize', 'ui.select', 'ngTable', 'ng-sortable'])
.config(function($interpolateProvider, $httpProvider) {
$interpolateProvider.startSymbol('{[{').endSymbol('}]}');
$httpProvider.interceptors.push('authInterceptor');
});

app.directive('loadingContainer', function () {
return {
restrict: 'A',
scope: false,
link: function(scope, element, attrs) {
var loadingLayer = angular.element('<div class="loading"></div>');
element.append(loadingLayer);
element.addClass('loading-container');
scope.$watch(attrs.loadingContainer, function(value) {
loadingLayer.toggleClass('ng-hide', !value);
});
}
};
});

/**
* AngularJS default filter with the following expression:
* "playlist in playlists | filter: {name: $select.search, age: $select.search}"
* performs a AND between 'name: $select.search' and 'age: $select.search'.
* We want to perform a OR.
*/
app.filter('listsFilter', function() {
return function(items, props) {
var out = [];

if (angular.isArray(items)) {
items.forEach(function(item) {
var itemMatches = false;

var keys = Object.keys(props);
for (var i = 0; i < keys.length; i++) {
var prop = keys[i];
var text = props[prop].toLowerCase();
if (item[prop].toString().toLowerCase().indexOf(text) !== -1) {
itemMatches = true;
break;
}
}

if (itemMatches) {
out.push(item);
}
});
} else {
// Let the output be the input untouched
out = items;
}

return out;
}
});

/**
* @ngdoc function
* @name playlistsApp.controller:PlaylistCtrl
* @description
* # PlaylistCtrl
* Controller of the playlistsApp
*/
app.controller('PlaylistCtrl', function ($scope, Playlist, ngTableParams, $timeout, $http) {
$scope.playlist = {};
$scope.playlists = []; //Playlist.getAll();

$scope.sortableConfig = {
group: 'articles',
animation: 150
};

$scope.tableParams = new ngTableParams({
page: 1, // show first page
count: 5, // count per page
itemsPerPage: 5
}, {
total: 0,// length of data
counts: [], // disable page sizes
getData: function($defer, params) {
var filters = params.filter();
params.$params.query = filters.query;
Playlist.getAllArticles($defer, params);
}
});
}).controller('FeaturedController', ['$scope', function ($scope) {
// TODO - this array should be filled with data taken from the Playlists API
$scope.articles = [
{title: 'learn angular', status: "Y", type: "news", created: "2014-02-19T15:48:13+0100"},
{title: 'build an angular app', status: "Y", type: "news", created: "2010-12-23T15:48:13+0100"}
];
$scope.sortableConfig = { group: 'articles', animation: 150 };

}]);
})();
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

/**
* AngularJS filter for converting datetime to a nice format
*/
angular.module('playlistsApp').filter('niceDate', [
'currentTime',
'$filter',
function (currentTime, $filter) {
return function (input) {
var date;
if (typeof input === 'string') {
date = new Date(Date.parse(input));
} else {
date = input;
}
if (currentTime.isToday(date)) {
return 'today @ ' + $filter('date')(date, 'H:mm');
} else {
return $filter('date')(date, 'dd.MM.yyyy @ H:mm');
}
};
}
]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
'use strict';

/**
* AngularJS Service for intercepting API requests and adding authorization
* info to them.
*
* @class authInterceptor
*/
angular.module('playlistsApp').factory('authInterceptor', [
'$injector',
'$q',
'$window',
function ($injector, $q, $window) {
// NOTE: userAuth service is not injected directly, because it depends
// on the $http service and the latter's provider uses this
// authInterceptor service --> circular dependency.
// We thus inject need to inject userAuth service on the fly (when it
// is actually needed).

return {
request: function (config) {
var endpoint,
token,
userAuth = $injector.get('userAuth');

config.headers = config.headers || {};
token = userAuth.token();
if (token) {
config.headers.Authorization = 'Bearer ' + token;
}

return config;
},

// If we receive an error response because authentication token
// is invalid/expired, we handle it by displaying a login modal.
//
// If login succeeds and a new token is obtained, the failed http
// request is transparently repeated with a correct token. If even
// this retried request (recognized by a special marker flag in
// request's http config) fails, the error is not further handled
// and is passed to through to the other parts of the application.
//
// Other types of http errors are not handled here and are simply
// passed through.
responseError: function (response) {
var configToRepeat,
failedRequestConfig,
retryDeferred,
userAuth,
$http;

userAuth = $injector.get('userAuth');

if (response.config.IS_RETRY) {
// Tried to retry the initial failed request but failed
// again --> forward the error without another retry (to
// avoid a possible infinite loop).
return $q.reject(response);
}

// NOTE: The API is not perfect yet and does not always return
// 401 on authentication errors, thus we must also rely on the
// error message (for now at least).
if (response.status === 401 ||
response.statusText === 'OAuth2 authentication required'
) {
// Request failed due to invalid oAuth token - try to
// obtain a new token and then repeat the failed request.
failedRequestConfig = response.config;
retryDeferred = $q.defer();

userAuth.newToken()
.then(function () {
// new token successfully obtained, repeat the request
$http = $injector.get('$http');

configToRepeat = angular.copy(failedRequestConfig);
configToRepeat.IS_RETRY = true;

$http(configToRepeat)
.then(function (newResponse) {
delete newResponse.config.IS_RETRY;
retryDeferred.resolve(newResponse);
})
.catch(function () {
retryDeferred.reject(response);
});
})
.catch(function () {
// obtaining new token failed, reject the request
retryDeferred.reject(response);
});

return retryDeferred.promise;
} else {
// some non-authentication error occured, these kind of
// errors are not handled by this interceptor --> simply
// forward the error
return $q.reject(response);
}
}
};
}
]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict';

angular.module('playlistsApp').service('currentTime', function () {
var preset = false;
this.set = function (newPreset) {
preset = newPreset;
};
this.unset = function () {
preset = false;
};
this.get = function () {
if (preset === false) {
return new Date();
} else {
return preset;
}
};
this.isToday = function (date) {
var now = this.get();
return date.toDateString() === now.toDateString();
};
});
Loading

0 comments on commit e5af3ec

Please sign in to comment.