├── e2e └── conf.js ├── Procfile ├── .gitignore ├── .bowerrc ├── client ├── app │ ├── gettingStarted │ │ └── gettingStarted.html │ ├── welcome │ │ ├── welcome.js │ │ └── welcome.html │ ├── users │ │ ├── user │ │ │ ├── user.html │ │ │ ├── user.js │ │ │ └── courses │ │ │ │ ├── courses.html │ │ │ │ └── courses.js │ │ └── users.js │ ├── courses │ │ └── course │ │ │ ├── lesson-link.html │ │ │ ├── lessonLink.js │ │ │ ├── lessons │ │ │ └── lesson │ │ │ │ ├── lesson.html │ │ │ │ └── lesson.js │ │ │ ├── course.html │ │ │ └── course.js │ ├── auth │ │ └── auth.js │ └── app.js ├── styles │ └── style.css └── index.html ├── server ├── db │ ├── storage │ │ └── CourseBuilder.sqlite │ ├── collections │ │ ├── courses.js │ │ ├── lessons.js │ │ └── assignments.js │ ├── models │ │ ├── assignment.js │ │ ├── course.js │ │ ├── lesson.js │ │ └── user.js │ └── config.js ├── helpers │ └── passport-helper.js └── server.js ├── index.js ├── README.md ├── specs ├── client │ ├── usersSpec.js │ ├── userSpec.js │ ├── authSpec.js │ ├── routingSpec.js │ ├── coursesSpec.js │ ├── courseSpec.js │ └── lessonSpec.js └── server │ └── ServerSpec.js ├── bower.json ├── Gulpfile.js ├── package.json ├── karma.conf.js ├── _PRESS-RELEASE.md └── npm-debug.log /e2e/conf.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node index.js 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | client/lib 3 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "client/lib" 3 | } 4 | -------------------------------------------------------------------------------- /client/app/gettingStarted/gettingStarted.html: -------------------------------------------------------------------------------- 1 |
2 | GET STARTED WITH THE TRY IT OUT BUTTON 3 |
4 | -------------------------------------------------------------------------------- /server/db/storage/CourseBuilder.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drabinowitz/CourseBuilder/HEAD/server/db/storage/CourseBuilder.sqlite -------------------------------------------------------------------------------- /client/app/welcome/welcome.js: -------------------------------------------------------------------------------- 1 | angular.module('CB.welcome', []) 2 | 3 | .controller('welcomeController', ['$scope',function($scope){ 4 | 5 | }]); 6 | -------------------------------------------------------------------------------- /client/app/welcome/welcome.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Welcome to Course Builder!

4 |
5 |
6 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var app = require('./server/server.js'); 2 | 3 | var port = process.env.PORT || 8000; 4 | 5 | app.listen(port); 6 | 7 | console.log('listening on port ' + port); 8 | -------------------------------------------------------------------------------- /server/db/collections/courses.js: -------------------------------------------------------------------------------- 1 | var db = require('../config'); 2 | var Course = require('../models/course'); 3 | 4 | var Courses = new db.Collection(); 5 | 6 | Courses.model = Course; 7 | 8 | module.exports = Courses; 9 | -------------------------------------------------------------------------------- /server/db/collections/lessons.js: -------------------------------------------------------------------------------- 1 | var db = require('../config'); 2 | var Lesson = require('../models/lesson'); 3 | 4 | var Lessons = new db.Collection(); 5 | 6 | Lessons.model = Lesson; 7 | 8 | module.exports = Lessons; 9 | -------------------------------------------------------------------------------- /server/db/collections/assignments.js: -------------------------------------------------------------------------------- 1 | var db = require('../config'); 2 | var Assignment = require('../models/assignment'); 3 | 4 | var Assignments = new db.Collection(); 5 | 6 | Assignments.model = Assignment; 7 | 8 | module.exports = Assignments; 9 | -------------------------------------------------------------------------------- /client/app/users/user/user.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Welcome, {{ user.username }}

4 | Courses 5 |
6 |
7 |
8 | -------------------------------------------------------------------------------- /client/app/courses/course/lesson-link.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ lesson.name }}

3 |

hours : {{ lesson.hours }}

4 |
5 | -------------------------------------------------------------------------------- /client/app/users/users.js: -------------------------------------------------------------------------------- 1 | angular.module('CB.users',[]) 2 | 3 | .factory('users', ['$http',function($http){ 4 | return { 5 | get: function(id){ 6 | return $http.get('/users/' + id + '/courses').then(function(result){ 7 | return result.data; 8 | }); 9 | } 10 | }; 11 | }]); 12 | -------------------------------------------------------------------------------- /client/app/courses/course/lessonLink.js: -------------------------------------------------------------------------------- 1 | angular.module('lesson.link', []) 2 | 3 | .directive('lessonLink', function () { 4 | return { 5 | restrict: 'E', 6 | scope: { 7 | lesson: '=lesson' 8 | }, 9 | replace: true, 10 | templateUrl: 'app/courses/course/lesson-link.html' 11 | }; 12 | }); 13 | -------------------------------------------------------------------------------- /server/db/models/assignment.js: -------------------------------------------------------------------------------- 1 | var db = require('../config'); 2 | var Lesson = require('./lesson'); 3 | 4 | var Assignment = db.Model.extend({ 5 | tableName: 'assignments', 6 | hasTimestamps: true, 7 | lesson: function() { 8 | return this.belongsTo(Lesson, 'lesson_id'); 9 | } 10 | }); 11 | 12 | module.exports = Assignment; 13 | -------------------------------------------------------------------------------- /client/app/users/user/user.js: -------------------------------------------------------------------------------- 1 | angular.module('CB.users.user',[ 2 | 'ui.router', 3 | 'CB.users' 4 | ]) 5 | 6 | .controller('userController',['$scope','$stateParams','users', 7 | function($scope,$stateParams,users){ 8 | 9 | users.get($stateParams.userId).then(function(user){ 10 | $scope.user = user; 11 | }); 12 | 13 | }]); 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CourseBuilder 2 | 3 | An application allowing people to build and organize courses, lessons, and assignments. The app is built on Angular, Express, Node, MySQL, UI.Router, ngFx, Passport, Bookshelf, Knex and Gulp, Karma, Mocha, and Chai for continuous deployment and testing. 4 | 5 | Check out the deployed application at: https://coursebuilder.herokuapp.com/ 6 | -------------------------------------------------------------------------------- /server/db/models/course.js: -------------------------------------------------------------------------------- 1 | var db = require('../config'); 2 | var Lesson = require('./lesson'); 3 | var User = require('./user'); 4 | 5 | var Course = db.Model.extend({ 6 | tableName : 'courses', 7 | hasTimestamps: true, 8 | lessons: function(){ 9 | return this.hasMany(Lesson); 10 | }, 11 | user: function(){ 12 | return this.belongsTo(User, 'user_id'); 13 | } 14 | 15 | }); 16 | 17 | module.exports = Course; 18 | -------------------------------------------------------------------------------- /server/db/models/lesson.js: -------------------------------------------------------------------------------- 1 | var db = require('../config'); 2 | var Assignment = require('./assignment'); 3 | var Course = require('./course'); 4 | 5 | var Lesson = db.Model.extend({ 6 | tableName : 'lessons', 7 | hasTimestamps: true, 8 | assignments: function(){ 9 | return this.hasMany(Assignment); 10 | }, 11 | course: function(){ 12 | return this.belongsTo(Course, 'course_id'); 13 | } 14 | 15 | }); 16 | 17 | module.exports = Lesson; 18 | -------------------------------------------------------------------------------- /client/app/auth/auth.js: -------------------------------------------------------------------------------- 1 | angular.module('CB.auth',[]) 2 | 3 | .controller('authController',['$scope','auth',function($scope,auth){ 4 | auth.get().then(function(user){ 5 | $scope.user = user; 6 | }); 7 | }]) 8 | 9 | .factory('auth',['$http',function($http){ 10 | return { 11 | get: function(){ 12 | return $http.get('/users').then(function(result){ 13 | return result.data; 14 | }).catch(function(err){ 15 | return false; 16 | }); 17 | } 18 | }; 19 | }]); 20 | -------------------------------------------------------------------------------- /specs/client/usersSpec.js: -------------------------------------------------------------------------------- 1 | describe('Users Factory', function() { 2 | var users,$httpBackend; 3 | 4 | beforeEach(module('CB')); 5 | beforeEach(inject(function($injector){ 6 | users = $injector.get('users'); 7 | $httpBackend = $injector.get('$httpBackend'); 8 | })); 9 | 10 | it ('should return the guest user', function(){ 11 | var mockUser = {'name':{}}; 12 | $httpBackend.expectGET("/users/1/courses").respond(mockUser); 13 | users.get(1).then(function(user){ 14 | expect(user).to.eql(mockUser); 15 | }); 16 | $httpBackend.flush(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Course-Builder", 3 | "version": "0.0.0", 4 | "homepage": "https://github.com/drabinowitz/2014-10-solo", 5 | "authors": [ 6 | "drabinowitz " 7 | ], 8 | "description": "a course designer, organizer, and builder for teachers", 9 | "license": "MIT", 10 | "ignore": [ 11 | "**/.*", 12 | "node_modules", 13 | "bower_components", 14 | "test", 15 | "tests" 16 | ], 17 | "dependencies": { 18 | "angular": "~1.3.4", 19 | "angular-ui-router": "~0.2.13", 20 | "bootstrap": "~3.3.1", 21 | "ngFx": "~1.0.5" 22 | }, 23 | "devDependencies": { 24 | "angular-mocks": "~1.3.4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /client/app/users/user/courses/courses.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 | 7 |
8 |
9 |
10 | 11 |
12 |
13 |
14 |

{{ course.name }}

15 |

hours : {{ course.hours }}

16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /client/app/courses/course/lessons/lesson/lesson.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Lesson: {{ lesson.name }}

4 |

Hours: {{ lesson.hours }}

5 |
6 |
7 |
8 |
9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |

{{ assignment.name }}

17 |

Hours: {{ assignment.hours }}

18 |

{{ assignment.body }}

19 |
20 |
21 | -------------------------------------------------------------------------------- /client/styles/style.css: -------------------------------------------------------------------------------- 1 | .lesson { 2 | display: inline-block; 3 | padding: 3px; 4 | border-radius: 6px; 5 | border: 1px solid rgba(128, 128, 128, 0.69); 6 | margin: 1px 0; 7 | width: 260px; 8 | background-color: white; 9 | } 10 | 11 | .course { 12 | padding: 5px; 13 | border: 1px solid grey; 14 | margin: 5px; 15 | border-radius: 5px; 16 | } 17 | 18 | input { 19 | height: 50px; 20 | width: 150px; 21 | /* display: block; */ 22 | margin: 10px; 23 | padding-left: 8px; 24 | } 25 | 26 | textarea { 27 | display: block; 28 | margin: 9px; 29 | width: 324px; 30 | } 31 | 32 | .row { 33 | border: 1px solid grey; 34 | } 35 | 36 | .col-md-8 { 37 | border: 1px solid grey; 38 | } 39 | 40 | form { 41 | margin-bottom: 5px; 42 | } 43 | 44 | aside.lesson-link-sidebar { 45 | position: fixed; 46 | background: rgb(250,250,250); 47 | left: 0; 48 | bottom: 0; 49 | top: 187px; 50 | width: 300px; 51 | z-index: 1; 52 | } 53 | 54 | .course-hour-progress-bar { 55 | height: 50px; 56 | border: 1px solid black; 57 | } 58 | 59 | .current-progress { 60 | background-color: blue; 61 | } 62 | 63 | .total-progress { 64 | width: 100px; 65 | } 66 | -------------------------------------------------------------------------------- /specs/client/userSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('user', function() { 4 | var $scope, $rootScope, $stateParams, createController, users, mockUser; 5 | 6 | mockUser = { 7 | name:'test', 8 | courses: ['1'], 9 | id:0 10 | }; 11 | 12 | beforeEach(module('CB')); 13 | beforeEach(inject(function($injector){ 14 | 15 | $rootScope = $injector.get('$rootScope'); 16 | $stateParams = $injector.get('$stateParams'); 17 | $scope = $rootScope.$new(); 18 | 19 | users = { 20 | get: function(){ 21 | return { 22 | then: function(cb){ 23 | return cb(mockUser); 24 | } 25 | }; 26 | } 27 | }; 28 | 29 | var $controller = $injector.get('$controller'); 30 | 31 | createController = function() { 32 | return $controller('userController', { 33 | $scope:$scope, 34 | $stateParams:$stateParams, 35 | users:users 36 | }); 37 | }; 38 | })); 39 | 40 | describe('userController', function() { 41 | 42 | it('should get user data for the associated user', function () { 43 | createController(); 44 | expect($scope.user).to.equal(mockUser); 45 | }); 46 | 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /client/app/users/user/courses/courses.js: -------------------------------------------------------------------------------- 1 | angular.module('CB.users.user.courses',[ 2 | 'ngFx', 3 | 'CB.users.user' 4 | ]) 5 | 6 | .controller('coursesController',['$scope','$stateParams','courses',function($scope,$stateParams,courses){ 7 | 8 | $scope.newCourse = {}; 9 | 10 | $scope.user = {courses:[]}; 11 | 12 | if ($stateParams.userId === '1'){$scope.isGuest = true;} 13 | 14 | courses.get($stateParams.userId).then(function(user){ 15 | $scope.user = user; 16 | }); 17 | 18 | $scope.addCourse = function(formIsValid){ 19 | if (formIsValid){ 20 | courses.add($stateParams.userId,$scope.newCourse.name,$scope.newCourse.hours).then(function(course){ 21 | $scope.newCourse = {}; 22 | $scope.user.courses.push(course); 23 | }); 24 | } 25 | }; 26 | 27 | }]) 28 | 29 | .factory('courses',['$http',function($http){ 30 | 31 | return { 32 | get: function(id){ 33 | return $http.get('/users/' + id + '/courses').then(function(result){ 34 | return result.data; 35 | }); 36 | }, 37 | add: function(id,name,hours){ 38 | return $http.post('/users/' + id + '/courses',{ 39 | name:name, 40 | hours:hours 41 | }).then(function(result){ 42 | return result.data; 43 | }); 44 | } 45 | }; 46 | }]); 47 | -------------------------------------------------------------------------------- /Gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'), 4 | nodemon = require('gulp-nodemon'), 5 | bs = require('browser-sync'), 6 | reload = bs.reload, 7 | karma = require('karma').server, 8 | shell = require('gulp-shell'); 9 | 10 | var paths = { 11 | 12 | scripts: ['client/app/**/*.js'], 13 | html: ['client/app/**/*.html', 'client/index.html'], 14 | styles: ['client/styles/style.css'], 15 | test: ['specs/**/*.js'] 16 | }; 17 | 18 | gulp.task('start', ['serve'], function() { 19 | bs({ 20 | notify: true, 21 | injectChanges: true, 22 | files: paths.scripts.concat(paths.html, paths.styles), 23 | proxy: 'localhost:8000' 24 | }); 25 | }); 26 | 27 | gulp.task('karma', shell.task([ 28 | 'karma start' 29 | ])); 30 | 31 | gulp.task('karma-auto', function(done){ 32 | karma.start({ 33 | configFile: __dirname + '/karma.conf.js', 34 | autoWatch: true, 35 | singleRun: false 36 | },done); 37 | }); 38 | 39 | gulp.task('selenium', shell.task([ 40 | 'webdriver-manager start' 41 | ])); 42 | 43 | gulp.task('e2e', shell.task([ 44 | 'protractor e2e/conf.js' 45 | ])); 46 | 47 | gulp.task('serve', function() { 48 | nodemon({script: 'index.js', ignore: 'node_modules/**/*.js'}); 49 | }); 50 | 51 | gulp.task('default', ['start']); 52 | -------------------------------------------------------------------------------- /client/app/courses/course/course.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

Course: {{course.name}}

4 |

Hours: {{course.hours}}

5 |
6 |
7 |
8 |
9 |
10 |
11 | 12 | 13 | 14 |
15 |
16 |
17 | 18 |
19 | 24 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /specs/client/authSpec.js: -------------------------------------------------------------------------------- 1 | describe('auth',function(){ 2 | 3 | describe('auth factory',function(){ 4 | var auth,$httpBackend; 5 | beforeEach(module('CB')); 6 | beforeEach(inject(function($injector){ 7 | auth = $injector.get('auth'); 8 | $httpBackend = $injector.get('$httpBackend'); 9 | })); 10 | 11 | it('should request the user',function(){ 12 | var mockUser = {'name':'test'}; 13 | $httpBackend.expectGET('/users').respond(mockUser); 14 | auth.get().then(function(user){ 15 | expect(user).to.equal(mockUser); 16 | }); 17 | }); 18 | 19 | }); 20 | 21 | describe('auth controller', function(){ 22 | var $scope,$rootScope,$controller,createController,auth,mockUser; 23 | 24 | beforeEach(module('CB')); 25 | beforeEach(inject(function($injector){ 26 | $rootScope = $injector.get('$rootScope'); 27 | $scope = $rootScope.$new(); 28 | $controller = $injector.get('$controller'); 29 | mockUser = {name:'test'}; 30 | 31 | auth = { 32 | get: function(){ 33 | return { 34 | then: function(cb){ 35 | cb(mockUser); 36 | } 37 | }; 38 | } 39 | }; 40 | 41 | createController = function(){ 42 | return $controller('authController',{ 43 | $scope:$scope, 44 | auth:auth 45 | }); 46 | }; 47 | })); 48 | 49 | it('should load the user if it logged in', function(){ 50 | createController(); 51 | expect($scope.user).to.equal(mockUser); 52 | }); 53 | }); 54 | 55 | }); 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Course-Builder", 3 | "version": "1.0.0", 4 | "description": "a Course organizer, designer, and builder for teachers", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "gulp serve", 8 | "postinstall": "bower cache clean && bower install" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/drabinowitz/2014-10-solo" 13 | }, 14 | "author": "drabinowitz", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/drabinowitz/2014-10-solo/issues" 18 | }, 19 | "homepage": "https://github.com/drabinowitz/2014-10-solo", 20 | "devDependencies": { 21 | "browser-sync": "^1.7.2", 22 | "expect.js": "^0.3.1", 23 | "jasmine-core": "^2.1.2", 24 | "karma": "^0.12.28", 25 | "karma-chrome-launcher": "^0.1.5", 26 | "karma-cli": "0.0.4", 27 | "karma-jasmine": "^0.3.2", 28 | "karma-mocha": "^0.1.9", 29 | "karma-nested-reporter": "^0.1.3", 30 | "karma-nyan-reporter": "0.0.50", 31 | "karma-phantomjs-launcher": "^0.1.4", 32 | "karma-unicorn-reporter": "^0.1.4", 33 | "mocha": "^2.0.1" 34 | }, 35 | "dependencies": { 36 | "bcrypt-nodejs": "0.0.3", 37 | "bluebird": "^2.3.11", 38 | "body-parser": "^1.9.3", 39 | "bookshelf": "^0.7.9", 40 | "bower": "^1.3.12", 41 | "chai": "^1.10.0", 42 | "cookie-parser": "^1.3.3", 43 | "express": "^4.10.4", 44 | "express-session": "^1.9.2", 45 | "gulp": "^3.8.10", 46 | "gulp-nodemon": "^1.0.4", 47 | "gulp-shell": "^0.2.11", 48 | "knex": "^0.7.3", 49 | "passport": "^0.2.1", 50 | "passport-github": "^0.1.5", 51 | "passport-local": "^1.0.0", 52 | "path": "^0.4.9", 53 | "request": "^2.49.0", 54 | "require": "^2.4.17", 55 | "sqlite3": "^3.0.4", 56 | "url": "^0.10.1" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /client/app/app.js: -------------------------------------------------------------------------------- 1 | angular.module('CB', [ 2 | 'CB.welcome', 3 | 'CB.auth', 4 | 'CB.courses.course.lessons.lesson', 5 | 'CB.users.user.courses', 6 | 'ngFx', 7 | 'ui.router' 8 | ]) 9 | .config(['$stateProvider','$urlRouterProvider', '$httpProvider', 10 | function($stateProvider,$urlRouterProvider,$httpProvider){ 11 | $urlRouterProvider.otherwise('/welcome'); 12 | 13 | $stateProvider 14 | .state('welcome', { 15 | url: '/welcome', 16 | templateUrl: 'app/welcome/welcome.html', 17 | controller: 'welcomeController' 18 | }) 19 | .state('getting-started', { 20 | url: '/getting-started', 21 | templateUrl: 'app/gettingStarted/gettingStarted.html' 22 | }) 23 | .state('users', { 24 | url: '/users', 25 | abstract: true, 26 | template: '
' 42 | }) 43 | .state('courses.course', { 44 | url: '/:courseId', 45 | templateUrl: 'app/courses/course/course.html', 46 | controller: 'courseController' 47 | }) 48 | .state('courses.course.lessons', { 49 | url: '/lessons', 50 | abstract: true, 51 | template: '
' 52 | }) 53 | .state('courses.course.lessons.lesson', { 54 | url: '/:lessonId/assignments', 55 | templateUrl: 'app/courses/course/lessons/lesson/lesson.html', 56 | controller: 'lessonController' 57 | }); 58 | }]); 59 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Course Builder 6 | 7 | 8 | 9 | 10 | 32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /client/app/courses/course/course.js: -------------------------------------------------------------------------------- 1 | angular.module('CB.courses.course',[ 2 | 'ngFx', 3 | 'ui.router', 4 | 'lesson.link' 5 | ]) 6 | 7 | .controller('courseController',['$scope','$stateParams','course', 8 | function($scope,$stateParams,course){ 9 | 10 | $scope.newLesson = {}; 11 | 12 | $scope.course = {lessons:[]}; 13 | $scope.lessons = $scope.course.lessons; 14 | 15 | var progressBarUpdate = function () { 16 | $scope.progressBar = { 17 | 'width': $scope.course.lessons.reduce(function (totalHours, lesson) { 18 | return totalHours + lesson.hours/$scope.course.hours * 100; 19 | }, 0) + 'px' 20 | }; 21 | }; 22 | 23 | progressBarUpdate(); 24 | 25 | $scope.$watch('lessons', progressBarUpdate); 26 | 27 | if ($stateParams.courseId === '1'){$scope.isGuest = true;} 28 | 29 | course.get($stateParams.courseId).then(function(course){ 30 | $scope.course = course; 31 | $scope.lessons = $scope.course.lessons; 32 | }); 33 | 34 | $scope.addLesson = function(formIsValid){ 35 | var order = 0; 36 | if($scope.course.lessons.length){ 37 | order = $scope.course.lessons[$scope.course.lessons.length - 1].order; 38 | } 39 | if(formIsValid){ 40 | course.add($stateParams.courseId,$scope.newLesson.name,$scope.newLesson.hours,order).then(function(lesson){ 41 | $scope.course.lessons.push(lesson); 42 | $scope.newLesson = {}; 43 | }); 44 | } 45 | }; 46 | 47 | }]) 48 | 49 | .factory('course',['$http',function($http){ 50 | 51 | return { 52 | get: function(id){ 53 | return $http.get('/courses/'+ id + '/lessons').then(function(results){ 54 | return results.data; 55 | }); 56 | }, 57 | add: function(id,name,hours,order){ 58 | return $http.post('/courses/' + id + '/lessons',{ 59 | name:name, 60 | hours:hours, 61 | order:order 62 | }).then(function(result){ 63 | return result.data; 64 | }); 65 | } 66 | }; 67 | 68 | }]); 69 | -------------------------------------------------------------------------------- /server/db/models/user.js: -------------------------------------------------------------------------------- 1 | var db = require('../config'); 2 | var Course = require('./course'); 3 | var Promise = require('bluebird'); 4 | var bcrypt = Promise.promisifyAll(require('bcrypt-nodejs')); 5 | 6 | var autoLogin = function(username,password,user){return user;}; 7 | 8 | var login = {}; 9 | 10 | login.local = function(username,password,user){ 11 | return bcrypt.compareAsync(password,user.get('password')); 12 | }; 13 | 14 | login.guest = autoLogin; 15 | login.github = autoLogin; 16 | login.facebook = autoLogin; 17 | 18 | var autoSignup = function(authType,username,password){ 19 | return new User({ 20 | username: username, 21 | authType: authType 22 | }).save(); 23 | }; 24 | 25 | var signup = {}; 26 | 27 | signup.local = function(authType,username,password){ 28 | return new User({ 29 | username: username, 30 | password: password 31 | }).save(); 32 | }; 33 | 34 | signup.guest = autoSignup; 35 | signup.github = autoSignup; 36 | signup.facebook = autoSignup; 37 | 38 | var User = db.Model.extend({ 39 | tableName: 'users', 40 | hasTimestamps: true, 41 | courses: function() { 42 | return this.hasMany(Course); 43 | }, 44 | 45 | initialize: function() { 46 | this.on('saving', function(model,attrs,options){ 47 | if (model.get('authType') === 'local'){ 48 | var password = model.get('password'); 49 | var salt = bcrypt.genSaltSync(); 50 | var hash = bcrypt.hashSync(password); 51 | model.set('password',hash); 52 | } 53 | }); 54 | } 55 | },{ 56 | 57 | login: Promise.method(function(username,password){ 58 | if(!username) throw new Error ('username required'); 59 | return new this({username: username}).fetch({require: true}).tap(function(user){ 60 | return login[user.get('authType')](username,password,user); 61 | }); 62 | }), 63 | 64 | signup: Promise.method(function(authType,username,password){ 65 | return new User({username: username}).feth() 66 | .then(function(found){ 67 | if (found) throw new Error ('username already exists'); 68 | return signup[authType](authType,username,password); 69 | }); 70 | }) 71 | }); 72 | 73 | module.exports = User; 74 | 75 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Mon Dec 01 2014 15:09:28 GMT-0500 (EST) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | //angular files 19 | 'client/lib/angular/angular.js', 20 | 'client/lib/angular-ui-router/release/angular-ui-router.js', 21 | 'client/lib/ngFx/dist/ngFx.js', 22 | 'client/lib/angular-mocks/angular-mocks.js', 23 | 24 | //app code 25 | 'client/app/**/*.js', 26 | 27 | //spec files 28 | 'node_modules/expect.js/index.js', 29 | 'specs/client/**/*.js' 30 | ], 31 | 32 | 33 | // list of files to exclude 34 | exclude: [ 35 | 'karma.conf.js' 36 | ], 37 | 38 | 39 | // preprocess matching files before serving them to the browser 40 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 41 | preprocessors: { 42 | }, 43 | 44 | 45 | // test results reporter to use 46 | // possible values: 'dots', 'progress' 47 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 48 | reporters: ['nyan'], 49 | 50 | 51 | // web server port 52 | port: 9876, 53 | 54 | 55 | // enable / disable colors in the output (reporters and logs) 56 | colors: true, 57 | 58 | 59 | // level of logging 60 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 61 | logLevel: config.LOG_INFO, 62 | 63 | 64 | // enable / disable watching file and executing tests whenever any file changes 65 | autoWatch: false, 66 | 67 | 68 | // start these browsers 69 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 70 | browsers: ['Chrome'], 71 | 72 | 73 | // Continuous Integration mode 74 | // if true, Karma captures browsers, runs the tests and exits 75 | singleRun: true 76 | }); 77 | }; 78 | -------------------------------------------------------------------------------- /specs/client/routingSpec.js: -------------------------------------------------------------------------------- 1 | describe('Routing', function() { 2 | var $state; 3 | beforeEach(module('CB')); 4 | 5 | beforeEach(inject(function($injector){ 6 | $state = $injector.get('$state'); 7 | })); 8 | 9 | it('should have a welcome state, template, and controller', function(){ 10 | expect($state.get('welcome')).to.be.ok(); 11 | expect($state.get('welcome').controller).to.be('welcomeController'); 12 | expect($state.get('welcome').templateUrl).to.be('app/welcome/welcome.html'); 13 | }); 14 | 15 | it('should have a getting-started state, template, and controller', function(){ 16 | expect($state.get('getting-started')).to.be.ok(); 17 | expect($state.get('getting-started').templateUrl).to.be('app/gettingStarted/gettingStarted.html'); 18 | }); 19 | 20 | it('should have a user state, template, and controller nested under users', function(){ 21 | expect($state.get('users.user')).to.be.ok(); 22 | expect($state.get('users.user').controller).to.be('userController'); 23 | expect($state.get('users.user').templateUrl).to.be('app/users/user/user.html'); 24 | }); 25 | 26 | it('should have a courses state, template, and controller nested under user', function(){ 27 | expect($state.get('users.user.courses')).to.be.ok(); 28 | expect($state.get('users.user.courses').controller).to.be('coursesController'); 29 | expect($state.get('users.user.courses').templateUrl).to.be('app/users/user/courses/courses.html'); 30 | }); 31 | 32 | it('should have a course state, template, and controller nested under courses', function(){ 33 | expect($state.get('courses.course')).to.be.ok(); 34 | expect($state.get('courses.course').controller).to.be('courseController'); 35 | expect($state.get('courses.course').templateUrl).to.be('app/courses/course/course.html'); 36 | }); 37 | 38 | it('should have a lesson state, template, and controller nested under course', function(){ 39 | expect($state.get('courses.course.lessons.lesson')).to.be.ok(); 40 | expect($state.get('courses.course.lessons.lesson').controller).to.be('lessonController'); 41 | expect($state.get('courses.course.lessons.lesson').templateUrl).to.be('app/courses/course/lessons/lesson/lesson.html'); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /client/app/courses/course/lessons/lesson/lesson.js: -------------------------------------------------------------------------------- 1 | angular.module('CB.courses.course.lessons.lesson',[ 2 | 'CB.courses.course', 3 | 'ui.router', 4 | 'ngFx' 5 | ]) 6 | 7 | .controller('lessonController',['$scope','$stateParams','lesson', 8 | function($scope,$stateParams,lesson){ 9 | 10 | $scope.newAssignment = {}; 11 | 12 | $scope.lesson = {assignments: []}; 13 | $scope.assignments = $scope.lesson.assignments; 14 | 15 | if ($stateParams.courseId === '1'){$scope.isGuest = true;} 16 | 17 | lesson.get($stateParams.courseId,$stateParams.lessonId).then(function(lesson){ 18 | $scope.lesson = lesson; 19 | $scope.assignments = $scope.lesson.assignments; 20 | }); 21 | 22 | var progressBarUpdate = function () { 23 | if ($scope.lesson.assignments) { 24 | $scope.progressBar = { 25 | 'width': $scope.lesson.assignments.reduce(function (totalHours, assignment) { 26 | return totalHours + assignment.hours/$scope.lesson.hours * 100; 27 | }, 0) + 'px' 28 | }; 29 | } 30 | }; 31 | 32 | progressBarUpdate(); 33 | 34 | $scope.$watch('assignments', progressBarUpdate); 35 | 36 | $scope.addAssignment = function(formIsValid){ 37 | var order = 0; 38 | if($scope.lesson.assignments.length){ 39 | order = $scope.lesson.assignments[$scope.lesson.assignments.length - 1].order; 40 | } 41 | if(formIsValid){ 42 | lesson.add($stateParams.courseId,$stateParams.lessonId,$scope.newAssignment.name,'text',$scope.newAssignment.hours,order,$scope.newAssignment.body).then(function(assignment){ 43 | $scope.lesson.assignments.push(assignment); 44 | }); 45 | } 46 | }; 47 | }]) 48 | 49 | .factory('lesson',['$http',function($http){ 50 | return { 51 | get: function(courseId,lessonId){ 52 | return $http.get('/courses/' + courseId + '/lessons/' + lessonId + '/assignments').then(function(result){ 53 | return result.data; 54 | }); 55 | }, 56 | add: function(courseId,lessonId,name,type,hours,order,body){ 57 | return $http.post('/courses/' + courseId + '/lessons/' + lessonId + '/assignments',{ 58 | name:name, 59 | type:type, 60 | hours:hours, 61 | order:order, 62 | body:body 63 | }).then(function(result){ 64 | return result.data; 65 | }); 66 | } 67 | }; 68 | }]); 69 | -------------------------------------------------------------------------------- /specs/client/coursesSpec.js: -------------------------------------------------------------------------------- 1 | describe('courses', function(){ 2 | 3 | describe('courses factory', function() { 4 | var courses,$httpBackend; 5 | beforeEach(module('CB')); 6 | beforeEach(inject(function($injector){ 7 | courses = $injector.get('courses'); 8 | $httpBackend = $injector.get('$httpBackend'); 9 | })); 10 | 11 | it('should get the courses for an associated user', function() { 12 | var mockUser = {'courses':[{},{},{}]}; 13 | $httpBackend.expectGET('/users/1/courses').respond(mockUser); 14 | courses.get(1).then(function(user){ 15 | expect(user).to.eql(mockUser); 16 | }); 17 | $httpBackend.flush(); 18 | }); 19 | 20 | it('should post new courses', function(){ 21 | var newMockCourse = {'name':'new'}; 22 | $httpBackend.expectPOST('/users/1/courses').respond(newMockCourse); 23 | courses.add(1).then(function(course){ 24 | expect(course).to.eql(newMockCourse); 25 | }); 26 | $httpBackend.flush(); 27 | }); 28 | }); 29 | 30 | describe('coursesController',function(){ 31 | var $scope, $rootScope, $stateParams, createController, courses, mockCourse; 32 | 33 | var mockUser = {'courses':[{ 34 | name:'testcourse', 35 | hours:10 36 | }]}; 37 | 38 | mockCourse = {'name':'test'}; 39 | 40 | beforeEach(module('CB')); 41 | beforeEach(inject(function($injector){ 42 | 43 | $rootScope = $injector.get('$rootScope'); 44 | $stateParams = $injector.get('$stateParams'); 45 | $scope = $rootScope.$new(); 46 | 47 | courses = { 48 | get: function(){ 49 | return { 50 | then:function(cb){ 51 | cb(mockUser); 52 | } 53 | }; 54 | }, 55 | add: function(){ 56 | return { 57 | then:function(cb){ 58 | cb(mockCourse); 59 | } 60 | }; 61 | } 62 | }; 63 | 64 | var $controller = $injector.get('$controller'); 65 | 66 | createController = function() { 67 | return $controller('coursesController', { 68 | $scope:$scope, 69 | $stateParams:$stateParams, 70 | courses:courses 71 | }); 72 | }; 73 | })); 74 | 75 | it('should load the courses for the associated user', function() { 76 | createController(); 77 | expect($scope.user).to.equal(mockUser); 78 | }); 79 | 80 | it('should add courses', function(){ 81 | createController(); 82 | $scope.user = {}; 83 | $scope.user.courses=[]; 84 | $scope.addCourse(true); 85 | expect($scope.user.courses[0]).to.equal(mockCourse); 86 | }); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /server/db/config.js: -------------------------------------------------------------------------------- 1 | var Bookshelf = require('bookshelf'); 2 | var path = require('path'); 3 | // var SQLITE3 = require('../../creds/SQLITE3'); 4 | 5 | var db = Bookshelf.initialize({ 6 | client: 'sqlite3', 7 | connection: { 8 | host: process.env.SQLITE3_HOST, 9 | user: process.env.SQLITE3_USER, 10 | password: process.env.SQLITE3_PASSWORD, 11 | database: process.env.SQLITE3_DATABASE, 12 | charset: 'utf8', 13 | filename: path.join(__dirname, 'storage/' + process.env.SQLITE3_DATABASE + '.sqlite') 14 | } 15 | }); 16 | 17 | db.knex.schema.hasTable('users').then(function(exists) { 18 | if (!exists) { 19 | db.knex.schema.createTable('users', function(user) { 20 | user.increments('id').primary(); 21 | user.string('username',255).unique(); 22 | user.string('password',255); 23 | user.string('authType',255).defaultTo('local'); 24 | user.timestamps(); 25 | }).then(function(table) { 26 | console.log('Created Table', table); 27 | }); 28 | } 29 | }); 30 | 31 | db.knex.schema.hasTable('courses').then(function(exists) { 32 | if (!exists){ 33 | db.knex.schema.createTable('courses', function(course) { 34 | course.increments('id').primary(); 35 | course.integer('user_id'); 36 | course.string('name',255).defaultTo('New Course'); 37 | course.integer('hours').defaultTo(30); 38 | course.timestamps(); 39 | }).then(function(table) { 40 | console.log('Created Table', table); 41 | }); 42 | } 43 | }); 44 | 45 | db.knex.schema.hasTable('lessons').then(function(exists) { 46 | if (!exists){ 47 | db.knex.schema.createTable('lessons', function(lesson) { 48 | lesson.increments('id').primary(); 49 | lesson.integer('course_id'); 50 | lesson.string('name',255).defaultTo('New Lesson'); 51 | lesson.integer('hours').defaultTo(3); 52 | lesson.integer('order'); 53 | lesson.timestamps(); 54 | }).then(function(table) { 55 | console.log('Created Table', table); 56 | }); 57 | } 58 | }); 59 | 60 | db.knex.schema.hasTable('assignments').then(function(exists) { 61 | if (!exists){ 62 | db.knex.schema.createTable('assignments', function(assignment){ 63 | assignment.increments('id').primary(); 64 | assignment.integer('lesson_id'); 65 | assignment.string('name',255).defaultTo('New Assignment'); 66 | assignment.string('type',255).defaultTo('text'); 67 | assignment.string('body',255); 68 | assignment.integer('hours').defaultTo(0); 69 | assignment.integer('order'); 70 | assignment.timestamps(); 71 | }).then(function(table) { 72 | console.log('Created Table', table); 73 | }); 74 | } 75 | }); 76 | 77 | module.exports = db; 78 | -------------------------------------------------------------------------------- /specs/client/courseSpec.js: -------------------------------------------------------------------------------- 1 | describe('course',function(){ 2 | 3 | describe('course factory',function(){ 4 | var course,$httpBackend; 5 | beforeEach(module('CB')); 6 | beforeEach(inject(function($injector){ 7 | course = $injector.get('course'); 8 | $httpBackend = $injector.get('$httpBackend'); 9 | })); 10 | 11 | it('should pull up the lesson ids for a course',function(){ 12 | var mockCourse = {'lessons':[{},{},{}]}; 13 | $httpBackend.expectGET('/courses/1/lessons').respond(mockCourse); 14 | course.get(1).then(function(course){ 15 | expect(course).to.eql(mockCourse); 16 | }); 17 | $httpBackend.flush(); 18 | }); 19 | 20 | it('should post new courses', function(){ 21 | var newMockLesson = {'name':'new'}; 22 | $httpBackend.expectPOST('/courses/1/lessons').respond(newMockLesson); 23 | course.add(1).then(function(lesson){ 24 | expect(lesson).to.eql(newMockLesson); 25 | }); 26 | $httpBackend.flush(); 27 | }); 28 | }); 29 | 30 | describe('courseController', function(){ 31 | var $scope,$stateParams,$rootScope,$controller,createController,course,mockCourse,mockLesson; 32 | 33 | beforeEach(module('CB')); 34 | beforeEach(inject(function($injector){ 35 | $rootScope = $injector.get('$rootScope'); 36 | $scope = $rootScope.$new(); 37 | $stateParams = $injector.get('$stateParams'); 38 | $stateParams.courseId = 0; 39 | $controller = $injector.get('$controller'); 40 | 41 | mockCourse = {name:'testcourse'}; 42 | 43 | mockLesson = {name:'testlesson'}; 44 | 45 | course = { 46 | get:function(courseId){ 47 | var course = { 48 | '0': mockCourse 49 | }; 50 | return { 51 | then: function(cb){ 52 | cb(course[courseId]); 53 | } 54 | }; 55 | }, 56 | add:function(id){ 57 | return { 58 | then: function(cb){ 59 | cb(mockLesson); 60 | } 61 | }; 62 | } 63 | }; 64 | createController = function(){ 65 | return $controller('courseController',{ 66 | $scope:$scope, 67 | $stateParams:$stateParams, 68 | course:course 69 | }); 70 | }; 71 | })); 72 | 73 | it('should load the course with its associated lesson',function(){ 74 | createController(); 75 | expect($scope.course).to.equal(mockCourse); 76 | }); 77 | 78 | it('should allow users to add lessons',function(){ 79 | createController(); 80 | $scope.course = {}; 81 | $scope.course.lessons=[]; 82 | $scope.addLesson(true); 83 | expect($scope.course.lessons[0]).to.equal(mockLesson); 84 | }); 85 | }); 86 | 87 | }); 88 | -------------------------------------------------------------------------------- /specs/client/lessonSpec.js: -------------------------------------------------------------------------------- 1 | describe('lesson',function(){ 2 | describe('lesson factory',function(){ 3 | var lesson,$httpBackend; 4 | 5 | beforeEach(module('CB')); 6 | beforeEach(inject(function($injector){ 7 | lesson = $injector.get('lesson'); 8 | $httpBackend = $injector.get('$httpBackend'); 9 | })); 10 | 11 | it('should get the templates for a lesson',function(){ 12 | var mockLesson = {'assignments':[{},{},{}]}; 13 | $httpBackend.expectGET('/courses/1/lessons/1/assignments').respond(mockLesson); 14 | lesson.get(1,1).then(function(lesson){ 15 | expect(lesson).to.eql(mockLesson); 16 | }); 17 | $httpBackend.flush(); 18 | }); 19 | 20 | it('should post new assignments', function(){ 21 | var newMockAssignment = {'name':'new'}; 22 | $httpBackend.expectPOST('/courses/1/lessons/1/assignments').respond(newMockAssignment); 23 | lesson.add(1,1).then(function(assignment){ 24 | expect(assignment).to.eql(newMockAssignment); 25 | }); 26 | $httpBackend.flush(); 27 | }); 28 | }); 29 | 30 | describe('lessonController',function(){ 31 | var $scope,$rootScope,$controller,$stateParams,createController,lesson,mockLesson,mockAssignment; 32 | 33 | beforeEach(module('CB')); 34 | beforeEach(inject(function($injector){ 35 | $rootScope = $injector.get('$rootScope'); 36 | $scope = $rootScope.$new(); 37 | $controller = $injector.get('$controller'); 38 | $stateParams = $injector.get('$stateParams'); 39 | $stateParams.lessonId = 0; 40 | 41 | mockLesson = { 42 | name:'testLesson', 43 | hours:5, 44 | id:0, 45 | templates:[{ 46 | type:'text', 47 | body:'testTemplate', 48 | id:0 49 | }] 50 | }; 51 | 52 | mockAssignment = {'name':'newassignment'}; 53 | 54 | lesson = { 55 | get:function(lessonId){ 56 | var lesson = [mockLesson]; 57 | return { 58 | then:function(cb){ 59 | cb(lesson[lessonId]); 60 | } 61 | }; 62 | }, 63 | add:function(){ 64 | return { 65 | then:function(cb){ 66 | cb(mockAssignment); 67 | } 68 | }; 69 | } 70 | }; 71 | 72 | createController = function(){ 73 | return $controller('lessonController',{ 74 | $scope: $scope, 75 | $stateParams: $stateParams, 76 | lesson: lesson 77 | }); 78 | }; 79 | 80 | })); 81 | 82 | it('should get associated lesson',function(){ 83 | createController(); 84 | expect($scope.lesson).to.equal(mockLesson); 85 | }); 86 | 87 | it('should add lessons',function(){ 88 | createController(); 89 | $scope.lesson = {}; 90 | $scope.lesson.assignments=[]; 91 | $scope.addAssignment(true); 92 | expect($scope.lesson.assignments[0]).to.equal(mockAssignment); 93 | }); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /server/helpers/passport-helper.js: -------------------------------------------------------------------------------- 1 | // var GITHUB = require('../../creds/github'); 2 | // var SECRETS = require('../../creds/secrets'); 3 | var passport = require('passport'); 4 | var LocalStrategy = require('passport-local'); 5 | var GithubStrategy = require('passport-github').Strategy; 6 | var User = require('../db/models/user'); 7 | var cookieParser = require('cookie-parser'); 8 | var session = require('express-session'); 9 | 10 | var authGen = function(strategy,successRedirect,failureRedirect){ 11 | strategy = strategy || 'local'; 12 | successRedirect = successRedirect || '/'; 13 | failureRedirect = failureRedirect || '/login'; 14 | 15 | return passport.authenticate(strategy,{ 16 | successRedirect: successRedirect, 17 | failureRedirect: failureRedirect, 18 | failureFlash: true 19 | }); 20 | }; 21 | 22 | var passportHelper = function(app){ 23 | 24 | app.use(cookieParser(process.env.COOKIE)); 25 | app.use(session({ 26 | secret: process.env.SESSION, 27 | resave: false, 28 | saveUninitialized: true 29 | })); 30 | app.use(passport.initialize()); 31 | app.use(passport.session()); 32 | 33 | /* passport.use(new LocalStrategy( 34 | 35 | function(username,password,done){ 36 | User.login(username,password) 37 | .then(function(user){ 38 | return done(null, user); 39 | }) 40 | .catch(function(err){ 41 | return done(null,false,{message: err}); 42 | }); 43 | } 44 | 45 | ));*/ 46 | 47 | passport.use(new GithubStrategy({ 48 | clientID: process.env.GITHUB_CLIENT_ID, 49 | clientSecret: process.env.GITHUB_CLIENT_SECRET, 50 | callbackURL: 'https://coursebuilder.herokuapp.com/github/callback' 51 | }, 52 | function(accessToken,refreshToken, profile, done){ 53 | if (profile.username === 'Guest'){return;} 54 | new User({username: profile.username}).fetch() 55 | .then(function(user){ 56 | if (!user){ 57 | return new User({username: profile.username, authType: 'github'}).save(); 58 | } else { 59 | return user; 60 | } 61 | }) 62 | .then(function(user){ 63 | if (user.get('authType') !== 'github') done(null,false,{message: 'username exists'}); 64 | else done(null,user); 65 | }); 66 | 67 | })); 68 | 69 | passport.serializeUser(function(user, done){ 70 | done(null, user.id); 71 | }); 72 | 73 | passport.deserializeUser(function(id, done){ 74 | new User({id: id}).fetch() 75 | .then(function(user){ 76 | done(null,user); 77 | }) 78 | .catch(function(err){ 79 | done(null,false,{message: err}); 80 | }); 81 | }); 82 | 83 | // passportHelper.authenticate = authGen(); 84 | 85 | passportHelper.authGithub = authGen('github'); 86 | 87 | }; 88 | 89 | 90 | passportHelper.verifyUser = function(req,res,next,destination){ 91 | destination = destination || '/login'; 92 | if (req.user){ 93 | next(); 94 | } else { 95 | res.redirect(destination); 96 | } 97 | }; 98 | 99 | passportHelper.login = function(req,res,user,destination){ 100 | destination = destination || '/'; 101 | req.login(user, function(err){ 102 | if (err)throw new Error(err); 103 | res.redirect(destination); 104 | }); 105 | }; 106 | 107 | passportHelper.logout = function(req,res,destination){ 108 | destination = destination || '/login'; 109 | req.logout(); 110 | res.redirect(destination); 111 | }; 112 | 113 | module.exports = passportHelper; 114 | -------------------------------------------------------------------------------- /_PRESS-RELEASE.md: -------------------------------------------------------------------------------- 1 | # Project Name # 2 | 3 | 18 | 19 | ## Heading ## 20 | > Name the product in a way the reader (i.e. your target customers) will understand. 21 | 22 | ## Sub-Heading ## 23 | > Describe who the market for the product is and what benefit they get. One sentence only underneath the title. 24 | 25 | ## Summary ## 26 | > Give a summary of the product and the benefit. Assume the reader will not read anything else so make this paragraph good. 27 | 28 | ## Problem ## 29 | > Describe the problem your product solves. 30 | 31 | ## Solution ## 32 | > Describe how your product elegantly solves the problem. 33 | 34 | ## Quote from You ## 35 | > A quote from a spokesperson in your company. 36 | 37 | ## How to Get Started ## 38 | > Describe how easy it is to get started. 39 | 40 | ## Customer Quote ## 41 | > Provide a quote from a hypothetical customer that describes how they experienced the benefit. 42 | 43 | ## Closing and Call to Action ## 44 | > Wrap it up and give pointers where the reader should go next. 45 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var bodyParser = require('body-parser'); 3 | var url = require('url'); 4 | var passportHelper = require('./helpers/passport-helper'); 5 | 6 | var db = require('./db/config'); 7 | var User = require('./db/models/user'); 8 | var Course = require('./db/models/course'); 9 | var Lesson = require('./db/models/lesson'); 10 | var Assignment = require('./db/models/assignment'); 11 | var Courses = require('./db/collections/courses'); 12 | var Lessons = require('./db/collections/lessons'); 13 | var Assignments = require('./db/collections/assignments'); 14 | 15 | var app = express(); 16 | 17 | app.use(bodyParser.json()); 18 | 19 | app.use(bodyParser.urlencoded({ extended: true })); 20 | 21 | passportHelper(app); 22 | 23 | app.get('/github',passportHelper.authGithub); 24 | 25 | app.get('/github/callback',passportHelper.authGithub); 26 | 27 | app.use(express.static(__dirname + '/../client')); 28 | 29 | app.route('/users/:id/courses') 30 | 31 | .get(function(req,res){ 32 | if ((req.user && req.user.attributes.id.toString() === req.params.id) || req.params.id === '1'){ 33 | new User({ 34 | 'id':req.params.id 35 | }).fetch({ 36 | withRelated: ['courses'] 37 | }).then(function(user){ 38 | res.json(user); 39 | }); 40 | } else { 41 | res.status(403).send(); 42 | } 43 | }) 44 | 45 | .post(function(req,res){ 46 | if (req.user && req.user.attributes.id.toString() === req.params.id){ 47 | new Course({ 48 | 'name':req.body.name, 49 | 'user_id':req.params.id, 50 | 'hours':req.body.hours 51 | }).save().then(function(course){ 52 | res.json(course); 53 | }); 54 | } else { 55 | res.status(403).send(); 56 | } 57 | }); 58 | 59 | app.route('/users') 60 | 61 | .get(function(req,res){ 62 | if (req.user){ 63 | new User({ 64 | 'id':req.user.attributes.id 65 | }).fetch().then(function(user){ 66 | res.json(user); 67 | }); 68 | } else { 69 | res.status(403).send(); 70 | } 71 | }); 72 | 73 | app.route('/courses/:courseId/lessons/:id/assignments') 74 | 75 | .get(function(req,res){ 76 | req.user = req.user || {attributes:{id:-1}}; 77 | new Course({ 78 | 'id':req.params.courseId, 79 | 'user_id':req.user.attributes.id 80 | }).fetch().then(function(found){ 81 | if ( found || req.params.courseId === '1'){ 82 | new Lesson({ 83 | 'id':req.params.id, 84 | 'course_id':req.params.courseId 85 | }).fetch({ 86 | withRelated:['assignments'] 87 | }).then(function(lesson){ 88 | res.json(lesson); 89 | }); 90 | } else { 91 | res.status(403).send(); 92 | } 93 | }); 94 | }) 95 | 96 | //TODO: not secure 97 | .post(function(req,res){ 98 | if (req.user){ 99 | new Assignment({ 100 | 'name':req.body.name, 101 | 'lesson_id':req.params.id, 102 | 'hours':req.body.hours, 103 | 'order':req.body.order, 104 | 'type':req.body.type, 105 | 'body':req.body.body 106 | }).save().then(function(assignment){ 107 | res.json(assignment); 108 | }); 109 | } else { 110 | res.status(403).send(); 111 | } 112 | }); 113 | 114 | app.route('/courses/:id/lessons') 115 | 116 | .get(function(req,res){ 117 | req.user = req.user || {attributes:{id:-1}}; 118 | new Course({ 119 | 'id':req.params.id, 120 | 'user_id':req.user.attributes.id 121 | }).fetch().then(function(found){ 122 | if ( found || req.params.id === '1'){ 123 | new Course({ 124 | 'id':req.params.id 125 | }).fetch({ 126 | withRelated:['lessons'] 127 | }).then(function(course){ 128 | res.json(course); 129 | }); 130 | } else { 131 | res.status(403).send(); 132 | } 133 | }); 134 | }) 135 | 136 | .post(function(req,res){ 137 | //TODO: not secure 138 | if (req.user){ 139 | new Lesson({ 140 | 'name':req.body.name, 141 | 'course_id':req.params.id, 142 | 'hours':req.body.hours, 143 | 'order':req.body.order, 144 | }).save().then(function(lesson){ 145 | res.json(lesson); 146 | }); 147 | } else { 148 | res.status(403).send(); 149 | } 150 | }); 151 | 152 | module.exports = app; 153 | -------------------------------------------------------------------------------- /specs/server/ServerSpec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var request = require('request'); 3 | 4 | var db = require('../../server/db/config'); 5 | var User = require('../../server/db/models/user'); 6 | var Course = require('../../server/db/models/course'); 7 | var Lesson = require('../../server/db/models/lesson'); 8 | var Assignment = require('../../server/db/models/assignment'); 9 | var Courses = require('../../server/db/collections/courses'); 10 | var Lessons = require('../../server/db/collections/lessons'); 11 | var Assignments = require('../../server/db/collections/assignments'); 12 | 13 | describe('Guest user and associated courses, lessons, and assignments',function(){ 14 | this.timeout(10000); 15 | 16 | beforeEach(function(done){ 17 | new User({ 18 | 'username': 'Guest' 19 | }).fetch().then(function(found){ 20 | if (found) return found; 21 | else { 22 | return new User({ 23 | 'username': 'Guest', 24 | 'authType': 'guest' 25 | }).save(); 26 | } 27 | }).then(function(user){ 28 | return new Course({ 29 | 'user_id': user.attributes.id 30 | }).fetch().then(function(found){ 31 | if (found) return found; 32 | else { 33 | return new Course({ 34 | 'name': 'Guest Course', 35 | 'hours': 10, 36 | 'user_id':user.attributes.id 37 | }).save(); 38 | } 39 | }).then(function(course){ 40 | return new Lesson({ 41 | 'course_id': course.attributes.id 42 | }).fetchAll().then(function(found){ 43 | if (found.length) return found; 44 | else { 45 | return new Lesson({ 46 | 'name':'Lesson 1', 47 | 'course_id': course.attributes.id, 48 | 'hours': 3, 49 | 'order': 0 50 | }).save().then(function(){ 51 | return new Lesson({ 52 | 'name':'Lesson 2', 53 | 'course_id': course.attributes.id, 54 | 'hours': 4, 55 | 'order': 1 56 | }).save().then(function(){ 57 | return new Lesson({ 58 | 'name':'Lesson 3', 59 | 'course_id': course.attributes.id, 60 | 'hours': 3, 61 | 'order': 2 62 | }).save(); 63 | }); 64 | }); 65 | } 66 | }).then(function(lessons){ 67 | return new Assignment({ 68 | 'lesson_id': 1 69 | }).fetchAll().then(function(found){ 70 | if (found.length) return found; 71 | else { 72 | return new Assignment({ 73 | 'name':'In Class Assignment', 74 | 'lesson_id': 1, 75 | 'hours': 3, 76 | 'order': 0, 77 | 'body' : 'Here is the first lesson' 78 | }).save().then(function(){ 79 | return new Assignment({ 80 | 'name':'Homework Assignment', 81 | 'lesson_id': 1, 82 | 'hours': 0, 83 | 'order': 1, 84 | 'body':'It can have many assignments' 85 | }).save(); 86 | }); 87 | } 88 | }).then(function(){ 89 | done(); 90 | }); 91 | }); 92 | }); 93 | }); 94 | }); 95 | 96 | it('should have a Guest user',function(done){ 97 | new User({ 98 | 'username': 'Guest' 99 | }).fetch().then(function(user){ 100 | expect(user.attributes.username).to.equal('Guest'); 101 | expect(user.attributes.id).to.equal(1); 102 | done(); 103 | }); 104 | }); 105 | 106 | it('should have a Guest Course for the Guest user',function(done){ 107 | new Course({ 108 | 'user_id': 1 109 | }).fetch().then(function(course){ 110 | expect(course.attributes.name).to.equal('Guest Course'); 111 | expect(course.attributes.id).to.equal(1); 112 | expect(course.attributes.hours).to.equal(10); 113 | done(); 114 | }); 115 | }); 116 | 117 | it('should have the lessons for the Guest Course course', function(done){ 118 | new Lesson({ 119 | 'course_id': 1 120 | }).fetchAll().then(function(lessons){ 121 | var lesson1 = lessons.at(0); 122 | var lesson2 = lessons.at(1); 123 | var lesson3 = lessons.at(2); 124 | expect(lesson1.attributes.name).to.equal('Lesson 1'); 125 | expect(lesson1.attributes.id).to.equal(1); 126 | expect(lesson1.attributes.hours).to.equal(3); 127 | expect(lesson1.attributes.order).to.equal(0); 128 | expect(lesson2.attributes.name).to.equal('Lesson 2'); 129 | expect(lesson2.attributes.id).to.equal(2); 130 | expect(lesson2.attributes.hours).to.equal(4); 131 | expect(lesson2.attributes.order).to.equal(1); 132 | expect(lesson3.attributes.name).to.equal('Lesson 3'); 133 | expect(lesson3.attributes.id).to.equal(3); 134 | expect(lesson3.attributes.hours).to.equal(3); 135 | expect(lesson3.attributes.order).to.equal(2); 136 | done(); 137 | }); 138 | }); 139 | 140 | it('should have assignments in the Lesson 1 lesson', function(done){ 141 | new Assignment({ 142 | 'lesson_id': 1 143 | }).fetchAll().then(function(assignments){ 144 | var assignment1 = assignments.at(0); 145 | var assignment2 = assignments.at(1); 146 | expect(assignment1.attributes.name).to.equal('In Class Assignment'); 147 | expect(assignment1.attributes.id).to.equal(1); 148 | expect(assignment1.attributes.hours).to.equal(3); 149 | expect(assignment1.attributes.order).to.equal(0); 150 | expect(assignment1.attributes.type).to.equal('text'); 151 | expect(assignment1.attributes.body).to.equal('Here is the first lesson'); 152 | expect(assignment2.attributes.name).to.equal('Homework Assignment'); 153 | expect(assignment2.attributes.id).to.equal(2); 154 | expect(assignment2.attributes.hours).to.equal(0); 155 | expect(assignment2.attributes.order).to.equal(1); 156 | expect(assignment2.attributes.type).to.equal('text'); 157 | expect(assignment2.attributes.body).to.equal('It can have many assignments'); 158 | done(); 159 | }); 160 | }); 161 | 162 | }); 163 | 164 | describe('should be able to access the Guest User courses', function(){ 165 | 166 | this.timeout(5000); 167 | 168 | it('should respond with courses for the associated user id', function(done){ 169 | request('http://127.0.0.1:8000/users/1/courses', function(error, res, body){ 170 | var user = JSON.parse(body); 171 | expect(user.username).to.equal('Guest'); 172 | expect(user.id).to.equal(1); 173 | var course1 = user.courses[0]; 174 | expect(course1.name).to.equal('Guest Course'); 175 | expect(course1.id).to.equal(1); 176 | expect(course1.hours).to.equal(10); 177 | done(); 178 | }); 179 | }); 180 | 181 | it('should respond with lessons for the associated course', function(done){ 182 | request('http://127.0.0.1:8000/courses/1/lessons', function(error, res, body){ 183 | var course = JSON.parse(body); 184 | expect(course.name).to.equal('Guest Course'); 185 | expect(course.id).to.equal(1); 186 | expect(course.hours).to.equal(10); 187 | var lesson1 = course.lessons[0]; 188 | var lesson2 = course.lessons[1]; 189 | var lesson3 = course.lessons[2]; 190 | expect(lesson1.name).to.equal('Lesson 1'); 191 | expect(lesson1.id).to.equal(1); 192 | expect(lesson1.hours).to.equal(3); 193 | expect(lesson1.order).to.equal(0); 194 | expect(lesson2.name).to.equal('Lesson 2'); 195 | expect(lesson2.id).to.equal(2); 196 | expect(lesson2.hours).to.equal(4); 197 | expect(lesson2.order).to.equal(1); 198 | expect(lesson3.name).to.equal('Lesson 3'); 199 | expect(lesson3.id).to.equal(3); 200 | expect(lesson3.hours).to.equal(3); 201 | expect(lesson3.order).to.equal(2); 202 | done(); 203 | }); 204 | }); 205 | 206 | it('should respond with assignments for an associated lesson',function(done){ 207 | request('http://127.0.0.1:8000/courses/1/lessons/1/assignments', function(error, res, body){ 208 | var lesson = JSON.parse(body); 209 | expect(lesson.name).to.equal('Lesson 1'); 210 | expect(lesson.id).to.equal(1); 211 | expect(lesson.hours).to.equal(3); 212 | expect(lesson.order).to.equal(0); 213 | 214 | var assignment1 = lesson.assignments[0]; 215 | var assignment2 = lesson.assignments[1]; 216 | expect(assignment1.name).to.equal('In Class Assignment'); 217 | expect(assignment1.id).to.equal(1); 218 | expect(assignment1.hours).to.equal(3); 219 | expect(assignment1.order).to.equal(0); 220 | expect(assignment1.type).to.equal('text'); 221 | expect(assignment1.body).to.equal('Here is the first lesson'); 222 | expect(assignment2.name).to.equal('Homework Assignment'); 223 | expect(assignment2.id).to.equal(2); 224 | expect(assignment2.hours).to.equal(0); 225 | expect(assignment2.order).to.equal(1); 226 | expect(assignment2.type).to.equal('text'); 227 | expect(assignment2.body).to.equal('It can have many assignments'); 228 | done(); 229 | }); 230 | }); 231 | 232 | }); 233 | -------------------------------------------------------------------------------- /npm-debug.log: -------------------------------------------------------------------------------- 1 | 0 info it worked if it ends with ok 2 | 1 verbose cli [ '/usr/local/bin/node', 3 | 1 verbose cli '/usr/local/bin/npm', 4 | 1 verbose cli 'install', 5 | 1 verbose cli 'azure-cli', 6 | 1 verbose cli '-g' ] 7 | 2 info using npm@1.4.28 8 | 3 info using node@v0.10.33 9 | 4 verbose node symlink /usr/local/bin/node 10 | 5 verbose cache add [ 'azure-cli', null ] 11 | 6 verbose cache add name=undefined spec="azure-cli" args=["azure-cli",null] 12 | 7 verbose parsed url { protocol: null, 13 | 7 verbose parsed url slashes: null, 14 | 7 verbose parsed url auth: null, 15 | 7 verbose parsed url host: null, 16 | 7 verbose parsed url port: null, 17 | 7 verbose parsed url hostname: null, 18 | 7 verbose parsed url hash: null, 19 | 7 verbose parsed url search: null, 20 | 7 verbose parsed url query: null, 21 | 7 verbose parsed url pathname: 'azure-cli', 22 | 7 verbose parsed url path: 'azure-cli', 23 | 7 verbose parsed url href: 'azure-cli' } 24 | 8 silly lockFile e55ea045-azure-cli azure-cli 25 | 9 verbose lock azure-cli /home/drabinowitz/.npm/e55ea045-azure-cli.lock 26 | 10 silly lockFile e55ea045-azure-cli azure-cli 27 | 11 silly lockFile e55ea045-azure-cli azure-cli 28 | 12 verbose addNamed [ 'azure-cli', '' ] 29 | 13 verbose addNamed [ null, '*' ] 30 | 14 silly lockFile a53364e7-azure-cli azure-cli@ 31 | 15 verbose lock azure-cli@ /home/drabinowitz/.npm/a53364e7-azure-cli.lock 32 | 16 silly addNameRange { name: 'azure-cli', range: '*', hasData: false } 33 | 17 verbose request where is /azure-cli 34 | 18 verbose request registry https://registry.npmjs.org/ 35 | 19 verbose request id 786d5366cc4fb595 36 | 20 verbose url raw /azure-cli 37 | 21 verbose url resolving [ 'https://registry.npmjs.org/', './azure-cli' ] 38 | 22 verbose url resolved https://registry.npmjs.org/azure-cli 39 | 23 verbose request where is https://registry.npmjs.org/azure-cli 40 | 24 info trying registry request attempt 1 at 21:59:37 41 | 25 http GET https://registry.npmjs.org/azure-cli 42 | 26 http 200 https://registry.npmjs.org/azure-cli 43 | 27 silly registry.get cb [ 200, 44 | 27 silly registry.get { date: 'Wed, 03 Dec 2014 02:59:38 GMT', 45 | 27 silly registry.get server: 'CouchDB/1.5.0 (Erlang OTP/R16B03)', 46 | 27 silly registry.get etag: '"BIM4NOAI7WMPSE2LQ954ZF28P"', 47 | 27 silly registry.get 'content-type': 'application/json', 48 | 27 silly registry.get 'cache-control': 'max-age=60', 49 | 27 silly registry.get 'content-length': '89447', 50 | 27 silly registry.get 'accept-ranges': 'bytes', 51 | 27 silly registry.get via: '1.1 varnish', 52 | 27 silly registry.get age: '0', 53 | 27 silly registry.get 'x-served-by': 'cache-iad2141-IAD', 54 | 27 silly registry.get 'x-cache': 'MISS', 55 | 27 silly registry.get 'x-cache-hits': '0', 56 | 27 silly registry.get 'x-timer': 'S1417575577.971237,VS0,VE54', 57 | 27 silly registry.get vary: 'Accept', 58 | 27 silly registry.get 'keep-alive': 'timeout=10, max=50', 59 | 27 silly registry.get connection: 'Keep-Alive' } ] 60 | 28 silly addNameRange number 2 { name: 'azure-cli', range: '*', hasData: true } 61 | 29 silly addNameRange versions [ 'azure-cli', 62 | 29 silly addNameRange [ '0.6.8', 63 | 29 silly addNameRange '0.6.9', 64 | 29 silly addNameRange '0.6.10', 65 | 29 silly addNameRange '0.6.11', 66 | 29 silly addNameRange '0.6.14', 67 | 29 silly addNameRange '0.6.15', 68 | 29 silly addNameRange '0.6.16', 69 | 29 silly addNameRange '0.6.17', 70 | 29 silly addNameRange '0.6.18', 71 | 29 silly addNameRange '0.7.0', 72 | 29 silly addNameRange '0.7.1', 73 | 29 silly addNameRange '0.7.2', 74 | 29 silly addNameRange '0.7.3', 75 | 29 silly addNameRange '0.7.4', 76 | 29 silly addNameRange '0.7.5', 77 | 29 silly addNameRange '0.8.0', 78 | 29 silly addNameRange '0.8.1', 79 | 29 silly addNameRange '0.8.2', 80 | 29 silly addNameRange '0.8.3', 81 | 29 silly addNameRange '0.8.4', 82 | 29 silly addNameRange '0.8.5', 83 | 29 silly addNameRange '0.8.6', 84 | 29 silly addNameRange '0.8.7', 85 | 29 silly addNameRange '0.8.8', 86 | 29 silly addNameRange '0.8.9', 87 | 29 silly addNameRange '0.8.10', 88 | 29 silly addNameRange '0.8.11', 89 | 29 silly addNameRange '0.8.12' ] ] 90 | 30 verbose addNamed [ 'azure-cli', '0.8.12' ] 91 | 31 verbose addNamed [ '0.8.12', '0.8.12' ] 92 | 32 silly lockFile ddf13109-azure-cli-0-8-12 azure-cli@0.8.12 93 | 33 verbose lock azure-cli@0.8.12 /home/drabinowitz/.npm/ddf13109-azure-cli-0-8-12.lock 94 | 34 silly lockFile 982618b5-g-azure-cli-azure-cli-0-8-12-tgz https://registry.npmjs.org/azure-cli/-/azure-cli-0.8.12.tgz 95 | 35 verbose lock https://registry.npmjs.org/azure-cli/-/azure-cli-0.8.12.tgz /home/drabinowitz/.npm/982618b5-g-azure-cli-azure-cli-0-8-12-tgz.lock 96 | 36 verbose addRemoteTarball [ 'https://registry.npmjs.org/azure-cli/-/azure-cli-0.8.12.tgz', 97 | 36 verbose addRemoteTarball 'be292bb1230dcf3053afacfd02c2c7595a243e9a' ] 98 | 37 info retry fetch attempt 1 at 21:59:38 99 | 38 verbose fetch to= /tmp/npm-30366-8Qky0eWl/registry.npmjs.org/azure-cli/-/azure-cli-0.8.12.tgz 100 | 39 http GET https://registry.npmjs.org/azure-cli/-/azure-cli-0.8.12.tgz 101 | 40 http 200 https://registry.npmjs.org/azure-cli/-/azure-cli-0.8.12.tgz 102 | 41 silly lockFile 982618b5-g-azure-cli-azure-cli-0-8-12-tgz https://registry.npmjs.org/azure-cli/-/azure-cli-0.8.12.tgz 103 | 42 silly lockFile 982618b5-g-azure-cli-azure-cli-0-8-12-tgz https://registry.npmjs.org/azure-cli/-/azure-cli-0.8.12.tgz 104 | 43 silly lockFile ddf13109-azure-cli-0-8-12 azure-cli@0.8.12 105 | 44 silly lockFile ddf13109-azure-cli-0-8-12 azure-cli@0.8.12 106 | 45 silly lockFile a53364e7-azure-cli azure-cli@ 107 | 46 silly lockFile a53364e7-azure-cli azure-cli@ 108 | 47 silly resolved [ { name: 'azure-cli', 109 | 47 silly resolved author: { name: 'Microsoft Corporation' }, 110 | 47 silly resolved contributors: 111 | 47 silly resolved [ [Object], 112 | 47 silly resolved [Object], 113 | 47 silly resolved [Object], 114 | 47 silly resolved [Object], 115 | 47 silly resolved [Object], 116 | 47 silly resolved [Object], 117 | 47 silly resolved [Object] ], 118 | 47 silly resolved version: '0.8.12', 119 | 47 silly resolved description: 'Microsoft Azure Cross Platform Command Line tool', 120 | 47 silly resolved tags: [ 'azure', 'cli' ], 121 | 47 silly resolved keywords: [ 'node', 'azure', 'cli', 'cloud hosting', 'deployment' ], 122 | 47 silly resolved main: './lib/cli.js', 123 | 47 silly resolved preferGlobal: 'true', 124 | 47 silly resolved engines: { node: '>= 0.8.26' }, 125 | 47 silly resolved licenses: [ [Object] ], 126 | 47 silly resolved dependencies: 127 | 47 silly resolved { 'adal-node': '0.1.7', 128 | 47 silly resolved async: '0.2.7', 129 | 47 silly resolved azure: '0.10.1', 130 | 47 silly resolved 'azure-common': '0.9.9', 131 | 47 silly resolved 'azure-gallery': '2.0.0-pre.11', 132 | 47 silly resolved 'azure-mgmt-authorization': '0.9.0-pre.2', 133 | 47 silly resolved 'azure-mgmt-resource': '2.0.0-pre.12', 134 | 47 silly resolved 'azure-storage': '0.4.0', 135 | 47 silly resolved 'azure-storage-legacy': '0.9.11', 136 | 47 silly resolved 'azure-extra': '0.1.3', 137 | 47 silly resolved mime: '~1.2.4', 138 | 47 silly resolved colors: '0.x.x', 139 | 47 silly resolved commander: '1.0.4', 140 | 47 silly resolved 'easy-table': '0.0.1', 141 | 47 silly resolved 'event-stream': '3.1.5', 142 | 47 silly resolved eyes: '0.x.x', 143 | 47 silly resolved github: '0.1.6', 144 | 47 silly resolved kuduscript: '0.1.11', 145 | 47 silly resolved moment: '2.6.0', 146 | 47 silly resolved 'node-uuid': '1.2.0', 147 | 47 silly resolved omelette: '0.1.0', 148 | 47 silly resolved request: '2.27.0', 149 | 47 silly resolved streamline: '0.4.5', 150 | 47 silly resolved through: '2.3.4', 151 | 47 silly resolved tunnel: '0.0.2', 152 | 47 silly resolved underscore: '1.4.x', 153 | 47 silly resolved validator: '~3.1.0', 154 | 47 silly resolved winston: '0.6.x', 155 | 47 silly resolved wordwrap: '0.0.2', 156 | 47 silly resolved xml2js: '0.1.x', 157 | 47 silly resolved xmlbuilder: '0.4.x', 158 | 47 silly resolved 'readable-stream': '~1.0.0', 159 | 47 silly resolved 'openssl-wrapper': '0.2.1', 160 | 47 silly resolved 'caller-id': '0.1.x' }, 161 | 47 silly resolved devDependencies: 162 | 47 silly resolved { mocha: '1.16.0', 163 | 47 silly resolved jshint: '>= 2.1.4', 164 | 47 silly resolved sinon: '*', 165 | 47 silly resolved should: '3.3.2', 166 | 47 silly resolved nock: '0.16', 167 | 47 silly resolved 'winston-memory': '*', 168 | 47 silly resolved cucumber: '~0.3.0' }, 169 | 47 silly resolved homepage: 'https://github.com/WindowsAzure/azure-sdk-tools-xplat', 170 | 47 silly resolved repository: 171 | 47 silly resolved { type: 'git', 172 | 47 silly resolved url: 'git@github.com:WindowsAzure/azure-sdk-tools-xplat.git' }, 173 | 47 silly resolved bugs: { url: 'https://github.com/WindowsAzure/azure-sdk-tools-xplat/issues' }, 174 | 47 silly resolved scripts: 175 | 47 silly resolved { test: 'npm -s run-script jshint && npm -s run-script unit && npm -s run-script unit-arm', 176 | 47 silly resolved unit: 'node scripts/unit.js testlist.txt', 177 | 47 silly resolved 'unit-mc': 'node scripts/unit.js --mc testlistmc.txt', 178 | 47 silly resolved 'unit-arm': 'node scripts/unit.js testlistarm.txt', 179 | 47 silly resolved jshint: 'jshint lib --jslint-reporter --extra-ext ._js', 180 | 47 silly resolved preci: 'jshint lib --reporter=checkstyle --extra-ext ._js > checkstyle-result.xml', 181 | 47 silly resolved ci: 'node scripts/unit.js testlist.txt -xunit', 182 | 47 silly resolved preacceptance: 'node scripts/cuke-environment.js setup', 183 | 47 silly resolved acceptance: 'node node_modules/cucumber/bin/cucumber.js', 184 | 47 silly resolved postacceptance: 'node scripts/cuke-environment.js teardown', 185 | 47 silly resolved 'extract-labels': 'node scripts/extract-labels' }, 186 | 47 silly resolved bin: { azure: './bin/azure' }, 187 | 47 silly resolved gitHead: '1c5e85bcf602f891dffdce1a74bb4a0fd93e6be5', 188 | 47 silly resolved _id: 'azure-cli@0.8.12', 189 | 47 silly resolved _shasum: 'be292bb1230dcf3053afacfd02c2c7595a243e9a', 190 | 47 silly resolved _from: 'azure-cli@', 191 | 47 silly resolved _npmVersion: '2.1.8', 192 | 47 silly resolved _nodeVersion: '0.10.29', 193 | 47 silly resolved _npmUser: { name: 'windowsazure', email: 'azuresdk@outlook.com' }, 194 | 47 silly resolved maintainers: [ [Object] ], 195 | 47 silly resolved dist: 196 | 47 silly resolved { shasum: 'be292bb1230dcf3053afacfd02c2c7595a243e9a', 197 | 47 silly resolved tarball: 'http://registry.npmjs.org/azure-cli/-/azure-cli-0.8.12.tgz' }, 198 | 47 silly resolved directories: {}, 199 | 47 silly resolved _resolved: 'https://registry.npmjs.org/azure-cli/-/azure-cli-0.8.12.tgz' } ] 200 | 48 info install azure-cli@0.8.12 into /usr/local/lib 201 | 49 info installOne azure-cli@0.8.12 202 | 50 verbose lib/node_modules/azure-cli unbuild 203 | 51 verbose tar unpack /home/drabinowitz/.npm/azure-cli/0.8.12/package.tgz 204 | 52 silly lockFile 5e0be1a0-local-lib-node-modules-azure-cli tar:///usr/local/lib/node_modules/azure-cli 205 | 53 verbose lock tar:///usr/local/lib/node_modules/azure-cli /home/drabinowitz/.npm/5e0be1a0-local-lib-node-modules-azure-cli.lock 206 | 54 silly lockFile 0daf9d42-npm-azure-cli-0-8-12-package-tgz tar:///home/drabinowitz/.npm/azure-cli/0.8.12/package.tgz 207 | 55 verbose lock tar:///home/drabinowitz/.npm/azure-cli/0.8.12/package.tgz /home/drabinowitz/.npm/0daf9d42-npm-azure-cli-0-8-12-package-tgz.lock 208 | 56 silly gunzTarPerm modes [ '775', '664' ] 209 | 57 error Error: EACCES, mkdir '/usr/local/lib/node_modules/azure-cli' 210 | 57 error { [Error: EACCES, mkdir '/usr/local/lib/node_modules/azure-cli'] 211 | 57 error errno: 3, 212 | 57 error code: 'EACCES', 213 | 57 error path: '/usr/local/lib/node_modules/azure-cli', 214 | 57 error fstream_type: 'Directory', 215 | 57 error fstream_path: '/usr/local/lib/node_modules/azure-cli', 216 | 57 error fstream_class: 'DirWriter', 217 | 57 error fstream_stack: 218 | 57 error [ '/usr/local/lib/node_modules/npm/node_modules/fstream/lib/dir-writer.js:36:23', 219 | 57 error '/usr/local/lib/node_modules/npm/node_modules/mkdirp/index.js:46:53', 220 | 57 error 'Object.oncomplete (fs.js:107:15)' ] } 221 | 58 error Please try running this command again as root/Administrator. 222 | 59 error System Linux 3.13.0-40-generic 223 | 60 error command "/usr/local/bin/node" "/usr/local/bin/npm" "install" "azure-cli" "-g" 224 | 61 error cwd /home/drabinowitz/Documents/github/2014-10-solo 225 | 62 error node -v v0.10.33 226 | 63 error npm -v 1.4.28 227 | 64 error path /usr/local/lib/node_modules/azure-cli 228 | 65 error fstream_path /usr/local/lib/node_modules/azure-cli 229 | 66 error fstream_type Directory 230 | 67 error fstream_class DirWriter 231 | 68 error code EACCES 232 | 69 error errno 3 233 | 70 error stack Error: EACCES, mkdir '/usr/local/lib/node_modules/azure-cli' 234 | 71 error fstream_stack /usr/local/lib/node_modules/npm/node_modules/fstream/lib/dir-writer.js:36:23 235 | 71 error fstream_stack /usr/local/lib/node_modules/npm/node_modules/mkdirp/index.js:46:53 236 | 71 error fstream_stack Object.oncomplete (fs.js:107:15) 237 | 72 verbose exit [ 3, true ] 238 | --------------------------------------------------------------------------------