├── 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 |
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 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
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 |
8 |
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 |
8 |
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 |
--------------------------------------------------------------------------------