diff --git a/README.md b/README.md index 0575946..0f7ae2e 100644 --- a/README.md +++ b/README.md @@ -44,4 +44,4 @@ Now, issuing a `git remove -v` you should see: ## VoilĂ ! -You should now be able to run `grunt server` +You should now be able to run `grunt server` or `node --debug server.js` if you want node.js with express. diff --git a/app/index.html b/app/index.html index 76f44b8..98fffcb 100644 --- a/app/index.html +++ b/app/index.html @@ -32,6 +32,22 @@ + + +
@@ -57,14 +73,25 @@ - + + + + + + + + + + + + diff --git a/app/scripts/app.js b/app/scripts/app.js index a7affc8..be6924a 100644 --- a/app/scripts/app.js +++ b/app/scripts/app.js @@ -1,11 +1,45 @@ 'use strict'; -angular.module('sbjsApp', []) - .config(function ($routeProvider) { +angular.module('sbjsApp', ['ngCookies','$strap.directives','firebase']) + .config(function ($routeProvider, $httpProvider) { $routeProvider - .when('/', {templateUrl: 'views/main.html',controller: 'MainCtrl'}) - .when('/auth', {templateUrl: 'views/auth.html',controller: 'AuthCtrl'}) - .otherwise({ - redirectTo: '/' - }); - }); + .when('/', {templateUrl: 'views/main.html', controller: 'MainCtrl' }) + .when('/auth', {templateUrl: 'views/auth.html', controller: 'AuthCtrl' }) + .when('/profile', {templateUrl: 'views/profile.html', controller: 'ProfileCtrl' }) + .when('/members', {templateUrl: 'views/members.html', controller: 'MembersCtrl' }) + .when('/registration', {templateUrl: 'views/registration.html', controller: 'RegistrationCtrl' }) + .when('/fire', { templateUrl: 'views/fire.html', controller: 'FireCtrl' }) + .otherwise({redirectTo: '/'}); + + //Interceptor is used in case the token becomes invalid while the session is active. + var interceptor = ['$location', '$q', function($location, $q) { + function success(response) { + return response; + } + + function error(response) { + if(response.status === 401) { + $location.path('/'); + return $q.reject(response); + } + else { + return $q.reject(response); + } + } + + return function(promise) { + return promise.then(success, error); + }; + }]; + + $httpProvider.responseInterceptors.push(interceptor); + }) + .run(['$rootScope', '$location', 'auth', function ($rootScope, $location, auth) { + $rootScope.$on('$routeChangeStart', function (event, next, current) { + //this is called every time a route changed + if (!auth.getToken()) { + //redirect to login if we can't get a valid token + $location.path('/'); + } + }); + }]); diff --git a/app/scripts/controllers/fire.js b/app/scripts/controllers/fire.js new file mode 100644 index 0000000..0d3a882 --- /dev/null +++ b/app/scripts/controllers/fire.js @@ -0,0 +1,25 @@ +'use strict'; + +angular.module('sbjsApp') + .controller('FireCtrl', function ($scope, angularFire) { + + $scope.notes = []; + var url = 'https://sbjs.firebaseio.com/notes'; + var promise = angularFire(url, $scope, 'notes', []) + .then( function(todos){ + startWatch($scope); + }); + } +); + + +function startWatch($scope) { + $scope.addNote = function(){ + var note = { + 'githubUserId': 'HardCoded', + 'message': $scope.newMessage + }; + $scope.notes.push(note); + $scope.newMessage = ''; + }; +} diff --git a/app/scripts/controllers/main.js b/app/scripts/controllers/main.js index 3376807..300a3ad 100644 --- a/app/scripts/controllers/main.js +++ b/app/scripts/controllers/main.js @@ -1,32 +1,15 @@ 'use strict'; angular.module('sbjsApp') - .controller('MainCtrl', function ($scope, utils) { - console.log("MainCtrl.location: ", location); - console.log("MainCtrl.location.search: ", location.search); - var queryParams = location.search - var params = utils.getParamsFromString(location.search); - console.log("MainCtrl.params.code: ", params); - console.log("MainCtrl.params.code: ", params.code); - - if(params.code){ - - } - // http://localhost/?code=537fb3f6697752be4951 - $scope.awesomeThings = [ - 'HTML5 Boilerplate', - 'AngularJS', - 'Karma' - ]; - }) - .controller('AuthCtrl', function ($scope) { - console.log("AuthCtrl.location: ", location); - console.log("AuthCtrl.location.search: ", location.search); - - // http://localhost/?code=537fb3f6697752be4951 - $scope.awesomeThings = [ - 'HTML5 Boilerplate', - 'AngularJS', - 'Karma' - ]; - }); +.controller('MainCtrl', function ($scope, utils) { + var options = ['client_id=f9aa961f63df8c7b766a','scope=user,user:email,public_repo']; + $scope.githubLoginUrl = 'https://github.com/login/oauth/authorize?'+options.join('&'); + + //debug + console.log('MainCtrl.location: ', location); + console.log('MainCtrl.location.search: ', location.search); + // var queryParams = location.search; + var params = utils.getParamsFromString(location.search); + console.log('MainCtrl.params.code: ', params); + console.log('MainCtrl.params.code: ', params.code); +}); diff --git a/app/scripts/controllers/members.js b/app/scripts/controllers/members.js new file mode 100644 index 0000000..b2ea374 --- /dev/null +++ b/app/scripts/controllers/members.js @@ -0,0 +1,7 @@ +'use strict'; + +angular.module('sbjsApp') + .controller('MembersCtrl', function($scope, auth){ + $scope.members = auth.fetchMembers(); + } +); diff --git a/app/scripts/controllers/profile.js b/app/scripts/controllers/profile.js new file mode 100644 index 0000000..869567d --- /dev/null +++ b/app/scripts/controllers/profile.js @@ -0,0 +1,7 @@ +'use strict'; + +angular.module('sbjsApp') + .controller('ProfileCtrl', function($scope, auth){ + $scope.profile = auth.fetchUserProfile(); + } +); diff --git a/app/scripts/controllers/registration.js b/app/scripts/controllers/registration.js new file mode 100644 index 0000000..ed13e54 --- /dev/null +++ b/app/scripts/controllers/registration.js @@ -0,0 +1,12 @@ +'use strict'; + +angular.module('sbjsApp') +.controller('RegistrationCtrl', function ($scope, $http, $cookies) { + $scope.forkMembersRepo = function(){ + // code to POST fork sbjs members repo + var url = 'https://api.github.com/repos/sbjs/sbjs.profiles/forks?access_token='+$cookies.token; + $http.post(url, {}).then(function(res){ + console.log('success!'); + }); + }; +}); diff --git a/app/scripts/lib/utils.js b/app/scripts/lib/utils.js index b9b4677..d29c56b 100644 --- a/app/scripts/lib/utils.js +++ b/app/scripts/lib/utils.js @@ -1,23 +1,25 @@ 'use strict'; angular.module('sbjsApp') - .service('utils', function(){ - this.getParamsFromString = function (paramString) { - // From: http://stackoverflow.com/a/2880929/39758 - if(paramString.charAt(0) == '?'){ - paramString = paramString.substring(1); - } - var match, - pl = /\+/g, // Regex for replacing addition symbol with a space - search = /([^&=]+)=?([^&]*)/g, - decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); }, - query = paramString,//.substring(1), - urlParams = {}; +.service('utils', function($cookies, $location){ - while (match = search.exec(query)) - urlParams[decode(match[1])] = decode(match[2]); - return urlParams; - }; - }); + this.getParamsFromString = function (paramString) { + // From: http://stackoverflow.com/a/2880929/39758 + if(paramString.charAt(0) === '?'){ + paramString = paramString.substring(1); + } + var match, + pl = /\+/g, // Regex for replacing addition symbol with a space + search = /([^&=]+)=?([^&]*)/g, + decode = function (s) { return decodeURIComponent(s.replace(pl, ' ')); }, + query = paramString,//.substring(1), + urlParams = {}; + + while ((match = search.exec(query))){ + urlParams[decode(match[1])] = decode(match[2]); + } + return urlParams; + }; +}); diff --git a/app/scripts/services/auth.js b/app/scripts/services/auth.js new file mode 100644 index 0000000..aa82d9b --- /dev/null +++ b/app/scripts/services/auth.js @@ -0,0 +1,40 @@ +'use strict'; + +angular.module('sbjsApp') +.service('auth', function auth($cookies, $http, $location) { + this.GITHUB_BASE_URL = "https://api.github.com"; + + this.getToken = function(){ + var access_token = $location.search().access_token; + if (access_token && access_token !== "undefined"){ + $cookies.token = $location.search().access_token; + $location.search('access_token', null); + } + // if we dont have a token, redirect to home to authenticate + if(!$cookies.token) { + $location.path( '/' ); + } + return $cookies.token; + }; + + this.parseUser = function(result){ + var user = result.data; + user.avatar_url += "&s=420"; + return user; + }; + + this.fetchUserProfile = function(){ + var url = this.GITHUB_BASE_URL + '/user?access_token=' + this.getToken(); + return $http.get(url).then(this.parseUser); + }; + + this.parseMembers = function(result){ + return _.pluck(result.data, 'owner'); + }; + + this.fetchMembers = function(){ + var url = this.GITHUB_BASE_URL + '/repos/sbjs/sbjs.profiles/forks?access_token=' + this.getToken(); + return $http.get(url).then(this.parseMembers); + } + +}); diff --git a/app/styles/main.css b/app/styles/main.css index c754fdd..673e99f 100644 --- a/app/styles/main.css +++ b/app/styles/main.css @@ -19,4 +19,27 @@ body { font-size: 60px; line-height: 1; letter-spacing: -1px; + margin: 10px auto; +} + +.profile { width: 430px; } + +.profile table tr th{ + text-align: left; + width: 150px; +} + +.members { width: 400px; } + +.members ul { margin: 0; } +.members li { list-style: none; } +.members a { text-decoration: none; } +.members li .thumbnail { + width: 80px; + display: inline-block; +} + +.members h2 { + display: inline-block; + margin: 10px 0 10px 10px; } diff --git a/app/views/fire.html b/app/views/fire.html new file mode 100644 index 0000000..566cd24 --- /dev/null +++ b/app/views/fire.html @@ -0,0 +1,8 @@ +
+
+

{{note.githubUserId}}

+
{{note.message}}
+
+ + +
diff --git a/app/views/main.html b/app/views/main.html index 6268c86..c8b6c67 100644 --- a/app/views/main.html +++ b/app/views/main.html @@ -1,5 +1,4 @@
-

Welcome to SBJS

-

Login to Github

-

Pretend Token

+

Welcome to SBJS

+ Login to Github
diff --git a/app/views/members.html b/app/views/members.html new file mode 100644 index 0000000..82b3b0a --- /dev/null +++ b/app/views/members.html @@ -0,0 +1,11 @@ +
+

Members

+ +
diff --git a/app/views/profile.html b/app/views/profile.html new file mode 100644 index 0000000..41c1fc0 --- /dev/null +++ b/app/views/profile.html @@ -0,0 +1,20 @@ +
+ +

{{profile.login}}

+
+ + + + + + + + + + + + + +
Name:{{profile.name}}
Location:{{profile.location}}
Public Repos:{{profile.public_repos}}
+
+
diff --git a/app/views/registration.html b/app/views/registration.html new file mode 100644 index 0000000..baab7d5 --- /dev/null +++ b/app/views/registration.html @@ -0,0 +1,7 @@ +

Become a Member

+ +

It's simple. Fork the sbjs.profiles repo. This will add you to our Members page.

+ +
+ +
diff --git a/bower.json b/bower.json index 829c3c2..cde0172 100644 --- a/bower.json +++ b/bower.json @@ -9,7 +9,11 @@ "es5-shim": "~2.0.8", "angular-resource": "~1.0.7", "angular-cookies": "~1.0.7", - "angular-sanitize": "~1.0.7" + "angular-sanitize": "~1.0.7", + "angular-bootstrap": "*", + "angular-strap": "*", + "underscore": "*", + "angular-fire":"*" }, "devDependencies": { "angular-mocks": "~1.0.7", diff --git a/github.conf.json b/github.conf.json new file mode 100644 index 0000000..5d4a98d --- /dev/null +++ b/github.conf.json @@ -0,0 +1,5 @@ +{ + "client_id": "f9aa961f63df8c7b766a", + "redirect_uri":"", + "client_secret": "CUT_AND_PASTE YOUR CLIENT SECRET HERE. ACTUALLY USE THE BUILD SCRIPTS DONT CHECK THIS IN!" +} diff --git a/karma.conf.js b/karma.conf.js index 2d6194b..758499a 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -9,6 +9,10 @@ files = [ JASMINE_ADAPTER, 'app/bower_components/angular/angular.js', 'app/bower_components/angular-mocks/angular-mocks.js', + 'app/bower_components/angular-cookies/angular-cookies.js', + 'app/bower_components/angular-strap/dist/angular-strap.js', + 'app/bower_components/underscore/underscore.js', + 'app/bower_components/angular-fire/angularFire.js', 'app/scripts/*.js', 'app/scripts/**/*.js', 'test/mock/**/*.js', diff --git a/package.json b/package.json index d4ad104..12360f0 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,9 @@ "dependencies": { "express": "3.3.4", "ejs": "~0.8.4", - "jade": "*" + "jade": "*", + "restler": "*", + "firebase" : "0.6.1" }, "scripts": { "test": "grunt test", diff --git a/server.js b/server.js index 40a630f..6ec4547 100644 --- a/server.js +++ b/server.js @@ -1,14 +1,16 @@ +'use strict'; /** * Module dependencies. */ -var express = require('express') - , routes = require('./routes') - , user = require('./routes/user') - , http = require('http') - , path = require('path') - , rest = require('restler'); +var express = require('express'), + routes = require('./routes'), + user = require('./routes/user'), + http = require('http'), + path = require('path'), + rest = require('restler'), + githubCfg = require('./github.conf.json'); var app = express(); @@ -24,23 +26,24 @@ app.use(express.favicon()); app.use(express.logger('dev')); app.use(express.bodyParser()); app.use(express.methodOverride()); +app.use(express.cookieParser()); +app.use(express.session({secret: 'sbjsSecretHash'})); app.use(app.router); // development only -if ('development' == app.get('env')) { - console.log("\n**** RUNNING IN DEVELOPMENT ENV ****\n") - app.use(express.errorHandler()); - app.set('views', __dirname + '/app'); - app.use(express.static(path.join(__dirname, 'app'))); -} -else { - console.log("ENV: ", app.get('env')) - console.log("\n**** RUNNING IN PRODUCTION ENV ****\n") - console.log("\n**** WHICH MEANS OUT OF DIST DIRECTORY! ****\n") - app.use(express.errorHandler()); - app.set('views', __dirname + '/dist'); - app.use(express.static(path.join(__dirname, 'dist'))); +if ('development' === app.get('env')) { + console.log('\n**** RUNNING IN DEVELOPMENT ENV ****\n'); + app.use(express.errorHandler()); + app.set('views', __dirname + '/app'); + app.use(express.static(path.join(__dirname, 'app'))); +}else{ + console.log('ENV: ', app.get('env')); + console.log('\n**** RUNNING IN PRODUCTION ENV ****\n'); + console.log('\n**** WHICH MEANS OUT OF DIST DIRECTORY! ****\n'); + app.use(express.errorHandler()); + app.set('views', __dirname + '/dist'); + app.use(express.static(path.join(__dirname, 'dist'))); } // If you want to use node and express, here are some examples @@ -53,32 +56,33 @@ else { // we could configure this to run in dev vs production. But make sure the default is for // production, as that is what azure will use to run the app app.get('/', function(request, response) { - var code= request.query.code; - - if(code){ - console.log("CODE: ", code); - - var data = { - client_id: 'f9aa961f63df8c7b766a', - //redirect_uri: '', - client_secret: 'CUT_AND_PASTE YOUR CLIENT SECRET HERE. ACTUALLY USE THE BUILD SCRIPTS DONT CHECK THIS IN!', - code: code - }; - rest.post('https://github.com/login/oauth/access_token', data) - .on('complete', function(data, response){ - console.log("DONE:", data) - if (response.statusCode == 200){ - console.log(data); // prints HTML - console.log("access_token", data.access_token); // prints HTML - } - }); - } - - response.render('index.html') + var code = request.query.code; + //TODO: refactor? + if(code){ + var options = { + 'data':{ + 'client_id': githubCfg.client_id, + 'client_secret': githubCfg.client_secret, + 'code': code + }, + 'headers':{ + 'Accept': 'application/json' + } + }; + + rest.post('https://github.com/login/oauth/access_token', options) + .on('complete', function(data, resp){ + if (resp.statusCode == 200){ + request.session.access_token = data.access_token; + response.redirect('/#/profile?access_token='+data.access_token); + } + }); + }else{ + response.render('index.html'); + } }); - var port = process.env.PORT || 9000; // var port = app.get('port') diff --git a/test/spec/controllers/fire.js b/test/spec/controllers/fire.js new file mode 100644 index 0000000..6fbcae1 --- /dev/null +++ b/test/spec/controllers/fire.js @@ -0,0 +1,19 @@ +'use strict'; + +describe('Controller: FireCtrl', function () { + + // load the controller's module + beforeEach(module('sbjsApp')); + + var FireCtrl, + scope; + + // Initialize the controller and a mock scope + beforeEach(inject(function ($controller, $rootScope) { + scope = $rootScope.$new(); + FireCtrl = $controller('FireCtrl', { + $scope: scope + }); + })); + +}); diff --git a/test/spec/controllers/main.js b/test/spec/controllers/main.js index 6129950..b39c073 100644 --- a/test/spec/controllers/main.js +++ b/test/spec/controllers/main.js @@ -16,7 +16,8 @@ describe('Controller: MainCtrl', function () { }); })); - it('should attach a list of awesomeThings to the scope', function () { - expect(scope.awesomeThings.length).toBe(3); + it('defines the githubLoginUrl', function() { + expect(scope.githubLoginUrl).toEqual('https://github.com/login/oauth/authorize?client_id=f9aa961f63df8c7b766a&scope=user,user:email,public_repo'); }); + }); diff --git a/test/spec/controllers/members.js b/test/spec/controllers/members.js new file mode 100644 index 0000000..b11947e --- /dev/null +++ b/test/spec/controllers/members.js @@ -0,0 +1,54 @@ +'use strict'; + +describe('Controller: MembersCtrl', function () { + + // load the controller's module + beforeEach(module('sbjsApp')); + + var MembersCtrl, getSpy, + scope, http, cookies; + + // Initialize the controller and a mock scope + beforeEach(inject(function ($controller, $rootScope, $http, $cookies) { + scope = $rootScope.$new(); + + MembersCtrl = $controller('MembersCtrl', { + $scope: scope + }); + + })); + + it("has members defined", function() { + expect(scope.members).toBeDefined(); + }); + + describe("#parseMembers", function() { + it("parses owner objects out of result data", function() { + expect(scope.members).not.toBeDefined(); + var result = { + data : [ + { owner : "Jim" }, + { owner : "Remy" } + ] + }; + scope.parseMembers(result); + expect(scope.members).toEqual(["Jim", "Remy"]); + }); + }); + + describe("#fetchMembers", function() { + it("fetches members from github URL", function() { + cookies.token = "1j2j3j475hf7"; + var thenSpy = jasmine.createSpy("then"); + getSpy.andReturn({ + then: thenSpy + }); + var url = 'https://api.github.com/repos/sbjs/sbjs.profiles/forks?access_token=' + cookies.token; + + scope.fetchMembers(); + + expect(http.get).toHaveBeenCalledWith(url); + expect(thenSpy).toHaveBeenCalledWith(scope.parseMembers); + }); + }); +}); diff --git a/test/spec/controllers/profile.js b/test/spec/controllers/profile.js new file mode 100644 index 0000000..3aeb7e1 --- /dev/null +++ b/test/spec/controllers/profile.js @@ -0,0 +1,26 @@ +'use strict'; + +describe('Controller: ProfileCtrl', function () { + + beforeEach(module('sbjsApp')); + + var ProfileCtrl, getSpy, + scope, http, cookies; + + // Initialize the controller and a mock scope + beforeEach(inject(function ($controller, $rootScope, $http, $cookies) { + scope = $rootScope.$new(); + http = $http; + cookies = $cookies; + + getSpy = spyOn(http, "get"); + getSpy.andReturn({ then : jasmine.createSpy("then") }); + + ProfileCtrl = $controller('ProfileCtrl', { + $scope: scope, + $http : http, + $cookies : cookies + }); + })); + +}); diff --git a/test/spec/controllers/registration.js b/test/spec/controllers/registration.js new file mode 100644 index 0000000..cb4197f --- /dev/null +++ b/test/spec/controllers/registration.js @@ -0,0 +1,37 @@ +'use strict'; + +describe('Controller: RegistrationCtrl', function () { + + // load the controller's module + beforeEach(module('sbjsApp')); + + var RegistrationCtrl, + scope, cookies, http; + + // Initialize the controller and a mock scope + beforeEach(inject(function ($controller, $rootScope, $cookies, $http) { + scope = $rootScope.$new(); + http = $http; + cookies = $cookies; + RegistrationCtrl = $controller('RegistrationCtrl', { + $scope: scope, + $http: http, + $cookies: cookies + }); + })); + + it("posts to github fork repo link", function() { + cookies.token = "123456654321"; + var thenSpy = jasmine.createSpy("then"); + spyOn(http, "post").andReturn({ + then : thenSpy + }); + var url = 'https://api.github.com/repos/sbjs/sbjs.profiles/forks?access_token='+cookies.token; + + scope.forkMembersRepo(); + + expect(http.post).toHaveBeenCalledWith(url, {}); + expect(thenSpy).toHaveBeenCalled(); + }); + +}); diff --git a/test/spec/lib/utils.js b/test/spec/lib/utils.js new file mode 100644 index 0000000..2c8c53f --- /dev/null +++ b/test/spec/lib/utils.js @@ -0,0 +1,4 @@ +describe("#utils", function() { + +}); + diff --git a/test/spec/services/auth.js b/test/spec/services/auth.js new file mode 100644 index 0000000..1da692e --- /dev/null +++ b/test/spec/services/auth.js @@ -0,0 +1,52 @@ +'use strict'; + +describe('Service: auth', function () { + + // load the service's module + beforeEach(module('sbjsApp')); + + // instantiate service + var auth; + beforeEach(inject(function (_auth_) { + auth = _auth_; + })); + + it('should do something', function () { + expect(!!auth).toBe(true); + }); + + describe("#parseUser", function() { + it("parses the user data from results", function() { + expect(scope.user).not.toBeDefined(); + + var result = { + data : { + "name" : "myName", + "avatar_url" : "http://www.avatar_url.com/?arg1=456" + } + }; + + scope.parseUser(result); + + result.data.avatar_url += "&s=420"; + expect(scope.user).toEqual(result.data); + }); + }); + + describe("#fetchUserProfile", function() { + it("fetches user profile data from git URL", function() { + cookies.token = "f7djv6d5susnrn4nw"; + var thenSpy = jasmine.createSpy("then"); + getSpy.andReturn({ + "then" : thenSpy + }); + var url = 'https://api.github.com/user?access_token=' + cookies.token; + + scope.fetchUserProfile(); + + expect(http.get).toHaveBeenCalledWith(url); + expect(thenSpy).toHaveBeenCalledWith(scope.parseUser); + }); + }); + +});