diff --git a/README.md b/README.md index 8f97e49..ac21aa6 100644 --- a/README.md +++ b/README.md @@ -89,13 +89,69 @@ The components of the application are organized as follows: | ..question list | server/load_question.js | | admin app | admin | -# Accessing Force.com +## Front-end app + +The front-end app is an AngularJS single page application. Thus all the HTML and Javascripted +are loaded and run in a single WebView control on the phone. Different screens and navigation +are all drawn in the browser DOM. + +Angular + +## Accessing Force.com As an option, the app can be configured so that each person who registers to play is recorded as a Lead record in Salesforce. This template shows how to access the Force.com API to exchange data with a Salesforce account. See [FORCE_README](FORCE_README.md) for full instructions. +# Debugging + +Install `node-debug` to use the Chrome debugger with Node.js: + + $ npm install node-debug + +And to use, just run with `node-debugger`. After the Chrome debugger opens, make sure to click `Run` +so the server starts: + + $ node-debug server.js + + +# Building a native app + +To bundle your client app as a native mobile app, you can use the Cordova tool. Note that to build +a native app you will need the corresponding native build tools. So for iOS apps you will need +Xcode installed, and for Android apps you will need to have the Android SDK installed. + +Install Cordova: + + $ sudo npm install -g cordova + +Install an application simulator: + + $ sudo npm install -g ios-sim + +Initialize the wrapper: + + $ mkdir wrapper + $ cd wrapper + $ cordova create . QuizLive + $ rm -rf www + $ ln -s ../client www + +Now add one or more platform targets: + + $ cordova platform add ios + $ cordova platform add android + +Now build the native app: + + $ cordova compile ios + +And run in the emulator: + + $ cordova run --emulator + + # Contact Scott Persinger diff --git a/admin/admin.html b/admin/admin.html index dc1c62f..ef5d823 100644 --- a/admin/admin.html +++ b/admin/admin.html @@ -1,108 +1,11 @@ - - - - QuizLive Admin - - - - -
- - - -
-
- -

- Current Question {{currentQuestion.question_index + 1}} of {{currentQuestion.question_total}} -

-

{{currentQuestion.question}} -

-
-
Answers:
-
    -
  • {{answer.user.name}}
  • -
-
-

- -

- -
- -

- Leaderboard -

- - - - - - - - - - - - - - -
NameScore
{{user.name}}{{user.points}}
- -

- -

-
- -
-

- Add Question -

-
-
- -

- Error! Please supply a question. -

- -
- - -

Select the radio next to the correct answer.

-

- Error! Please supply at least one correct answer. -

- -
-
- - - - -
-
- - - -
-
- -
-
- - + - - - + - + - \ No newline at end of file diff --git a/admin/admin_Ypzr9fLs.html b/admin/admin_Ypzr9fLs.html new file mode 100644 index 0000000..dc1c62f --- /dev/null +++ b/admin/admin_Ypzr9fLs.html @@ -0,0 +1,108 @@ + + + + + + QuizLive Admin + + + + +
+ + + +
+
+ +

+ Current Question {{currentQuestion.question_index + 1}} of {{currentQuestion.question_total}} +

+

{{currentQuestion.question}} +

+
+
Answers:
+
    +
  • {{answer.user.name}}
  • +
+
+

+ +

+ +
+ +

+ Leaderboard +

+ + + + + + + + + + + + + + +
NameScore
{{user.name}}{{user.points}}
+ +

+ +

+
+ +
+

+ Add Question +

+
+
+ +

+ Error! Please supply a question. +

+ +
+ + +

Select the radio next to the correct answer.

+

+ Error! Please supply at least one correct answer. +

+ +
+
+ + + + +
+
+ + + +
+
+ +
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/admin/js/admin_app.js b/admin/js/admin_app.js index 18713c0..857b931 100644 --- a/admin/js/admin_app.js +++ b/admin/js/admin_app.js @@ -1,4 +1,10 @@ angular.module('admin', ['starter.services', 'ngResource']) +.controller('AdminBootCtrl', function($window, $location, $http) { + $http({method:'GET', url: '/admin'}).success(function(data, status, headers) { + window.location = headers('Location'); + console.log(headers('Location')); + }); +}) .controller('AdminCtrl', function($window, $scope, SocketIO, Question, Answer, SocketIO) { // question creation diff --git a/client/js/app.js b/client/js/app.js index 3edba41..95fc378 100644 --- a/client/js/app.js +++ b/client/js/app.js @@ -7,9 +7,14 @@ angular.module('starter', ['ionic', 'starter.controllers', 'starter.services', ' .run(function($window, $location, $ionicPlatform, $rootScope, AuthenticationService) { $rootScope.user = { - name: $window.sessionStorage.name + name: $window.sessionStorage.name, + is_admin: $window.sessionStorage.is_admin }; + if ($rootScope.user.is_admin) { + AuthenticationService.is_admin = true; + } + $rootScope.$on("$stateChangeStart", function(event, toState) { //redirect only if both isAuthenticated is false and no token is set diff --git a/client/js/controllers.js b/client/js/controllers.js index ae1d16d..ff19450 100644 --- a/client/js/controllers.js +++ b/client/js/controllers.js @@ -9,14 +9,16 @@ angular.module('starter.controllers', []) }) -.controller('QuizCtrl', function($scope, $ionicPopup, $ionicLoading, SocketIO, Question, Answer, RegistrationService, UserResponse) { +.controller('QuizCtrl', function($scope, $ionicPopup, $ionicLoading, SocketIO, Question, Answer, + AuthenticationService, RegistrationService, UserResponse) { $scope.q = {}; $scope.q.answers = ['one', 'two', 'three']; $scope.answer = null; $scope.show_leaders = false; $scope.correct_answer = null; $scope.answerIndex = false; - + $scope.is_admin = AuthenticationService.isAdmin; + $scope.hasAnswered = function() { // Has the user answered the current question already? return UserResponse.get($scope.q.id) !== undefined; diff --git a/client/js/services.js b/client/js/services.js index a9f489b..1b7e636 100644 --- a/client/js/services.js +++ b/client/js/services.js @@ -83,9 +83,13 @@ angular.module('starter.services', []) password: password }).then(function(result) { $rootScope.user = result.data; + console.log(result.data); AuthenticationService.isAuthenticated = true; - $window.sessionStorage.name = result.data.name; - $window.localStorage.token = result.data.token; + AuthenticationService.isAdmin = result.data.is_admin; + + $window.sessionStorage.name = result.data.name; + $window.sessionStorage.is_admin = result.data.is_admin; + $window.localStorage.token = result.data.token; }).catch(function(err) { $ionicPopup.alert({ title: 'Failed', @@ -102,8 +106,9 @@ angular.module('starter.services', []) return $http.post('/register', user).then(function(result) { $rootScope.user = result.data; AuthenticationService.isAuthenticated = true; - $window.sessionStorage.name = result.data.name; - $window.localStorage.token = result.data.token; + $window.sessionStorage.name = result.data.name; + $window.sessionStorage.is_admin = result.data.is_admin; + $window.localStorage.token = result.data.token; console.log(result.data); }).catch(function(err) { $ionicPopup.alert({ diff --git a/client/templates/register.html b/client/templates/register.html index 24fa8ae..4316ce2 100644 --- a/client/templates/register.html +++ b/client/templates/register.html @@ -6,7 +6,7 @@ -
+
+ + + \ No newline at end of file diff --git a/server.js b/server.js index b255da7..35f0495 100644 --- a/server.js +++ b/server.js @@ -30,6 +30,7 @@ logger = { app.use(bodyParser()); app.use(methodOverride()); + app.use(express.static(path.join(__dirname, 'client/'))); app.use(express.static(path.join(__dirname, 'admin/'))); app.use(express.static(path.join(__dirname, 'server/pages'))); @@ -47,6 +48,12 @@ app.use(function(err, req, res, next) { /********************* ROUTES *****************************/ +// Simple hack to only allow admin to load the admin page. +app.get('/admin', auth.authenticate, auth.require_admin, function (req, res) { + res.set('Location', '/admin_Ypzr9fLs.html'); + return res.send('OK'); +}); + app.use('/register', auth.register); app.use('/login', auth.login); @@ -57,10 +64,10 @@ app.use('/resource', restful(models.Question, 'questions')); app.use('/resource', restful(models.Answer, 'answers', { pre_save: save_answer })); -app.post('/resource/questions/:questionId/activate', models.activate_question); -app.post('/resource/questions/:questionId/next', models.next_question); -app.get('/resource/leaders', models.leaders); -app.delete('/resource/leaders', auth.clear_leaders); +app.post('/resource/questions/:questionId/activate', auth.require_admin, models.activate_question); +app.post('/resource/questions/:questionId/next', auth.require_admin, models.next_question); +app.get('/resource/leaders', auth.require_admin, models.leaders); +app.delete('/resource/leaders', auth.require_admin, auth.clear_leaders); function save_answer(req, res, callback) { var answer = req.body; diff --git a/server/auth.js b/server/auth.js index f1b5df1..c0ba971 100644 --- a/server/auth.js +++ b/server/auth.js @@ -145,10 +145,18 @@ module.exports = function(models) { return models.clear_leaders(req, res, next); } + function require_admin(req, res, next) { + if (!req.user.get('is_admin')) { + res.status(401).send("Unauthorized"); + } else { + return next(); + } + } return { register: register, login: login, + require_admin: require_admin, on_register: on_register, authenticate: authenticate, clear_leaders: clear_leaders diff --git a/server/load_questions.js b/server/load_questions.js index f25c9dd..19d9e94 100644 --- a/server/load_questions.js +++ b/server/load_questions.js @@ -2,12 +2,15 @@ var config = require('./config'), knex = require('knex')(config.knex_options), Promise = require('knex/lib/promise'); -var qs = [{ +var startq = { 'question': 'start', - 'answers': ['start'], + 'answers': JSON.stringify(['start']), 'answer_index': 1, 'show': true -}, { +}; + +var qs = [ +{ 'question': 'What was Oscar the Grouch\'s original color?', 'answers': ['Orange', 'Red', 'Yello'], 'answer_index': 1 @@ -90,6 +93,8 @@ var qs = [{ }]; knex('questions').del().then(function() { + return knex('questions').insert(startq); +}).then(function() { return Promise.map(qs, function(question) { console.log("Creating ", question.question); question.answers = JSON.stringify(question.answers); diff --git a/server/models.js b/server/models.js index 8d27482..96a0161 100644 --- a/server/models.js +++ b/server/models.js @@ -47,7 +47,7 @@ module.exports = function(bookshelf) { function next_question(req, res, next) { var idWhere = '(select min(id) from questions where show = false and id > (select max(id) from questions where show = true))'; return bookshelf.knex('questions').update({ - 'show': knex.raw('(id = ' + idWhere + ')') + 'show': bookshelf.knex.raw('(id = ' + idWhere + ')') }) .then(function() { res.send('OK');