├── .buildignore ├── .gitattributes ├── client ├── app │ ├── account │ │ ├── login │ │ │ ├── login.css │ │ │ ├── login.controller.js │ │ │ └── login.html │ │ ├── signup │ │ │ ├── signup.css │ │ │ ├── signup.controller.js │ │ │ └── signup.html │ │ ├── account.js │ │ └── settings │ │ │ ├── settings.controller.js │ │ │ └── settings.html │ ├── contributionRecd │ │ ├── contributionRecd.css │ │ ├── contributionRecd.controller.js │ │ ├── contributionRecd.js │ │ └── contributionRecd.html │ ├── contribution │ │ ├── contribution.css │ │ ├── contribution.js │ │ ├── contribution.html │ │ └── contribution.controller.js │ ├── main │ │ ├── main.controller.js │ │ ├── main.js │ │ ├── main.css │ │ ├── main.controller.spec.js │ │ └── main.html │ ├── submission │ │ ├── submission.css │ │ ├── submission.js │ │ ├── submission.controller.js │ │ └── submission.html │ ├── inbox │ │ ├── inbox.js │ │ ├── inbox.css │ │ ├── inbox.html │ │ └── inbox.controller.js │ ├── dashboard │ │ ├── dashboard.js │ │ ├── dashboard.css │ │ ├── dashboard.controller.js │ │ └── dashboard.html │ ├── allHelpRequest │ │ ├── allHelpRequest.js │ │ ├── allHelpRequest.css │ │ ├── allHelpRequest.controller.js │ │ └── allHelpRequest.html │ ├── help-request │ │ ├── help-request.js │ │ ├── help-request.css │ │ ├── help-request.html │ │ └── help-request.controller.js │ ├── app.css │ └── app.js ├── robots.txt ├── assets │ └── images │ │ ├── letterA.jpg │ │ └── calligraphy.png ├── components │ ├── auth │ │ ├── user.service.js │ │ ├── helpRequest.service.js │ │ ├── contribution.service.js │ │ └── auth.service.js │ ├── modal │ │ ├── modal.css │ │ ├── modal.html │ │ └── modal.service.js │ └── navbar │ │ ├── navbar.controller.js │ │ └── navbar.html ├── .jshintrc ├── favicon.ico ├── index.html └── .htaccess ├── server ├── config │ ├── secret.example │ └── dbConfig.example.js ├── .jshintrc-spec ├── .jshintrc ├── models │ ├── project.js │ ├── projectupvotes.js │ ├── contributionupvotes.js │ ├── contributions.js │ ├── projectcomments.js │ ├── contributioncomments.js │ ├── user.js │ ├── associations.js │ └── helpers.js └── app.js ├── .bowerrc ├── .floo ├── .flooignore ├── .travis.yml ├── .gitignore ├── e2e └── main │ ├── main.po.js │ └── main.spec.js ├── .editorconfig ├── bower.json ├── LICENSE ├── .yo-rc.json ├── protractor.conf.js ├── README.md ├── karma.conf.js ├── package.json ├── PRESS-RELEASE.md ├── CONTRIBUTING.md ├── STYLE-GUIDE.md ├── Gruntfile.js └── commit_history.md /.buildignore: -------------------------------------------------------------------------------- 1 | *.coffee -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /client/app/account/login/login.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /client/app/contributionRecd/contributionRecd.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/config/secret.example: -------------------------------------------------------------------------------- 1 | SECRET GOES HERE -------------------------------------------------------------------------------- /client/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "client/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.floo: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://floobits.com/HackReactor/familythief" 3 | } -------------------------------------------------------------------------------- /.flooignore: -------------------------------------------------------------------------------- 1 | #* 2 | *.o 3 | *.pyc 4 | *~ 5 | extern/ 6 | node_modules/ 7 | tmp 8 | vendor/ -------------------------------------------------------------------------------- /client/app/account/signup/signup.css: -------------------------------------------------------------------------------- 1 | .list-group-item { 2 | font-size: 18px; 3 | border: none; 4 | } -------------------------------------------------------------------------------- /client/assets/images/letterA.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craigsmith24/family-thief/HEAD/client/assets/images/letterA.jpg -------------------------------------------------------------------------------- /client/assets/images/calligraphy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/craigsmith24/family-thief/HEAD/client/assets/images/calligraphy.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | - '0.11' 5 | before_script: 6 | - npm install -g bower grunt-cli 7 | - bower install 8 | services: mongodb -------------------------------------------------------------------------------- /client/app/contribution/contribution.css: -------------------------------------------------------------------------------- 1 | p#helpRequestText, p#contributionText { 2 | border: 3px solid green; 3 | border-radius: 10px; 4 | padding: 10px 10px; 5 | } -------------------------------------------------------------------------------- /client/app/main/main.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .controller('MainCtrl', function ($scope, $http) { 5 | 6 | 7 | }); 8 | -------------------------------------------------------------------------------- /client/app/submission/submission.css: -------------------------------------------------------------------------------- 1 | .submission-form { 2 | margin-bottom: 40px; 3 | } 4 | 5 | .success-color { 6 | color: green; 7 | } 8 | 9 | .error-color { 10 | color: red; 11 | } -------------------------------------------------------------------------------- /server/.jshintrc-spec: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ".jshintrc", 3 | "globals": { 4 | "describe": true, 5 | "it": true, 6 | "before": true, 7 | "beforeEach": true, 8 | "after": true, 9 | "afterEach": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | public 3 | .tmp 4 | .idea 5 | .DS_Store 6 | client/bower_components 7 | dist 8 | /server/config/local.env.js 9 | 10 | # Ingore actual db config 11 | /server/config/dbConfig.js 12 | 13 | # Ignore actual authentication secret 14 | /server/config/secret 15 | -------------------------------------------------------------------------------- /client/app/main/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .config(function ($stateProvider) { 5 | $stateProvider 6 | .state('main', { 7 | url: '/', 8 | templateUrl: 'app/main/main.html', 9 | controller: 'MainCtrl' 10 | }); 11 | }); -------------------------------------------------------------------------------- /server/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "eqeqeq": true, 6 | "immed": true, 7 | "latedef": "nofunc", 8 | "newcap": true, 9 | "noarg": true, 10 | "regexp": true, 11 | "undef": true, 12 | "smarttabs": true, 13 | "asi": true, 14 | "debug": true 15 | } 16 | -------------------------------------------------------------------------------- /client/app/inbox/inbox.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .config(function ($stateProvider) { 5 | $stateProvider 6 | .state('inbox', { 7 | url: '/inbox', 8 | templateUrl: 'app/inbox/inbox.html', 9 | controller: 'InboxCtrl', 10 | authenticate: true 11 | }); 12 | }); -------------------------------------------------------------------------------- /client/app/dashboard/dashboard.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .config(function ($stateProvider) { 5 | $stateProvider 6 | .state('dashboard', { 7 | url: '/dashboard', 8 | templateUrl: 'app/dashboard/dashboard.html', 9 | controller: 'DashboardCtrl', 10 | authenticate: true 11 | }); 12 | }); -------------------------------------------------------------------------------- /client/app/contribution/contribution.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .config(function ($stateProvider) { 5 | $stateProvider 6 | .state('contribution', { 7 | url: '/contribution', 8 | templateUrl: 'app/contribution/contribution.html', 9 | controller: 'ContributionCtrl', 10 | authenticate: true 11 | }); 12 | }); -------------------------------------------------------------------------------- /client/app/allHelpRequest/allHelpRequest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .config(function ($stateProvider) { 5 | $stateProvider 6 | .state('allHelpRequest', { 7 | url: '/allHelpRequest', 8 | templateUrl: 'app/allHelpRequest/allHelpRequest.html', 9 | controller: 'AllHelpRequestCtrl', 10 | authenticate: true 11 | }); 12 | }); -------------------------------------------------------------------------------- /server/models/project.js: -------------------------------------------------------------------------------- 1 | var db = require('../config/dbConfig.js'); 2 | var Sequelize = require('sequelize'); 3 | var User = require('./user.js'); 4 | 5 | var Project = db.define('Project', 6 | { 7 | title: Sequelize.STRING, 8 | summary: Sequelize.STRING, 9 | text: Sequelize.TEXT, 10 | user_id: Sequelize.INTEGER 11 | } 12 | ); 13 | 14 | db.sync(); 15 | 16 | module.exports = Project; 17 | -------------------------------------------------------------------------------- /client/app/contributionRecd/contributionRecd.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .controller('ContributionRecdCtrl', function ($scope, $http, User, HelpRequest) { 5 | $scope.getCurrentUser = User.getCurrentUser; 6 | 7 | $scope.helpRequest = {}; 8 | 9 | $scope.respondToHelpRequest = function() { 10 | 11 | } 12 | 13 | 14 | 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /client/app/submission/submission.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //add submission state to the app 4 | 5 | angular.module('familyThiefApp') 6 | .config(function ($stateProvider) { 7 | $stateProvider 8 | .state('submission', { 9 | url: '/submission', 10 | templateUrl: 'app/submission/submission.html', 11 | controller: 'SubmissionCtrl', 12 | authenticate: true 13 | }); 14 | }); -------------------------------------------------------------------------------- /server/models/projectupvotes.js: -------------------------------------------------------------------------------- 1 | var db = require('../config/dbConfig.js'); 2 | var Sequelize = require('sequelize'); 3 | var User = require('./user.js'); 4 | var Project = require('./project.js'); 5 | 6 | var ProjectUpvote = db.define('ProjectUpvote', 7 | { 8 | upvoter: Sequelize.INTEGER, 9 | projectupvoted: Sequelize.INTEGER 10 | 11 | } 12 | ); 13 | 14 | db.sync(); 15 | 16 | module.exports = ProjectUpvote; 17 | -------------------------------------------------------------------------------- /client/app/help-request/help-request.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //add help-request state to the app 4 | 5 | angular.module('familyThiefApp') 6 | .config(function ($stateProvider) { 7 | $stateProvider 8 | .state('help-request', { 9 | url: '/help-request', 10 | templateUrl: 'app/help-request/help-request.html', 11 | controller: 'HelpRequestCtrl', 12 | authenticate: true 13 | }); 14 | }); -------------------------------------------------------------------------------- /client/app/allHelpRequest/allHelpRequest.css: -------------------------------------------------------------------------------- 1 | .search-list-item { 2 | padding: 5px 5px; 3 | margin-bottom: 10px; 4 | height: 100px; 5 | overflow: hidden; 6 | border: 3px solid #47a447; 7 | border-radius: 10px; 8 | width: 70%; 9 | margin: 20px auto; 10 | } 11 | 12 | .search-list-item:hover { 13 | cursor: pointer; 14 | background-color: #448BCA; 15 | } 16 | 17 | a.search-item { 18 | color: black; 19 | display: block; 20 | } -------------------------------------------------------------------------------- /server/models/contributionupvotes.js: -------------------------------------------------------------------------------- 1 | var db = require('../config/dbConfig.js'); 2 | var Sequelize = require('sequelize'); 3 | var User = require('./user.js'); 4 | var Contribution = require('./contributions.js'); 5 | 6 | var ContributionUpvote = db.define('ContributionUpvote', 7 | { 8 | upvoter: Sequelize.INTEGER, 9 | contributionupvoted: Sequelize.INTEGER 10 | 11 | } 12 | ); 13 | 14 | db.sync(); 15 | 16 | module.exports = ContributionUpvote; 17 | -------------------------------------------------------------------------------- /client/app/contributionRecd/contributionRecd.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //add contributionRecd state to the app 4 | 5 | angular.module('familyThiefApp') 6 | .config(function ($stateProvider) { 7 | $stateProvider 8 | .state('contributionRecd', { 9 | url: '/contributionRecd', 10 | templateUrl: 'app/contributionRecd/contributionRecd.html', 11 | controller: 'ContributionRecdCtrl', 12 | authenticate: false 13 | }); 14 | }); -------------------------------------------------------------------------------- /client/app/inbox/inbox.css: -------------------------------------------------------------------------------- 1 | .mailbox-list-item { 2 | padding: 5px 5px; 3 | margin-bottom: 10px; 4 | height: 100px; 5 | overflow: hidden; 6 | border: 3px solid #47a447; 7 | border-radius: 10px; 8 | width: 70%; 9 | margin: 20px auto; 10 | } 11 | 12 | .mailbox-list-item:hover { 13 | cursor: pointer; 14 | background-color: #448BCA; 15 | } 16 | 17 | a.mailbox-item { 18 | color: black; 19 | display: block; 20 | } 21 | 22 | .unseen { 23 | border-color: red; 24 | } -------------------------------------------------------------------------------- /e2e/main/main.po.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file uses the Page Object pattern to define the main page for tests 3 | * https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ 4 | */ 5 | 6 | 'use strict'; 7 | 8 | var MainPage = function() { 9 | this.heroEl = element(by.css('.hero-unit')); 10 | this.h1El = this.heroEl.element(by.css('h1')); 11 | this.imgEl = this.heroEl.element(by.css('img')); 12 | }; 13 | 14 | module.exports = new MainPage(); 15 | 16 | -------------------------------------------------------------------------------- /server/models/contributions.js: -------------------------------------------------------------------------------- 1 | var db = require('../config/dbConfig.js'); 2 | var Sequelize = require('sequelize'); 3 | var User = require('./user.js'); 4 | var Project = require('./project.js'); 5 | 6 | var Contribution = db.define('Contribution', 7 | { 8 | contributionText: Sequelize.TEXT, 9 | unseenHelp: Sequelize.BOOLEAN, 10 | contributor: Sequelize.INTEGER, 11 | project: Sequelize.INTEGER 12 | 13 | } 14 | ); 15 | 16 | db.sync(); 17 | 18 | module.exports = Contribution; 19 | -------------------------------------------------------------------------------- /e2e/main/main.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Main View', function() { 4 | var page; 5 | 6 | beforeEach(function() { 7 | browser.get('/'); 8 | page = require('./main.po'); 9 | }); 10 | 11 | it('should include jumbotron with correct data', function() { 12 | expect(page.h1El.getText()).toBe('\'Allo, \'Allo!'); 13 | expect(page.imgEl.getAttribute('src')).toMatch(/assets\/images\/yeoman.png$/); 14 | expect(page.imgEl.getAttribute('alt')).toBe('I\'m Yeoman'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /server/models/projectcomments.js: -------------------------------------------------------------------------------- 1 | var db = require('../config/dbConfig.js'); 2 | var Sequelize = require('sequelize'); 3 | var User = require('./user.js'); 4 | var Project = require('./project.js'); 5 | 6 | var ProjectComment = db.define('ProjectComment', 7 | { 8 | comment: Sequelize.STRING, 9 | unseenComment: Sequelize.BOOLEAN, 10 | commenter: Sequelize.INTEGER, 11 | projectCommented: Sequelize.INTEGER 12 | 13 | } 14 | ); 15 | 16 | db.sync(); 17 | 18 | module.exports = ProjectComment; 19 | 20 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /client/components/auth/user.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .factory('User', function ($resource) { 5 | return $resource('/api/users/:id/:controller', { 6 | id: '@_id' 7 | }, 8 | { 9 | changePassword: { 10 | method: 'PUT', 11 | params: { 12 | controller:'password' 13 | } 14 | }, 15 | get: { 16 | method: 'GET', 17 | params: { 18 | id:'me' 19 | } 20 | } 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /server/config/dbConfig.example.js: -------------------------------------------------------------------------------- 1 | var Sequelize = require('sequelize'); 2 | 3 | // Here you replace someUsername and somePassword with your prefered mysql 4 | // username and password. If you would like to use something other than mysql, 5 | // go for it. Check out Sequelize's docs for more info: 6 | // http://sequelize.readthedocs.org/en/latest/ 7 | 8 | var sequelize = new Sequelize('websteamCMD', 'someUsername', 'somePassword', { 9 | host: 'localhost', 10 | dialect: 'mysql' 11 | }); 12 | 13 | module.exports = sequelize; 14 | -------------------------------------------------------------------------------- /server/models/contributioncomments.js: -------------------------------------------------------------------------------- 1 | var db = require('../config/dbConfig.js'); 2 | var Sequelize = require('sequelize'); 3 | var User = require('./user.js'); 4 | var Contribution = require('./contributions.js'); 5 | 6 | var ContributionComment = db.define('ContributionComment', 7 | { 8 | comment: Sequelize.STRING, 9 | unseenComment: Sequelize.BOOLEAN, 10 | commenter: Sequelize.INTEGER, 11 | contributionCommented: Sequelize.INTEGER 12 | 13 | } 14 | ); 15 | 16 | db.sync(); 17 | 18 | module.exports = ContributionComment; 19 | -------------------------------------------------------------------------------- /client/components/auth/helpRequest.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // this service provides an abstracted interface for working with helpRequests that we can use in any controller 4 | 5 | angular.module('familyThiefApp') 6 | .factory('HelpRequest', function ($resource) { 7 | var currentHelpRequest; 8 | return $resource('/api/helpRequests/:id/:controller', { 9 | id: '@id' 10 | }, 11 | { 12 | upvote: { 13 | method: 'POST', 14 | params: { 15 | id: 'votes' 16 | } 17 | } 18 | }); 19 | }); -------------------------------------------------------------------------------- /client/app/help-request/help-request.css: -------------------------------------------------------------------------------- 1 | .contrib-list-item { 2 | padding: 5px 5px; 3 | margin-bottom: 10px; 4 | height: 100px; 5 | overflow: hidden; 6 | border: 3px solid #47a447; 7 | border-radius: 10px; 8 | } 9 | 10 | a.list-item { 11 | color: black; 12 | display: block; 13 | } 14 | 15 | #hrSummary, #hrText { 16 | border: 3px solid #47a447; 17 | border-radius: 10px; 18 | padding: 10px 10px; 19 | } 20 | 21 | .contrib-list-item:hover { 22 | cursor: pointer; 23 | background-color: #448BCA; 24 | } 25 | 26 | .contrib-list-item .title { 27 | font-size: 18px; 28 | } -------------------------------------------------------------------------------- /client/components/modal/modal.css: -------------------------------------------------------------------------------- 1 | .modal-primary .modal-header, 2 | .modal-info .modal-header, 3 | .modal-success .modal-header, 4 | .modal-warning .modal-header, 5 | .modal-danger .modal-header { 6 | color: #fff; 7 | border-radius: 5px 5px 0 0; 8 | } 9 | .modal-primary .modal-header { 10 | background: #428bca; 11 | } 12 | .modal-info .modal-header { 13 | background: #5bc0de; 14 | } 15 | .modal-success .modal-header { 16 | background: #5cb85c; 17 | } 18 | .modal-warning .modal-header { 19 | background: #f0ad4e; 20 | } 21 | .modal-danger .modal-header { 22 | background: #d9534f; 23 | } -------------------------------------------------------------------------------- /client/components/modal/modal.html: -------------------------------------------------------------------------------- 1 | 5 | 9 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "family-thief", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": ">=1.2.*", 6 | "json3": "~3.3.1", 7 | "es5-shim": "~3.0.1", 8 | "jquery": "~1.11.0", 9 | "bootstrap": "~3.1.1", 10 | "angular-resource": ">=1.2.*", 11 | "angular-cookies": ">=1.2.*", 12 | "angular-sanitize": ">=1.2.*", 13 | "angular-bootstrap": "~0.11.0", 14 | "font-awesome": ">=4.1.0", 15 | "lodash": "~2.4.1", 16 | "angular-ui-router": "~0.2.10" 17 | }, 18 | "devDependencies": { 19 | "angular-mocks": ">=1.2.*", 20 | "angular-scenario": ">=1.2.*" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /client/components/auth/contribution.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // this service provides an abstracted interface for working with contributions that we can use in any controller 4 | 5 | angular.module('familyThiefApp') 6 | .factory('Contribution', function ($resource) { 7 | return $resource('/api/contributions/:id', { 8 | id: '@id' 9 | }, 10 | { 11 | upvote: { 12 | method: 'POST', 13 | params: { 14 | id: 'votes' 15 | } 16 | }, 17 | addComment: { 18 | method: 'POST', 19 | params: { 20 | id: 'comments' 21 | } 22 | } 23 | }); 24 | }); -------------------------------------------------------------------------------- /client/components/navbar/navbar.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .controller('NavbarCtrl', function ($scope, $location, Auth) { 5 | $scope.menu = [{ 6 | 'title': 'Home', 7 | 'link': '/' 8 | }]; 9 | 10 | $scope.isCollapsed = true; 11 | $scope.isLoggedIn = Auth.isLoggedIn; 12 | $scope.isAdmin = Auth.isAdmin; 13 | $scope.getCurrentUser = Auth.getCurrentUser; 14 | 15 | $scope.logout = function() { 16 | Auth.logout(); 17 | $location.path('/login'); 18 | }; 19 | 20 | $scope.isActive = function(route) { 21 | return route === $location.path(); 22 | }; 23 | }); -------------------------------------------------------------------------------- /client/app/main/main.css: -------------------------------------------------------------------------------- 1 | .thing-form { 2 | margin: 20px 0; 3 | } 4 | 5 | #banner { 6 | border-bottom: none; 7 | margin-top: -20px; 8 | } 9 | 10 | #banner h1 { 11 | font-size: 60px; 12 | line-height: 1; 13 | letter-spacing: -1px; 14 | } 15 | 16 | .hero-unit { 17 | position: relative; 18 | padding: 30px 15px; 19 | color: #F5F5F5; 20 | text-align: center; 21 | text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1); 22 | background: #448BCA; 23 | } 24 | 25 | .footer { 26 | text-align: center; 27 | padding: 30px 0; 28 | margin-top: 70px; 29 | border-top: 1px solid #E5E5E5; 30 | } 31 | 32 | .page-header { 33 | text-align: center; 34 | } 35 | -------------------------------------------------------------------------------- /client/app/account/account.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .config(function ($stateProvider) { 5 | $stateProvider 6 | .state('login', { 7 | url: '/login', 8 | templateUrl: 'app/account/login/login.html', 9 | controller: 'LoginCtrl' 10 | }) 11 | .state('signup', { 12 | url: '/signup', 13 | templateUrl: 'app/account/signup/signup.html', 14 | controller: 'SignupCtrl' 15 | }) 16 | .state('settings', { 17 | url: '/settings', 18 | templateUrl: 'app/account/settings/settings.html', 19 | controller: 'SettingsCtrl', 20 | authenticate: true 21 | }); 22 | }); -------------------------------------------------------------------------------- /client/app/dashboard/dashboard.css: -------------------------------------------------------------------------------- 1 | .hero-unit{ 2 | height: 335px; 3 | } 4 | 5 | .search { 6 | 7 | margin-top: 50px; 8 | } 9 | 10 | .search-label { 11 | font: 30px; 12 | } 13 | 14 | .form-control { 15 | margin: 25px; 16 | } 17 | 18 | .dash-list-item:hover { 19 | cursor: pointer; 20 | background-color: #448BCA; 21 | } 22 | 23 | .dash-corner { 24 | text-align: right; 25 | } 26 | .dash-list-item { 27 | padding: 5px 5px; 28 | margin-bottom: 10px; 29 | height: 100px; 30 | overflow: hidden; 31 | border: 3px solid #47a447; 32 | border-radius: 10px; 33 | } 34 | 35 | a.list-item { 36 | color: black; 37 | display: block; 38 | } 39 | 40 | .dash-helpRequest .title { 41 | font-size: 18px; 42 | } -------------------------------------------------------------------------------- /client/app/account/settings/settings.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .controller('SettingsCtrl', function ($scope, User, Auth) { 5 | $scope.errors = {}; 6 | 7 | $scope.changePassword = function(form) { 8 | $scope.submitted = true; 9 | if(form.$valid) { 10 | Auth.changePassword( $scope.user.oldPassword, $scope.user.newPassword ) 11 | .then( function() { 12 | $scope.message = 'Password successfully changed.'; 13 | }) 14 | .catch( function() { 15 | form.password.$setValidity('mongoose', false); 16 | $scope.errors.other = 'Incorrect password'; 17 | $scope.message = ''; 18 | }); 19 | } 20 | }; 21 | }); 22 | -------------------------------------------------------------------------------- /client/app/account/login/login.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .controller('LoginCtrl', function ($scope, Auth, $location) { 5 | $scope.user = {}; 6 | $scope.errors = {}; 7 | 8 | $scope.login = function(form) { 9 | $scope.submitted = true; 10 | 11 | if(form.$valid) { 12 | Auth.login({ 13 | username: $scope.user.username, 14 | password: $scope.user.password 15 | }) 16 | .then( function() { 17 | // Logged in, redirect to user's dashboard 18 | $location.path('/dashboard'); 19 | }) 20 | .catch( function(err) { 21 | $scope.errors.other = err.message; 22 | }); 23 | } 24 | }; 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /client/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "jQuery": true, 23 | "angular": true, 24 | "console": true, 25 | "$": true, 26 | "_": true, 27 | "moment": true, 28 | "describe": true, 29 | "beforeEach": true, 30 | "module": true, 31 | "inject": true, 32 | "it": true, 33 | "expect": true, 34 | "browser": true, 35 | "element": true, 36 | "by": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /client/app/main/main.controller.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: MainCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('familyThiefApp')); 7 | 8 | var MainCtrl, 9 | scope, 10 | $httpBackend; 11 | 12 | // Initialize the controller and a mock scope 13 | beforeEach(inject(function (_$httpBackend_, $controller, $rootScope) { 14 | $httpBackend = _$httpBackend_; 15 | $httpBackend.expectGET('/api/things') 16 | .respond(['HTML5 Boilerplate', 'AngularJS', 'Karma', 'Express']); 17 | 18 | scope = $rootScope.$new(); 19 | MainCtrl = $controller('MainCtrl', { 20 | $scope: scope 21 | }); 22 | })); 23 | 24 | it('should attach a list of things to the scope', function () { 25 | $httpBackend.flush(); 26 | expect(scope.awesomeThings.length).toBe(4); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /server/models/user.js: -------------------------------------------------------------------------------- 1 | var db = require('../config/dbConfig.js'); 2 | var Sequelize = require('sequelize'); 3 | var bcrypt = require('bcrypt-nodejs'); 4 | 5 | var User = db.define('User', 6 | { 7 | 8 | username: Sequelize.STRING, 9 | email: Sequelize.STRING, 10 | password: Sequelize.STRING 11 | 12 | 13 | }, 14 | 15 | { 16 | instanceMethods: { 17 | 18 | setPassword : function(newPassword, callback) { 19 | var self = this; 20 | bcrypt.hash(newPassword, null, null, function(err, hash){ 21 | if (!err) { 22 | self.update({password: hash}).then(callback); 23 | } 24 | }); 25 | }, 26 | 27 | checkPassword: function(attemptedPassword) { 28 | return bcrypt.compareSync(attemptedPassword, this.get('password')); 29 | } 30 | } 31 | } 32 | 33 | ); 34 | 35 | 36 | db.sync(); 37 | 38 | module.exports = User; -------------------------------------------------------------------------------- /client/app/submission/submission.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .controller('SubmissionCtrl', function ($scope, $http, Auth, HelpRequest) { 5 | $scope.currentUser = Auth.getCurrentUser(); 6 | 7 | $scope.helpRequest = {}; 8 | $scope.submissionSuccess = false; 9 | $scope.submissionError = false; 10 | 11 | 12 | 13 | $scope.submitHelpRequest = function(isValid) { 14 | if(isValid) { 15 | HelpRequest.save({ 16 | title: $scope.helpRequest.title, 17 | text: $scope.helpRequest.text, 18 | summary: $scope.helpRequest.summary 19 | }, function(helpRequest){ 20 | $scope.submissionError = false; 21 | $scope.submissionSuccess = true; 22 | // save help request submission in user's current session 23 | $scope.currentUser.helpRequests.push(helpRequest); 24 | }); 25 | } else { 26 | $scope.submissionError = true; 27 | } 28 | } 29 | 30 | 31 | 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /client/app/inbox/inbox.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 |
7 | 8 | 9 |
10 |
11 |
12 | From: {{contribution.contributor}} 13 |
14 |
15 | For: {{contribution.title}} 16 |
17 |
18 | {{contribution.contributionText}} 19 |
20 |
21 |
22 |
23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /client/app/account/signup/signup.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .controller('SignupCtrl', function ($scope, Auth, $location) { 5 | $scope.user = {}; 6 | $scope.errors = {}; 7 | 8 | $scope.register = function(form) { 9 | $scope.submitted = true; 10 | 11 | if(form.$valid) { 12 | Auth.createUser({ 13 | username: $scope.user.username, 14 | email: $scope.user.email, 15 | password: $scope.user.password 16 | }) 17 | .then( function() { 18 | // Account created, redirect to user's dashboard 19 | $location.path('/dashboard'); 20 | }) 21 | .catch( function(err) { 22 | err = err.data; 23 | $scope.errors = {}; 24 | 25 | // Update validity of form fields that match the mongoose errors 26 | angular.forEach(err.errors, function(error, field) { 27 | form[field].$setValidity('mongoose', false); 28 | $scope.errors[field] = error.message; 29 | }); 30 | }); 31 | } 32 | }; 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Family-Thief 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /client/app/inbox/inbox.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .controller('InboxCtrl', function ($scope, $http, Auth, HelpRequest, $location) { 5 | 6 | // inject data of currently logged-in user into this controller 7 | $scope.user = Auth.getCurrentUser(); 8 | // array of results initially set to empty 9 | $scope.contributions = []; 10 | 11 | //retrieve from database all objects with contributions to user's help request(s) 12 | $scope.contribution = function() { 13 | $http({ 14 | url: "api/mailbox", 15 | method: "GET", 16 | }).success(function(data, status) { 17 | $scope.contributions = data; 18 | }); 19 | }; 20 | $scope.contribution(); 21 | 22 | //loads contribution in new view when clicked 23 | $scope.loadContribution = function(id, unseen) { 24 | Auth.setContribution(id); // sets the id of the contribution that the user is about to view 25 | if(unseen) { 26 | // update the data of the current user's session 27 | $scope.user.numberUnseenHelps -= 1; 28 | } 29 | $location.path('/contribution'); 30 | }; 31 | 32 | }); -------------------------------------------------------------------------------- /client/app/allHelpRequest/allHelpRequest.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .controller('AllHelpRequestCtrl', function ($scope, $http, Auth, HelpRequest, $location) { 5 | 6 | $scope.user = Auth.getCurrentUser(); 7 | 8 | $scope.resultList = []; 9 | $scope.searchString; 10 | 11 | //search for a project containing the query strings 12 | $scope.search = function() { 13 | $http({ 14 | url: "api/helpRequests", 15 | method: "GET", 16 | params: {search: $scope.searchString} 17 | }).success(function(data, status) { 18 | console.log(data); 19 | $scope.resultList = data; 20 | }); 21 | }; 22 | 23 | $scope.getRecentlySubmitted = function() { 24 | $http({ 25 | url: "api/allHelpRequests", 26 | method: "GET", 27 | }).success(function(data, status) { 28 | $scope.resultList = data; 29 | }); 30 | }; 31 | 32 | $scope.getRecentlySubmitted(); 33 | 34 | $scope.loadHelpRequest = function(id) { 35 | Auth.setHelpRequest(id); 36 | $location.path('/help-request'); 37 | }; 38 | 39 | }); 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /client/app/main/main.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 10 | 11 |
12 |
13 | 14 |
15 |
16 |
17 | 18 |
19 |
20 | 21 |
22 |
23 |
24 |
25 | Get Help! 26 |
27 |
28 | Help Someone! 29 |
30 |
31 |
32 | 33 | 38 | -------------------------------------------------------------------------------- /client/app/dashboard/dashboard.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .controller('DashboardCtrl', function ($scope, $http, Auth, HelpRequest, $location) { 5 | 6 | // inject data of currently logged-in user into this controller 7 | $scope.user = Auth.getCurrentUser(); 8 | // array of search results initially set to empty 9 | $scope.searchResults = []; 10 | // define the searchString var 11 | $scope.searchString; 12 | 13 | //search for a project containing the query strings 14 | $scope.search = function() { 15 | $http({ 16 | url: "api/helpRequests", 17 | method: "GET", 18 | params: {search: $scope.searchString} 19 | }).success(function(data, status) { 20 | $scope.searchResults = data; 21 | }); 22 | }; 23 | 24 | // load the help request that gets clicked 25 | $scope.loadHelpRequest = function(id) { 26 | Auth.setHelpRequest(id); 27 | $location.path('/help-request'); 28 | }; 29 | 30 | // load the contribution that gets clicked 31 | $scope.loadContribution = function(id) { 32 | Auth.setContribution(id); // sets the id of the contribution that the user is about to view 33 | $location.path('/contribution'); 34 | }; 35 | 36 | }); 37 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-angular-fullstack": { 3 | "insertRoutes": true, 4 | "registerRoutesFile": "server/routes.js", 5 | "routesNeedle": "// Insert routes below", 6 | "routesBase": "/api/", 7 | "pluralizeRoutes": true, 8 | "insertSockets": true, 9 | "registerSocketsFile": "server/config/socketio.js", 10 | "socketsNeedle": "// Insert sockets below", 11 | "filters": { 12 | "js": true, 13 | "html": true, 14 | "css": true, 15 | "uirouter": true, 16 | "bootstrap": true, 17 | "uibootstrap": true 18 | } 19 | }, 20 | "generator-ng-component": { 21 | "routeDirectory": "client/app/", 22 | "directiveDirectory": "client/app/", 23 | "filterDirectory": "client/app/", 24 | "serviceDirectory": "client/app/", 25 | "basePath": "client", 26 | "moduleName": "", 27 | "filters": [ 28 | "uirouter" 29 | ], 30 | "extensions": [ 31 | "js", 32 | "html", 33 | "css" 34 | ], 35 | "directiveSimpleTemplates": "", 36 | "directiveComplexTemplates": "", 37 | "filterTemplates": "", 38 | "serviceTemplates": "", 39 | "factoryTemplates": "", 40 | "controllerTemplates": "", 41 | "decoratorTemplates": "", 42 | "providerTemplates": "", 43 | "routeTemplates": "" 44 | } 45 | } -------------------------------------------------------------------------------- /client/app/contribution/contribution.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

{{contribution.contributor}}'s contribution for {{contribution.userHelped}}'s - {{contribution.votes}} votes

5 |
6 | 7 |

Text of Original Help Request

8 |

{{contribution.helpRequestText}}

9 |
10 |
11 |

The Contribution

12 |

{{contribution.contributionText}}

13 |
14 | 15 | 16 | 17 |
18 |
19 |

{{comment.commenter}}

20 |

{{comment.comment}}

21 |
22 |
23 |
24 |
-------------------------------------------------------------------------------- /client/app/allHelpRequest/allHelpRequest.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 |
7 |
8 |
9 | See Recent Help Requests 10 |

or

11 |
12 | 13 | 14 |
15 |
16 |
17 | 18 |
19 |
20 |
21 |

{{result.title}}

22 |
23 |
24 |

{{result.summary}}

25 |
26 |
27 |

{{result.origDate | date:'yyyy-MM-dd'}}

28 |
29 |
30 |
31 |
32 | 33 | -------------------------------------------------------------------------------- /client/app/account/settings/settings.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |

Change Password

7 |
8 |
9 |
10 | 11 |
12 | 13 | 14 | 16 |

17 | {{ errors.other }} 18 |

19 |
20 | 21 |
22 | 23 | 24 | 27 |

29 | Password must be at least 3 characters. 30 |

31 |
32 | 33 |

{{ message }}

34 | 35 | 36 |
37 |
38 |
39 |
-------------------------------------------------------------------------------- /client/app/app.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Bootstrap Fonts 4 | */ 5 | 6 | @font-face { 7 | font-family: 'Glyphicons Halflings'; 8 | src: url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot'); 9 | src: url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), 10 | url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff') format('woff'), 11 | url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.ttf') format('truetype'), 12 | url('../bower_components/bootstrap/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); 13 | } 14 | 15 | /** 16 | *Font Awesome Fonts 17 | */ 18 | 19 | @font-face { 20 | font-family: 'FontAwesome'; 21 | src: url('../bower_components/font-awesome/fonts/fontawesome-webfont.eot?v=4.1.0'); 22 | src: url('../bower_components/font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'), 23 | url('../bower_components/font-awesome/fonts/fontawesome-webfont.woff?v=4.1.0') format('woff'), 24 | url('../bower_components/font-awesome/fonts/fontawesome-webfont.ttf?v=4.1.0') format('truetype'), 25 | url('../bower_components/font-awesome/fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular') format('svg'); 26 | font-weight: normal; 27 | font-style: normal; 28 | } 29 | 30 | /** 31 | * App-wide Styles 32 | */ 33 | 34 | .browsehappy { 35 | margin: 0.2em 0; 36 | background: #ccc; 37 | color: #000; 38 | padding: 0.2em 0; 39 | } 40 | -------------------------------------------------------------------------------- /client/app/contributionRecd/contributionRecd.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

Response to your Help Request

6 |
7 |
8 | 9 | 11 |
12 |
13 | 14 |

{{contribution.helpRequestText}}

15 |
16 |
17 | 18 |

{{helpRequest.text}}

19 |
20 |
21 | 22 | 24 |
25 | 26 |
27 |
28 |
29 |
30 | -------------------------------------------------------------------------------- /client/app/account/login/login.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |

Login

7 |
8 | 9 |
10 | 11 | 12 | 13 |
14 | 15 |
16 | 17 | 18 | 19 |
20 | 21 |
22 |

23 | Please enter your username and password. 24 |

25 | 26 |

{{ errors.other }}

27 |
28 | 29 |
30 | 33 | 34 | Register 35 | 36 |
37 |
38 |
39 |
40 |
41 | 42 |
43 |
44 |
45 |
46 |
-------------------------------------------------------------------------------- /client/app/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp', [ 4 | 'ngCookies', 5 | 'ngResource', 6 | 'ngSanitize', 7 | 'ui.router', 8 | 'ui.bootstrap' 9 | ]) 10 | .config(function ($stateProvider, $urlRouterProvider, $locationProvider, $httpProvider) { 11 | $urlRouterProvider 12 | .otherwise('/'); 13 | 14 | $locationProvider.html5Mode(true); 15 | $httpProvider.interceptors.push('authInterceptor'); 16 | }) 17 | .factory('authInterceptor', function ($rootScope, $q, $cookieStore, $location) { 18 | return { 19 | // Add authorization token to headers 20 | request: function (config) { 21 | config.headers = config.headers || {}; 22 | if ($cookieStore.get('token')) { 23 | config.headers.Authorization = 'Bearer ' + $cookieStore.get('token'); 24 | } 25 | return config; 26 | }, 27 | 28 | // Intercept 401s and redirect you to login 29 | responseError: function(response) { 30 | if(response.status === 401) { 31 | $location.path('/login'); 32 | // remove any stale tokens 33 | $cookieStore.remove('token'); 34 | return $q.reject(response); 35 | } 36 | else { 37 | return $q.reject(response); 38 | } 39 | } 40 | }; 41 | }) 42 | 43 | .run(function ($rootScope, $location, Auth) { 44 | // Redirect to login if route requires auth and you're not logged in 45 | $rootScope.$on('$stateChangeStart', function (event, next) { 46 | Auth.isLoggedInAsync(function(loggedIn) { 47 | if (next.authenticate && !loggedIn) { 48 | $location.path('/login'); 49 | } 50 | }); 51 | }); 52 | }); -------------------------------------------------------------------------------- /client/components/navbar/navbar.html: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration 2 | // https://github.com/angular/protractor/blob/master/referenceConf.js 3 | 4 | 'use strict'; 5 | 6 | exports.config = { 7 | // The timeout for each script run on the browser. This should be longer 8 | // than the maximum time your application needs to stabilize between tasks. 9 | allScriptsTimeout: 110000, 10 | 11 | // A base URL for your application under test. Calls to protractor.get() 12 | // with relative paths will be prepended with this. 13 | baseUrl: 'http://localhost:' + (process.env.PORT || '9000'), 14 | 15 | // If true, only chromedriver will be started, not a standalone selenium. 16 | // Tests for browsers other than chrome will not run. 17 | chromeOnly: true, 18 | 19 | // list of files / patterns to load in the browser 20 | specs: [ 21 | 'e2e/**/*.spec.js' 22 | ], 23 | 24 | // Patterns to exclude. 25 | exclude: [], 26 | 27 | // ----- Capabilities to be passed to the webdriver instance ---- 28 | // 29 | // For a full list of available capabilities, see 30 | // https://code.google.com/p/selenium/wiki/DesiredCapabilities 31 | // and 32 | // https://code.google.com/p/selenium/source/browse/javascript/webdriver/capabilities.js 33 | capabilities: { 34 | 'browserName': 'chrome' 35 | }, 36 | 37 | // ----- The test framework ----- 38 | // 39 | // Jasmine and Cucumber are fully supported as a test and assertion framework. 40 | // Mocha has limited beta support. You will need to include your own 41 | // assertion framework if working with mocha. 42 | framework: 'jasmine', 43 | 44 | // ----- Options to be passed to minijasminenode ----- 45 | // 46 | // See the full list at https://github.com/juliemr/minijasminenode 47 | jasmineNodeOpts: { 48 | defaultTimeoutInterval: 30000 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /client/app/contribution/contribution.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .controller('ContributionCtrl', function ($scope, $http, Auth, $location, Contribution) { 5 | 6 | // inject data of currently logged-in user into this controller 7 | $scope.user = Auth.getCurrentUser(); 8 | $scope.contribution = {}; 9 | $scope.contribution.id = Auth.getContribution(); // gets the id of the contribution that is about to be loaded 10 | $scope.id = Auth.getContribution(); // delete when server is returning the id in getContribution data 11 | $scope.newCommentText; 12 | $scope.hasVoted = false; 13 | $scope.isOwner = false; 14 | 15 | $scope.getContributionData = function() { 16 | Contribution.get({id: $scope.contribution.id}, function(contribution) { 17 | if(contribution.contributor === $scope.user.username) { 18 | $scope.isOwner = true; 19 | } 20 | console.log(contribution); 21 | $scope.contribution = contribution; 22 | }); 23 | } 24 | 25 | $scope.getContributionData(); 26 | 27 | $scope.addComment = function(form) { 28 | if(form.$valid) { 29 | var comment = { 30 | contributionId: $scope.id, 31 | commenter: $scope.user.username, 32 | text: $scope.newCommentText 33 | }; 34 | $http.post('api/contributions/comments', comment) 35 | .success(function(data, status) { 36 | comment.comment = comment.text; 37 | $scope.contribution.comments.push(comment); 38 | }); 39 | } 40 | }; 41 | $scope.upvote = function() { 42 | if(!$scope.hasVoted) { 43 | $http.post("api/contributions/votes", { 44 | contributionId: Auth.getContribution() 45 | }) 46 | .success(function(data, status) { 47 | $scope.contribution.votes += 1; 48 | $scope.user.votes += 1; 49 | }); 50 | } 51 | }; 52 | 53 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Stories in Ready](https://badge.waffle.io/family-thief/family-thief.png?label=ready&title=Ready)](https://waffle.io/family-thief/family-thief) 2 | # InkWell 3 | 4 | > A collaborative writing platform 5 | 6 | ## Team 7 | 8 | - __Product Owner__: Brandon Ellis 9 | - __Scrum Master__: Adam Van Antwerp 10 | - __Development Team Members__: Henry Ng, Craig Smith 11 | 12 | ## Table of Contents 13 | 14 | 1. [Usage](#usage) 15 | 1. [Requirements](#requirements) 16 | 1. [Development](#development) 17 | 1. [Installing Dependencies](#installing-dependencies) 18 | 1. [Roadmap](#roadmap) 19 | 1. [Team](#team) 20 | 1. [Contributing](#contributing) 21 | 22 | ## Usage 23 | 24 | - Download and unzip the repository. 25 | - Install [dependencies](#installing-dependencies). 26 | - Rename /server/config/dbConfig.example.js to be dbConfig.js 27 | - Edit dbConfig.js to point to a valid relational database (sequelize supports quite a few, see [this](http://docs.sequelizejs.com/en/latest/docs/getting-started/) page for examples and instructions on configuring the ORM) 28 | - Rename /server/config/secret.example to secret and replace the content with a new secret string. Try making it long and not comprised of discernable words. 29 | - Start the server from the base directory with `node server\app.js`. 30 | 31 | ## Requirements 32 | 33 | - Node 0.10.x 34 | - MySQL, MariaDB, SQLite, PostgreSQL or MS SQL 35 | 36 | ## Development 37 | 38 | ### Installing Dependencies 39 | 40 | From within the root directory: 41 | 42 | ```sh 43 | sudo npm install -g bower 44 | npm install 45 | bower install 46 | ``` 47 | 48 | ### Roadmap 49 | 50 | View the project roadmap [here](https://waffle.io/family-thief/family-thief). 51 | View the project commit history [here](commit_history.md). 52 | 53 | ### Database Schema 54 | 55 | ![relational schema](http://i.imgur.com/G078ktJ.png "Relational Schema") 56 | 57 | 58 | ## Contributing 59 | 60 | See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines. 61 | -------------------------------------------------------------------------------- /client/app/help-request/help-request.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Contribute to this project...

5 |

Your Help Request

6 |
7 |

{{helpRequest.title}} - {{helpRequest.votes}} votes

8 | 9 |

Summary

10 |

{{helpRequest.summary}}

11 |

Text

12 |

{{helpRequest.text}}

13 |
14 | 37 |
38 |
39 | -------------------------------------------------------------------------------- /client/app/submission/submission.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

Your New Help Request

6 |
7 |
8 | 9 | 10 |

The title of your help request

11 |

The title is required.

12 |
13 |
14 | 15 | 17 |

Paste the writing you would like help with here

18 |

Your writing is required.

19 |
20 |
21 | 22 | 23 |

Write what you'd like help with here

24 |

Your summary is required.

25 |
26 |

Your help request for this piece of writing has been submitted.

You need to fill more of the form out

27 |
28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /client/app/dashboard/dashboard.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |

Welcome {{user.username}}!

7 |

Start writing today...

8 |
9 |
10 |

You Have {{user.numberUnseenHelps}} new help from others! 11 | 12 |

13 |

You have {{user.votes}} likes for your writing!

14 |
15 |
16 |
17 | 18 | 19 |
20 | 55 |
56 | 57 | -------------------------------------------------------------------------------- /server/models/associations.js: -------------------------------------------------------------------------------- 1 | var User = require('./user.js'); 2 | var Project = require('./project.js'); 3 | var ProjectUpvote = require('./projectupvotes.js'); 4 | var Contribution = require('./contributions.js'); 5 | var ContributionUpvote = require('./contributionupvotes.js'); 6 | var ContributionComment = require('./contributioncomments.js'); 7 | var ProjectComment = require('./projectcomments.js'); 8 | var db = require('../config/dbConfig.js'); 9 | 10 | //relationship between user and project 11 | User.hasMany(Project, {foreignKey: 'user_id'}); 12 | Project.belongsTo(User, {foreignKey: 'user_id'}); 13 | 14 | //relationship between project and contribution 15 | Project.hasMany(Contribution, {foreignKey: 'project'}); 16 | Contribution.belongsTo(Project, {foreignKey: 'project'}); 17 | 18 | //relationship between user and contribution 19 | User.hasMany(Contribution, {foreignKey: 'contributor'}); 20 | Contribution.belongsTo(User, {foreignKey: 'contributor'}); 21 | 22 | //relationship between user and contribution comments 23 | User.hasMany(ContributionComment, {foreignKey: 'commenter'}); 24 | ContributionComment.belongsTo(User, {foreignKey: 'commenter'}); 25 | 26 | //relationship between contribution and contribution comments 27 | Contribution.hasMany(ContributionComment, {foreignKey: 'contributionCommented'}); 28 | ContributionComment.belongsTo(Contribution, {foreignKey: 'contributionCommented'}); 29 | 30 | //relationship between project comments and users 31 | User.hasMany(ProjectComment, {foreignKey: 'commenter'}); 32 | ProjectComment.belongsTo(User, {foreignKey: 'commenter'}); 33 | 34 | //relationship between project and project comments 35 | Project.hasMany(ProjectComment, {foreignKey: 'projectCommented'}); 36 | ProjectComment.belongsTo(Project, {foreignKey: 'projectCommented'}); 37 | 38 | //relationship between contribution and contributionupvote 39 | Contribution.hasMany(ContributionUpvote, {foreignKey: 'contributionupvoted'}); 40 | ContributionUpvote.belongsTo(Contribution, {foreignKey: 'contributioupvoted'}); 41 | 42 | //relationship between user and contribution upvote 43 | User.hasMany(ContributionUpvote, {foreignKey: 'upvoter'}); 44 | ContributionUpvote.belongsTo(User, {foreignKey: 'upvoter'}); 45 | 46 | //relationship between project and projectupvote 47 | Project.hasMany(ProjectUpvote, {foreignKey: 'projectupvoted'}); 48 | ProjectUpvote.belongsTo(Project, {foreignKey: 'projectupvoted'}); 49 | 50 | //relationship between projectupvote and user 51 | User.hasMany(ProjectUpvote, {foreignKey: 'upvoter'}); 52 | ProjectUpvote.belongsTo(User, {foreignKey: 'upvoter'}); 53 | 54 | 55 | db.sync(); 56 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.10/config/configuration-file.html 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | // testing framework to use (jasmine/mocha/qunit/...) 10 | frameworks: ['jasmine'], 11 | 12 | // list of files / patterns to load in the browser 13 | files: [ 14 | 'client/bower_components/jquery/dist/jquery.js', 15 | 'client/bower_components/angular/angular.js', 16 | 'client/bower_components/angular-mocks/angular-mocks.js', 17 | 'client/bower_components/angular-resource/angular-resource.js', 18 | 'client/bower_components/angular-cookies/angular-cookies.js', 19 | 'client/bower_components/angular-sanitize/angular-sanitize.js', 20 | 'client/bower_components/angular-route/angular-route.js', 21 | 'client/bower_components/angular-bootstrap/ui-bootstrap-tpls.js', 22 | 'client/bower_components/lodash/dist/lodash.compat.js', 23 | 'client/bower_components/angular-ui-router/release/angular-ui-router.js', 24 | 'client/app/app.js', 25 | 'client/app/app.coffee', 26 | 'client/app/**/*.js', 27 | 'client/app/**/*.coffee', 28 | 'client/components/**/*.js', 29 | 'client/components/**/*.coffee', 30 | 'client/app/**/*.jade', 31 | 'client/components/**/*.jade', 32 | 'client/app/**/*.html', 33 | 'client/components/**/*.html' 34 | ], 35 | 36 | preprocessors: { 37 | '**/*.jade': 'ng-jade2js', 38 | '**/*.html': 'html2js', 39 | '**/*.coffee': 'coffee', 40 | }, 41 | 42 | ngHtml2JsPreprocessor: { 43 | stripPrefix: 'client/' 44 | }, 45 | 46 | ngJade2JsPreprocessor: { 47 | stripPrefix: 'client/' 48 | }, 49 | 50 | // list of files / patterns to exclude 51 | exclude: [], 52 | 53 | // web server port 54 | port: 8080, 55 | 56 | // level of logging 57 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 58 | logLevel: config.LOG_INFO, 59 | 60 | 61 | // enable / disable watching file and executing tests whenever any file changes 62 | autoWatch: false, 63 | 64 | 65 | // Start these browsers, currently available: 66 | // - Chrome 67 | // - ChromeCanary 68 | // - Firefox 69 | // - Opera 70 | // - Safari (only Mac) 71 | // - PhantomJS 72 | // - IE (only Windows) 73 | browsers: ['PhantomJS'], 74 | 75 | 76 | // Continuous Integration mode 77 | // if true, it capture browsers, run tests and exit 78 | singleRun: false 79 | }); 80 | }; 81 | -------------------------------------------------------------------------------- /client/components/modal/modal.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .factory('Modal', function ($rootScope, $modal) { 5 | /** 6 | * Opens a modal 7 | * @param {Object} scope - an object to be merged with modal's scope 8 | * @param {String} modalClass - (optional) class(es) to be applied to the modal 9 | * @return {Object} - the instance $modal.open() returns 10 | */ 11 | function openModal(scope, modalClass) { 12 | var modalScope = $rootScope.$new(); 13 | scope = scope || {}; 14 | modalClass = modalClass || 'modal-default'; 15 | 16 | angular.extend(modalScope, scope); 17 | 18 | return $modal.open({ 19 | templateUrl: 'components/modal/modal.html', 20 | windowClass: modalClass, 21 | scope: modalScope 22 | }); 23 | } 24 | 25 | // Public API here 26 | return { 27 | 28 | /* Confirmation modals */ 29 | confirm: { 30 | 31 | /** 32 | * Create a function to open a delete confirmation modal (ex. ng-click='myModalFn(name, arg1, arg2...)') 33 | * @param {Function} del - callback, ran when delete is confirmed 34 | * @return {Function} - the function to open the modal (ex. myModalFn) 35 | */ 36 | delete: function(del) { 37 | del = del || angular.noop; 38 | 39 | /** 40 | * Open a delete confirmation modal 41 | * @param {String} name - name or info to show on modal 42 | * @param {All} - any additional args are passed staight to del callback 43 | */ 44 | return function() { 45 | var args = Array.prototype.slice.call(arguments), 46 | name = args.shift(), 47 | deleteModal; 48 | 49 | deleteModal = openModal({ 50 | modal: { 51 | dismissable: true, 52 | title: 'Confirm Delete', 53 | html: '

Are you sure you want to delete ' + name + ' ?

', 54 | buttons: [{ 55 | classes: 'btn-danger', 56 | text: 'Delete', 57 | click: function(e) { 58 | deleteModal.close(e); 59 | } 60 | }, { 61 | classes: 'btn-default', 62 | text: 'Cancel', 63 | click: function(e) { 64 | deleteModal.dismiss(e); 65 | } 66 | }] 67 | } 68 | }, 'modal-danger'); 69 | 70 | deleteModal.result.then(function(event) { 71 | del.apply(event, args); 72 | }); 73 | }; 74 | } 75 | } 76 | }; 77 | }); 78 | -------------------------------------------------------------------------------- /client/app/help-request/help-request.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .controller('HelpRequestCtrl', function ($scope, $http, Auth, HelpRequest, $location) { 5 | $scope.currentUser = Auth.getCurrentUser(); 6 | $scope.helpRequest = {}; 7 | $scope.newContribution = {}; 8 | $scope.helpRequest.id = Auth.getHelpRequest(); 9 | $scope.hasVoted = false; 10 | $scope.contributionSuccess = false; 11 | 12 | 13 | // grabs the appropriate helpRequest data based on property stored in Auth service 14 | $scope.getHelpRequestData = function() { 15 | HelpRequest.get({id: $scope.helpRequest.id}, function(helpRequest) { 16 | $scope.helpRequest = helpRequest; 17 | // check if current user is owner of this help request and display it differently if so 18 | if($scope.currentUser.username === $scope.helpRequest.username) { 19 | $scope.isOwner = true; 20 | } else { 21 | $scope.isOwner = false; 22 | } 23 | }); 24 | } 25 | 26 | $scope.getHelpRequestData(); 27 | 28 | $scope.respondToHelpRequest = function() { 29 | var contribText = $scope.newContribution.text; 30 | $http.post('/api/contributions', { 31 | helperUsername: $scope.currentUser.username, 32 | helpedId: Auth.getHelpRequest(), // returns the id of the currently viewed help request 33 | text: $scope.newContribution.text 34 | }) 35 | .success(function(data, status) { 36 | // make this new data display in this view 37 | $scope.helpRequest.contributions.push({ 38 | helperUsername: $scope.currentUser.username, // username of contributor 39 | id: data.id, 40 | textSnippet: contribText, 41 | username: $scope.helpRequest.username, // owner of help request 42 | origDate: data.origDate 43 | }); 44 | $scope.currentUser.contributions.push({ 45 | id: data.id, 46 | textSnippet: contribText, 47 | helperUsername: $scope.currentUser.username, 48 | origDate: data.origDate 49 | }); 50 | $scope.contributionSuccess = true; 51 | }) 52 | }; 53 | 54 | $scope.loadContribution = function(id) { 55 | Auth.setContribution(id); // sets the id of the contribution that the user is about to view 56 | $location.path('/contribution'); 57 | }; 58 | 59 | $scope.upvote = function() { 60 | if(!$scope.hasVoted) { 61 | $http.post("api/helpRequests/votes", { 62 | helpRequestId: Auth.getHelpRequest() 63 | }) 64 | .success(function(data, status) { 65 | $scope.currentUser.votes += 1; 66 | $scope.helpRequest.votes += 1; 67 | }); 68 | } 69 | } 70 | 71 | 72 | 73 | 74 | }); 75 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "family-thief", 3 | "version": "0.0.0", 4 | "main": "server/app.js", 5 | "dependencies": { 6 | "bcrypt-nodejs": "0.0.3", 7 | "body-parser": "~1.5.0", 8 | "composable-middleware": "~0.3.0", 9 | "compression": "~1.0.1", 10 | "connect-mongo": "^0.4.1", 11 | "cookie-parser": "~1.0.1", 12 | "ejs": "~0.8.4", 13 | "errorhandler": "~1.0.0", 14 | "express": "~4.0.0", 15 | "express-session": "~1.0.2", 16 | "lodash": "~2.4.1", 17 | "method-override": "~1.0.0", 18 | "morgan": "~1.0.0", 19 | "mysql": "^2.6.2", 20 | "sequelize": "~2.1.0", 21 | "serve-favicon": "~2.0.1", 22 | "readline": "0.0.7", 23 | "passport": "~0.2.1", 24 | "passport-local": "~1.0.0", 25 | "jsonwebtoken": "~5.0.0", 26 | "express-jwt": "~3.0.0" 27 | }, 28 | "devDependencies": { 29 | "grunt": "~0.4.4", 30 | "grunt-autoprefixer": "~0.7.2", 31 | "grunt-wiredep": "~1.8.0", 32 | "grunt-concurrent": "~0.5.0", 33 | "grunt-contrib-clean": "~0.5.0", 34 | "grunt-contrib-concat": "~0.4.0", 35 | "grunt-contrib-copy": "~0.5.0", 36 | "grunt-contrib-cssmin": "~0.9.0", 37 | "grunt-contrib-htmlmin": "~0.2.0", 38 | "grunt-contrib-imagemin": "~0.7.1", 39 | "grunt-contrib-jshint": "~0.10.0", 40 | "grunt-contrib-uglify": "~0.4.0", 41 | "grunt-contrib-watch": "~0.6.1", 42 | "grunt-google-cdn": "~0.4.0", 43 | "grunt-newer": "~0.7.0", 44 | "grunt-ng-annotate": "^0.2.3", 45 | "grunt-rev": "~0.1.0", 46 | "grunt-svgmin": "~0.4.0", 47 | "grunt-usemin": "~2.1.1", 48 | "grunt-env": "~0.4.1", 49 | "grunt-node-inspector": "~0.1.5", 50 | "grunt-nodemon": "~0.2.0", 51 | "grunt-angular-templates": "^0.5.4", 52 | "grunt-dom-munger": "^3.4.0", 53 | "grunt-protractor-runner": "^1.1.0", 54 | "grunt-asset-injector": "^0.1.0", 55 | "grunt-karma": "~0.8.2", 56 | "grunt-build-control": "0.4.0", 57 | "grunt-mocha-test": "~0.10.2", 58 | "jit-grunt": "^0.5.0", 59 | "time-grunt": "~0.3.1", 60 | "grunt-express-server": "~0.4.17", 61 | "grunt-open": "~0.2.3", 62 | "open": "~0.0.4", 63 | "jshint-stylish": "~0.1.5", 64 | "connect-livereload": "~0.4.0", 65 | "karma-ng-scenario": "~0.1.0", 66 | "karma-firefox-launcher": "~0.1.3", 67 | "karma-script-launcher": "~0.1.0", 68 | "karma-html2js-preprocessor": "~0.1.0", 69 | "karma-ng-jade2js-preprocessor": "^0.1.2", 70 | "karma-jasmine": "~0.1.5", 71 | "karma-chrome-launcher": "~0.1.3", 72 | "requirejs": "~2.1.11", 73 | "karma-requirejs": "~0.2.1", 74 | "karma-coffee-preprocessor": "~0.2.1", 75 | "karma-jade-preprocessor": "0.0.11", 76 | "karma-phantomjs-launcher": "~0.1.4", 77 | "karma": "~0.12.9", 78 | "karma-ng-html2js-preprocessor": "~0.1.0", 79 | "supertest": "~0.11.0", 80 | "should": "~3.3.1" 81 | }, 82 | "engines": { 83 | "node": ">=0.10.0" 84 | }, 85 | "scripts": { 86 | "start": "node server/app.js", 87 | "test": "grunt test", 88 | "update-webdriver": "node node_modules/grunt-protractor-runner/node_modules/protractor/bin/webdriver-manager update" 89 | }, 90 | "private": true 91 | } 92 | -------------------------------------------------------------------------------- /client/app/account/signup/signup.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |

Sign up

7 |
8 |
9 |

End Writer's Block

10 |
11 |
12 |
13 |
14 |
15 | 16 |
18 | 19 | 20 | 22 |

23 | A username is required 24 |

25 |
26 | 27 |
29 | 30 | 31 | 34 |

35 | Doesn't look like a valid email. 36 |

37 |

38 | What's your email address? 39 |

40 |

41 | {{ errors.email }} 42 |

43 |
44 | 45 |
47 | 48 | 49 | 53 |

55 | Password must be at least 3 characters. 56 |

57 |

58 | {{ errors.password }} 59 |

60 |
61 | 62 | 63 |
64 | 67 | 68 | Login 69 | 70 |
71 | 72 |
73 |
74 |
75 |
    76 |
  • Join a community of writers that push each other to write
  • 77 |
  • Submit your own writing and mark the places where you're stuck or need ideas
  • 78 |
  • Practice your own writing by helping others in the places where they're stuck
  • 79 |
  • Earn points by contributing. Defeat writer's block forever
  • 80 |
81 |
82 |
83 |
-------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var bodyParser = require('body-parser'); 3 | var expressJwt = require('express-jwt'); 4 | var jwt = require('jsonwebtoken'); 5 | var helper = require('./models/helpers.js'); 6 | var fs = require('fs'); 7 | 8 | var app = express(); 9 | 10 | app.use(bodyParser.json()); 11 | app.use(bodyParser.urlencoded({ extended: true })); 12 | 13 | app.use(express.static(__dirname + '/../client')); 14 | 15 | var secret = String(fs.readFileSync(__dirname + '/config/secret')); 16 | 17 | //refactor this to explicitly protect certain routes 18 | app.use('/api/auth/local', expressJwt({secret: secret})); 19 | app.use('/api/things', expressJwt({secret: secret})); 20 | app.use('/api/helpRequests/votes', expressJwt({secret: secret})); 21 | app.use('/api/users/me', expressJwt({secret: secret})); 22 | app.use('/api/helpRequests/', expressJwt({secret: secret})); 23 | app.use('/api/contributions/', expressJwt({secret: secret})); 24 | app.use('/api/contributions/comments', expressJwt({secret: secret})); 25 | app.use('/api/contributions/votes', expressJwt({secret: secret})); 26 | app.use('/api/mailbox', expressJwt({secret: secret})); 27 | 28 | //path for when users are created 29 | app.post('/api/users', function(req, res){ 30 | helper.searchOrMake(req.body.username, req.body.email, req.body.password, res, secret); 31 | }); 32 | 33 | // path for when users are logging in 34 | app.post('/auth/local', function(req, res) { 35 | helper.authenticate(req.body.username, req.body.password, res, secret); 36 | }); 37 | 38 | //path for user's profile 39 | app.get('/api/users/me', function(req, res){ 40 | var decoded = jwt.decode(req.headers.authorization.slice(7)); 41 | helper.findAllInfo(decoded.username, res); 42 | }); 43 | 44 | //path for help request 45 | app.post('/api/helpRequests', function(req, res){ 46 | var decoded = jwt.decode(req.headers.authorization.slice(7)); 47 | helper.helpRequest(decoded.username, req.body, res); 48 | }); 49 | 50 | //path for votes 51 | app.post('/api/helpRequests/votes', function(req, res){ 52 | var decoded = jwt.decode(req.headers.authorization.slice(7)); 53 | helper.projectUpvote(decoded.id, req.body.helpRequestId, res); 54 | }); 55 | 56 | //path for viewing specific helpRequest 57 | app.get('/api/helpRequests/:id', function(req, res){ 58 | helper.viewProject(req.params.id, res); 59 | }); 60 | 61 | //path for posting contribution 62 | app.post('/api/contributions', function (req, res){ 63 | var decoded = jwt.decode(req.headers.authorization.slice(7)); 64 | helper.makeContribution(decoded.username, req.body, res); 65 | }); 66 | 67 | //path for posting contribution comments 68 | app.post('/api/contributions/comments', function (req, res){ 69 | helper.contributionComment(req.body, res); 70 | }); 71 | 72 | //path for voting on contributions 73 | app.post('/api/contributions/votes', function (req, res){ 74 | var decoded = jwt.decode(req.headers.authorization.slice(7)); 75 | helper.contributionUpvote(decoded.id, req.body.contributionId, res); 76 | }); 77 | 78 | //path for viewing contributions 79 | app.get('/api/contributions/:id', function (req, res){ 80 | var decoded = jwt.decode(req.headers.authorization.slice(7)); 81 | console.log("Getting contribution: ", decoded); 82 | helper.viewContribution(req.params.id, decoded, res); 83 | }); 84 | 85 | //path for searches 86 | app.get('/api/helpRequests?', function (req, res){ 87 | helper.searching(req.query.search, res); 88 | }); 89 | 90 | //path for mailbox 91 | app.get('/api/mailbox', function (req, res){ 92 | var decoded = jwt.decode(req.headers.authorization.slice(7)); 93 | helper.checkUnseenContributions(decoded.username, res); 94 | }); 95 | 96 | //path for all help request projects 97 | app.get('/api/allhelpRequests', function (req, res){ 98 | helper.getAll(res); 99 | }); 100 | 101 | app.listen(3000); 102 | -------------------------------------------------------------------------------- /client/favicon.ico: -------------------------------------------------------------------------------- 1 |   �( @   -2Op"=p�Jt��Jt��b���������������������������������������������������b���Jt��Jt��"=p�Op-2O`O�O�O�O�O�O�O� $\�Jt��������������v���v���������������Jt�� $\�O�O�O�O�O�O�O�O`O�O�O�O�O�O�O�O�O�O� ;n�s���>���>���>���>���s��� ;n�O�O�O�O�O�O�O�O�O�O�O`O�O�O�O�O�O�O�O�O�O� $\�]���^n��^n��]��� $\�O�O�O�O�O�O�O�O�O�O�O`O�O�O�O�O�O�O�O�O�O�O�n�*��*��n�O�O�O�O�O�O�O�O�O�O�O�  O�O�O�O�O�O�O�O�O�O�O�5>Y�5>Y�O�O�O�O�O�O�O�O�O�O�O�  -2O�O�O�O�O�O�O�O�O�O�&6e�&6e�O�O�O�O�O�O�O�O�O�O�-25r�4���E��� $\�O�O�O�O�O�O�O�O�O�O�O�O�O�O�O�O�O�O� $\�E���4���5r�5r�E���M���M���v���0\��O�O�O�O�O�O�O� $\� $\�O�O�O�O�O�O�O�0\��v���M���M���E���5r�)��p&��p��&��������������b���Jt��Jt��Jt��0\��#i��.r��.r��#i��0\��Jt��Jt��Jt��b���������������&��p��&��)��p4���&��-���_������������������]���]�������7���p�����������p���7�������]���]�������������������_��-���-���4���qֈp��p��p����������������������p���7���#i��p�����������p���#i��7���p�����������������������p��&��-���qֈ8��(p��p��I���v���v���]���7���n���v���p���#i��]���v���v���]���#i��p���v���n���7���]���v���v���I���-���-���8��(;��`-���M���7���7���7���.r��R��E��R��E��7���7���7���7���E��R��E��R��.r��7���7���7���M���M���;��`���������������������������z��������������������������� 2 | �  ��� 3 | � 9� 9� 9� 9� 9� 9� 9� 9� 4 |  �n�n� 5 |  � 9� 9� 9� 9� 9� 9� 9� 9� 6 | ����*�x*��*��*��*��*��*��*��n�&��#��&��&��n�*��*��*��*��*��*��*��*�x*ݟ*��*��*��*��*��*��!��#��&��#��&��*��!��!��*��*��*��*��*��*��*ݟ*ݿ*��*��*��*��*��*��n�*��*�� 9� 9�*��*���*��*��*��*��*��*��*ݿ*��*��*��*��*��*��*��!��#��&��&��&��*��#��!��*��*��*��*��*��*��*��  ��������I�&��&��&��&��I���������  U��������� 7 |  �n�n� 8 |  ����������-2z����������������������z������������������������ 9 | ����������������������� 10 | ������������������������� 11 | ������������������������-2����������������������U�������������������z5r������������������-25r�U�����������z  ������������������������������?��� -------------------------------------------------------------------------------- /client/components/auth/auth.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .factory('Auth', function Auth($location, $rootScope, $http, User, $cookieStore, $q) { 5 | var currentUser = {}; 6 | // keeps track of whether a user is about to view a helpRequest 7 | var helpRequestId; 8 | // keeps track of whether a user is about to view a contribution 9 | var contributionId; 10 | if($cookieStore.get('token')) { 11 | currentUser = User.get(); 12 | } 13 | 14 | return { 15 | 16 | /** 17 | * Authenticate user and save token 18 | * 19 | * @param {Object} user - login info 20 | * @param {Function} callback - optional 21 | * @return {Promise} 22 | */ 23 | login: function(user, callback) { 24 | var cb = callback || angular.noop; 25 | var deferred = $q.defer(); 26 | 27 | $http.post('/auth/local', { 28 | username: user.username, 29 | password: user.password 30 | }). 31 | success(function(data) { 32 | $cookieStore.put('token', data.token); 33 | currentUser = User.get(); 34 | deferred.resolve(data); 35 | return cb(); 36 | }). 37 | error(function(err) { 38 | this.logout(); 39 | deferred.reject(err); 40 | return cb(err); 41 | }.bind(this)); 42 | 43 | return deferred.promise; 44 | }, 45 | 46 | /** 47 | * Delete access token and user info 48 | * 49 | * @param {Function} 50 | */ 51 | logout: function() { 52 | $cookieStore.remove('token'); 53 | currentUser = {}; 54 | }, 55 | 56 | /** 57 | * Create a new user 58 | * 59 | * @param {Object} user - user info 60 | * @param {Function} callback - optional 61 | * @return {Promise} 62 | */ 63 | createUser: function(user, callback) { 64 | var cb = callback || angular.noop; 65 | 66 | return User.save(user, 67 | function(data) { 68 | $cookieStore.put('token', data.token); 69 | currentUser = User.get(); 70 | return cb(user); 71 | }, 72 | function(err) { 73 | this.logout(); 74 | return cb(err); 75 | }.bind(this)).$promise; 76 | }, 77 | 78 | /** 79 | * Change password 80 | * 81 | * @param {String} oldPassword 82 | * @param {String} newPassword 83 | * @param {Function} callback - optional 84 | * @return {Promise} 85 | */ 86 | changePassword: function(oldPassword, newPassword, callback) { 87 | var cb = callback || angular.noop; 88 | 89 | return User.changePassword({ id: currentUser._id }, { 90 | oldPassword: oldPassword, 91 | newPassword: newPassword 92 | }, function(user) { 93 | return cb(user); 94 | }, function(err) { 95 | return cb(err); 96 | }).$promise; 97 | }, 98 | 99 | /** 100 | * Gets all available info on authenticated user 101 | * 102 | * @return {Object} user 103 | */ 104 | getCurrentUser: function() { 105 | return currentUser; 106 | }, 107 | 108 | /** 109 | * Check if a user is logged in 110 | * 111 | * @return {Boolean} 112 | */ 113 | isLoggedIn: function() { 114 | return currentUser.hasOwnProperty('username'); 115 | }, 116 | 117 | /** 118 | * Waits for currentUser to resolve before checking if user is logged in 119 | */ 120 | isLoggedInAsync: function(cb) { 121 | if(currentUser.hasOwnProperty('$promise')) { 122 | currentUser.$promise.then(function() { 123 | cb(true); 124 | }).catch(function() { 125 | cb(false); 126 | }); 127 | } else if(currentUser.hasOwnProperty('role')) { 128 | cb(true); 129 | } else { 130 | cb(false); 131 | } 132 | }, 133 | 134 | /** 135 | * Check if a user is an admin 136 | * 137 | * @return {Boolean} 138 | */ 139 | isAdmin: function() { 140 | return currentUser.role === 'admin'; 141 | }, 142 | 143 | /** 144 | * Get auth token 145 | */ 146 | getToken: function() { 147 | return $cookieStore.get('token'); 148 | }, 149 | 150 | /** 151 | * Set help request to be viewed 152 | */ 153 | setHelpRequest: function(id) { 154 | helpRequestId = id; 155 | }, 156 | 157 | getHelpRequest: function() { 158 | return helpRequestId; 159 | }, 160 | 161 | /** 162 | * Set help request to be viewed 163 | */ 164 | setContribution: function(id) { 165 | contributionId = id; 166 | }, 167 | 168 | getContribution: function() { 169 | return contributionId; 170 | } 171 | 172 | 173 | }; 174 | }); 175 | -------------------------------------------------------------------------------- /PRESS-RELEASE.md: -------------------------------------------------------------------------------- 1 | # InkWell # 2 | 3 | 18 | 19 | ## Bust up writers block ## 20 | > Get help on that piece that you're stuck on. 21 | 22 | ## Summary ## 23 | > Inkwell is a collaborative writing platform. It allows you to get help from a community of writers for any project you're on. With a simple, intuitive interface and the features you need, InkWell has been designed for all kinds of writers to allow for a more enjoyable, frustration-free writing experience. InkWell is inpsired by collaborative efforts, such as open-source-software, and brings the community driven experience to writing in a way that pushes writing into the new age. 24 | 25 | ## Problem ## 26 | > The problem is as old as writing itsef: writers block. You're on a roll, but suddenly you're out of ideas and momentum. Tough luck. 27 | 28 | ## Solution ## 29 | > InkWell provides you an environment to get some helpful suggestions on your writing. A community of writers can help you move past your creative stopper and get you putting that story to paper once again. It brings together those that need help with those that are willing to give it, and gives them a platform to share and celebrate these contributions with each other. Ultimately, the project is the help-requester's, but now that writer has options not previously available. 30 | 31 | ## Our Inspiration ## 32 | > "We made InkWell to excell writing into the open-source era. We saw that, as a whole, writing is a solitary effort, though there is no reason to not include the contributions of multiple writers in modern writing. Imagine a book written by hundereds of writers? What would that read like? InkWell is a step in that direction." -Adam Van Antwerp, Scrum Master 33 | 34 | ## How to Get Started ## 35 | > Using InkWell is simple: Just create a new user and log into the site. From there, you are free to submit your own projects to get help with (called help-requests), or help other people on their projects and submit contributions. The interface allows you to search for projects that might be interesting to you, or simply browse through the most recently submitted help requests. From there the only limit is your creativity! 36 | 37 | ## Customer Quote ## 38 | > "Writer's block sucks! Now, I can get some ideas quickly and keep moving forward with my awesome story, while still maintaining owenership of the direction." -Roger Harding, amateur author 39 | 40 | ## Closing and Call to Action ## 41 | > Aspiring authors can visit the site to get started, and curious developers can see all of our progress with the project on github, as the entire project is open source. 42 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 42 | 43 | 44 |
45 | 46 | 47 | 56 | 57 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## General Workflow 4 | 5 | 1. Fork the repo 6 | 1. Cut a namespaced feature branch from master 7 | - bug/... 8 | - feat/... 9 | - test/... 10 | - doc/... 11 | - refactor/... 12 | 1. Make commits to your feature branch. Prefix each commit like so: 13 | - (feat) Added a new feature 14 | - (fix) Fixed inconsistent tests [Fixes #0] 15 | - (refactor) ... 16 | - (cleanup) ... 17 | - (test) ... 18 | - (doc) ... 19 | 1. When you've finished with your fix or feature, Rebase upstream changes into your branch. submit a [pull request][] 20 | directly to master. Include a description of your changes. 21 | 1. Your pull request will be reviewed by another maintainer. The point of code 22 | reviews is to help keep the codebase clean and of high quality and, equally 23 | as important, to help you grow as a programmer. If your code reviewer 24 | requests you make a change you don't understand, ask them why. 25 | 1. Fix any issues raised by your code reviwer, and push your fixes as a single 26 | new commit. 27 | 1. Once the pull request has been reviewed, it will be merged by another member of the team. Do not merge your own commits. 28 | 29 | ## Detailed Workflow 30 | 31 | ### Fork the repo 32 | 33 | Use github’s interface to make a fork of the repo, then add that repo as an upstream remote: 34 | 35 | ``` 36 | git remote add upstream https://github.com/hackreactor-labs/.git 37 | ``` 38 | 39 | ### Cut a namespaced feature branch from master 40 | 41 | Your branch should follow this naming convention: 42 | - bug/... 43 | - feat/... 44 | - test/... 45 | - doc/... 46 | - refactor/... 47 | 48 | These commands will help you do this: 49 | 50 | ``` bash 51 | 52 | # Creates your branch and brings you there 53 | git checkout -b `your-branch-name` 54 | ``` 55 | 56 | ### Make commits to your feature branch. 57 | 58 | Prefix each commit like so 59 | - (feat) Added a new feature 60 | - (fix) Fixed inconsistent tests [Fixes #0] 61 | - (refactor) ... 62 | - (cleanup) ... 63 | - (test) ... 64 | - (doc) ... 65 | 66 | Make changes and commits on your branch, and make sure that you 67 | only make changes that are relevant to this branch. If you find 68 | yourself making unrelated changes, make a new branch for those 69 | changes. 70 | 71 | #### Commit Message Guidelines 72 | 73 | - Commit messages should be written in the present tense; e.g. "Fix continuous 74 | integration script". 75 | - The first line of your commit message should be a brief summary of what the 76 | commit changes. Aim for about 70 characters max. Remember: This is a summary, 77 | not a detailed description of everything that changed. 78 | - If you want to explain the commit in more depth, following the first line should 79 | be a blank line and then a more detailed description of the commit. This can be 80 | as detailed as you want, so dig into details here and keep the first line short. 81 | 82 | ### Rebase upstream changes into your branch 83 | 84 | Once you are done making changes, you can begin the process of getting 85 | your code merged into the main repo. Step 1 is to rebase upstream 86 | changes to the master branch into yours by running this command 87 | from your branch: 88 | 89 | ```bash 90 | git pull --rebase upstream master 91 | ``` 92 | 93 | This will start the rebase process. You must commit all of your changes 94 | before doing this. If there are no conflicts, this should just roll all 95 | of your changes back on top of the changes from upstream, leading to a 96 | nice, clean, linear commit history. 97 | 98 | If there are conflicting changes, git will start yelling at you part way 99 | through the rebasing process. Git will pause rebasing to allow you to sort 100 | out the conflicts. You do this the same way you solve merge conflicts, 101 | by checking all of the files git says have been changed in both histories 102 | and picking the versions you want. Be aware that these changes will show 103 | up in your pull request, so try and incorporate upstream changes as much 104 | as possible. 105 | 106 | You pick a file by `git add`ing it - you do not make commits during a 107 | rebase. 108 | 109 | Once you are done fixing conflicts for a specific commit, run: 110 | 111 | ```bash 112 | git rebase --continue 113 | ``` 114 | 115 | This will continue the rebasing process. Once you are done fixing all 116 | conflicts you should run the existing tests to make sure you didn’t break 117 | anything, then run your new tests (there are new tests, right?) and 118 | make sure they work also. 119 | 120 | If rebasing broke anything, fix it, then repeat the above process until 121 | you get here again and nothing is broken and all the tests pass. 122 | 123 | ### Make a pull request 124 | 125 | Make a clear pull request from your fork and branch to the upstream master 126 | branch, detailing exactly what changes you made and what feature this 127 | should add. The clearer your pull request is the faster you can get 128 | your changes incorporated into this repo. 129 | 130 | At least one other person MUST give your changes a code review, and once 131 | they are satisfied they will merge your changes into upstream. Alternatively, 132 | they may have some requested changes. You should make more commits to your 133 | branch to fix these, then follow this process again from rebasing onwards. 134 | 135 | Once you get back here, make a comment requesting further review and 136 | someone will look at your code again. If they like it, it will get merged, 137 | else, just repeat again. 138 | 139 | Thanks for contributing! 140 | 141 | ### Guidelines 142 | 143 | 1. Uphold the current code standard: 144 | - Keep your code [DRY][]. 145 | - Apply the [boy scout rule][]. 146 | - Follow [STYLE-GUIDE.md](STYLE-GUIDE.md) 147 | 1. Run the [tests][] before submitting a pull request. 148 | 1. Tests are very, very important. Submit tests if your pull request contains 149 | new, testable behavior. 150 | 1. Your pull request is comprised of a single ([squashed][]) commit. 151 | 152 | ## Checklist: 153 | 154 | This is just to help you organize your process 155 | 156 | - [ ] Did I cut my work branch off of master (don't cut new branches from existing feature brances)? 157 | - [ ] Did I follow the correct naming convention for my branch? 158 | - [ ] Is my branch focused on a single main change? 159 | - [ ] Do all of my changes directly relate to this change? 160 | - [ ] Did I rebase the upstream master branch after I finished all my 161 | work? 162 | - [ ] Did I write a clear pull request message detailing what changes I made? 163 | - [ ] Did I get a code review? 164 | - [ ] Did I make any requested changes from that code review? 165 | 166 | If you follow all of these guidelines and make good changes, you should have 167 | no problem getting your changes merged in. 168 | 169 | 170 | 171 | [style guide]: https://github.com/hackreactor-labs/style-guide 172 | [n-queens]: https://github.com/hackreactor-labs/n-queens 173 | [Underbar]: https://github.com/hackreactor-labs/underbar 174 | [curriculum workflow diagram]: http://i.imgur.com/p0e4tQK.png 175 | [cons of merge]: https://f.cloud.github.com/assets/1577682/1458274/1391ac28-435e-11e3-88b6-69c85029c978.png 176 | [Bookstrap]: https://github.com/hackreactor/bookstrap 177 | [Taser]: https://github.com/hackreactor/bookstrap 178 | [tools workflow diagram]: http://i.imgur.com/kzlrDj7.png 179 | [Git Flow]: http://nvie.com/posts/a-successful-git-branching-model/ 180 | [GitHub Flow]: http://scottchacon.com/2011/08/31/github-flow.html 181 | [Squash]: http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html 182 | -------------------------------------------------------------------------------- /STYLE-GUIDE.md: -------------------------------------------------------------------------------- 1 | ### Indentation 2 | 3 | When writing any block of code that is logically subordinate to the line immediately before and after it, that block should be indented two spaces more than the surrounding lines 4 | 5 | * Do not put any tab characters anywhere in your code. You would do best to stop pressing the tab key entirely. 6 | * Increase the indent level for all blocks by two extra spaces 7 | * When a line opens a block, the next line starts 2 spaces further in than the line that opened 8 | 9 | ```javascript 10 | // good: 11 | if(condition){ 12 | action(); 13 | } 14 | 15 | // bad: 16 | if(condition){ 17 | action(); 18 | } 19 | ``` 20 | 21 | * When a line closes a block, that line starts at the same level as the line that opened the block 22 | ```javascript 23 | // good: 24 | if(condition){ 25 | action(); 26 | } 27 | 28 | // bad: 29 | if(condition){ 30 | action(); 31 | } 32 | ``` 33 | 34 | * No two lines should ever have more or less than 2 spaces difference in their indentation. Any number of mistakes in the above rules could lead to this, but one example would be: 35 | 36 | ```javascript 37 | // bad: 38 | transmogrify({ 39 | a: { 40 | b: function(){ 41 | } 42 | }}); 43 | ``` 44 | 45 | * use sublime's arrow collapsing as a guide. do the collapsing lines seem like they should be 'contained' by the line with an arrow on it? 46 | 47 | 48 | ### Variable names 49 | 50 | * A single descriptive word is best. 51 | 52 | ```javascript 53 | // good: 54 | var animals = ['cat', 'dog', 'fish']; 55 | 56 | // bad: 57 | var targetInputs = ['cat', 'dog', 'fish']; 58 | ``` 59 | 60 | * Collections such as arrays and maps should have plural noun variable names. 61 | 62 | ```javascript 63 | // good: 64 | var animals = ['cat', 'dog', 'fish']; 65 | 66 | // bad: 67 | var animalList = ['cat', 'dog', 'fish']; 68 | 69 | // bad: 70 | var animal = ['cat', 'dog', 'fish']; 71 | ``` 72 | 73 | * Name your variables after their purpose, not their structure 74 | 75 | ```javascript 76 | // good: 77 | var animals = ['cat', 'dog', 'fish']; 78 | 79 | // bad: 80 | var array = ['cat', 'dog', 'fish']; 81 | ``` 82 | 83 | 84 | ### Language constructs 85 | 86 | * Do not use `for...in` statements with the intent of iterating over a list of numeric keys. Use a for-with-semicolons statement in stead. 87 | 88 | ```javascript 89 | // good: 90 | var list = ['a', 'b', 'c'] 91 | for(var i = 0; i < list.length; i++){ 92 | alert(list[i]); 93 | } 94 | 95 | // bad: 96 | var list = ['a', 'b', 'c'] 97 | for(var i in list){ 98 | alert(list[i]); 99 | } 100 | ``` 101 | 102 | * Never omit braces for statement blocks (although they are technically optional). 103 | ```javascript 104 | // good: 105 | for(key in object){ 106 | alert(key); 107 | } 108 | 109 | // bad: 110 | for(key in object) 111 | alert(key); 112 | ``` 113 | 114 | * Always use `===` and `!==`, since `==` and `!=` will automatically convert types in ways you're unlikely to expect. 115 | 116 | ```javascript 117 | // good: 118 | 119 | // this comparison evaluates to false, because the number zero is not the same as the empty string. 120 | if(0 === ''){ 121 | alert('looks like they\'re equal'); 122 | } 123 | 124 | // bad: 125 | 126 | // This comparison evaluates to true, because after type coercion, zero and the empty string are equal. 127 | if(0 == ''){ 128 | alert('looks like they\'re equal'); 129 | } 130 | ``` 131 | 132 | * Don't use function statements for the entire first half of the course. They introduce a slew of subtle new rules to how the language behaves, and without a clear benefit. Once you and all your peers are expert level in the second half, you can start to use the more (needlessly) complicated option if you like. 133 | 134 | ```javascript 135 | // good: 136 | var go = function(){...}; 137 | 138 | // bad: 139 | function stop(){...}; 140 | ``` 141 | 142 | 143 | ### Semicolons 144 | 145 | * Don't forget semicolons at the end of lines 146 | 147 | ```javascript 148 | // good: 149 | alert('hi'); 150 | 151 | // bad: 152 | alert('hi') 153 | ``` 154 | 155 | * Semicolons are not required at the end of statements that include a block--i.e. `if`, `for`, `while`, etc. 156 | 157 | 158 | ```javascript 159 | // good: 160 | if(condition){ 161 | response(); 162 | } 163 | 164 | // bad: 165 | if(condition){ 166 | response(); 167 | }; 168 | ``` 169 | 170 | * Misleadingly, a function may be used at the end of a normal assignment statement, and would require a semicolon (even though it looks rather like the end of some statement block). 171 | 172 | ```javascript 173 | // good: 174 | var greet = function(){ 175 | alert('hi'); 176 | }; 177 | 178 | // bad: 179 | var greet = function(){ 180 | alert('hi'); 181 | } 182 | ``` 183 | 184 | # Supplemental reading 185 | 186 | ### Code density 187 | 188 | * Conserve line quantity by minimizing the number lines you write in. The more concisely your code is written, the more context can be seen in one screen. 189 | * Conserve line length by minimizing the amount of complexity you put on each line. Long lines are difficult to read. Rather than a character count limit, I recommend limiting the amount of complexity you put on a single line. Try to make it easily read in one glance. This goal is in conflict with the line quantity goal, so you must do your best to balance them. 190 | 191 | ### Comments 192 | 193 | * Provide comments any time you are confident it will make reading your code easier. 194 | * Be aware that comments come at some cost. They make a file longer and can drift out of sync with the code they annotate. 195 | * Comment on what code is attempting to do, not how it will achieve it. 196 | * A good comment is often less effective than a good variable name. 197 | 198 | 199 | ### Padding & additional whitespace 200 | 201 | * Generally, we don't care where you put extra spaces, provided they are not distracting. 202 | * You may use it as padding for visual clarity. If you do though, make sure it's balanced on both sides. 203 | 204 | ```javascript 205 | // optional: 206 | alert( "I chose to put visual padding around this string" ); 207 | 208 | // bad: 209 | alert( "I only put visual padding on one side of this string"); 210 | ``` 211 | 212 | * You may use it to align two similar lines, but it is not recommended. This pattern usually leads to unnecessary edits of many lines in your code every time you change a variable name. 213 | 214 | ```javascript 215 | // discouraged: 216 | var firstItem = getFirst (); 217 | var secondItem = getSecond(); 218 | ``` 219 | 220 | * Put `else` and `else if` statements on the same line as the ending curly brace for the preceding `if` block 221 | ```javascript 222 | // good: 223 | if(condition){ 224 | response(); 225 | }else{ 226 | otherResponse(); 227 | } 228 | 229 | // bad: 230 | if(condition){ 231 | response(); 232 | } 233 | else{ 234 | otherResponse(); 235 | } 236 | ``` 237 | 238 | 239 | 240 | ### Working with files 241 | 242 | * Do not end a file with any character other than a newline. 243 | * Don't use the -a or -m flags for `git commit` for the first half of the class, since they conceal what is actually happening (and do slightly different things than most people expect). 244 | 245 | ```shell 246 | # good: 247 | > git add . 248 | > git commit 249 | [save edits to the commit message file using the text editor that opens] 250 | 251 | # bad: 252 | > git commit -a 253 | [save edits to the commit message file using the text editor that opens] 254 | 255 | # bad: 256 | > git add . 257 | > git commit -m "updated algorithm" 258 | ``` 259 | 260 | 261 | ### Opening or closing too many blocks at once 262 | 263 | * The more blocks you open on a single line, the more your reader needs to remember about the context of what they are reading. Try to resolve your blocks early, and refactor. A good rule is to avoid closing more than two blocks on a single line--three in a pinch. 264 | 265 | ```javascript 266 | // avoid: 267 | _.ajax(url, {success: function(){ 268 | // ... 269 | }}); 270 | 271 | // prefer: 272 | _.ajax(url, { 273 | success: function(){ 274 | // ... 275 | } 276 | }); 277 | ``` 278 | 279 | 280 | ### Variable declaration 281 | 282 | * Use a new var statement for each line you declare a variable on. 283 | * Do not break variable declarations onto mutiple lines. 284 | * Use a new line for each variable declaration. 285 | * See http://benalman.com/news/2012/05/multiple-var-statements-javascript/ for more details 286 | 287 | ```javascript 288 | // good: 289 | var ape; 290 | var bat; 291 | 292 | // bad: 293 | var cat, 294 | dog 295 | 296 | // use sparingly: 297 | var eel, fly; 298 | ``` 299 | 300 | ### Capital letters in variable names 301 | 302 | * Some people choose to use capitalization of the first letter in their variable names to indicate that they contain a [class](http://en.wikipedia.org/wiki/Class_(computer_science\)). This capitalized variable might contain a function, a prototype, or some other construct that acts as a representative for the whole class. 303 | * Optionally, some people use a capital letter only on functions that are written to be run with the keyword `new`. 304 | * Do not use all-caps for any variables. Some people use this pattern to indicate an intended "constant" variable, but the language does not offer true constants, only mutable variables. 305 | 306 | 307 | ### Minutia 308 | 309 | * Don't rely on JavaScripts implicit global variables. If you are intending to write to the global scope, export things to `window.*` explicitly instead. 310 | 311 | ```javascript 312 | // good: 313 | var overwriteNumber = function(){ 314 | window.exported = Math.random(); 315 | }; 316 | 317 | // bad: 318 | var overwriteNumber = function(){ 319 | exported = Math.random(); 320 | }; 321 | ``` 322 | 323 | * For lists, put commas at the end of each newline, not at the beginning of each item in a list 324 | 325 | ```javascript 326 | // good: 327 | var animals = [ 328 | 'ape', 329 | 'bat', 330 | 'cat' 331 | ]; 332 | 333 | // bad: 334 | var animals = [ 335 | 'ape' 336 | , 'bat' 337 | , 'cat' 338 | ]; 339 | ``` 340 | 341 | * Avoid use of `switch` statements altogether. They are hard to outdent using the standard whitespace rules above, and are prone to error due to missing `break` statements. See [this article](http://ericleads.com/2012/12/switch-case-considered-harmful/) for more detail. 342 | 343 | * Prefer single quotes around JavaScript strings, rather than double quotes. Having a standard of any sort is preferable to a mix-and-match approach, and single quotes allow for easy embedding of HTML, which prefers double quotes around tag attributes. 344 | 345 | ```javascript 346 | // good: 347 | var dog = 'dog'; 348 | var cat = 'cat'; 349 | 350 | // acceptable: 351 | var dog = "dog"; 352 | var cat = "cat"; 353 | 354 | // bad: 355 | var dog = 'dog'; 356 | var cat = "cat"; 357 | ``` 358 | 359 | 360 | ### HTML 361 | 362 | * Do not use ids for html elements. Use a class instead. 363 | 364 | ```html 365 | 366 | 367 | 368 | 369 | 370 | ``` 371 | 372 | * Do not include a `type=text/javascript"` attribute on script tags 373 | 374 | ```html 375 | 376 | 377 | 378 | 379 | 380 | ``` 381 | -------------------------------------------------------------------------------- /server/models/helpers.js: -------------------------------------------------------------------------------- 1 | var Sequelize = require('sequelize'); 2 | var User = require('./user.js'); 3 | var Project = require('./project.js'); 4 | var ProjectUpvote = require('./projectupvotes.js'); 5 | var Contribution = require('./contributions.js'); 6 | var ContributionUpvote = require('./contributionupvotes.js'); 7 | var ContributionComment = require('./contributioncomments.js'); 8 | var ProjectComment = require('./projectcomments.js'); 9 | var associations = require('./associations.js'); 10 | var jwt = require('jsonwebtoken'); 11 | 12 | module.exports.authenticate = function(username, password, response, secret) { 13 | User.find({where: {username: username}}).then(function(user) { 14 | if(user) { 15 | 16 | if (user.checkPassword(password)) { 17 | var profile = { 18 | username: user.username, 19 | email: user.email 20 | }; 21 | console.log("User authenticated!"); 22 | response.json({token: jwt.sign(profile, secret, { expiresInMinutes: 60 * 5})}); 23 | } else { 24 | console.log("Bad Password, Charlie"); 25 | response.send(401, "Wrong username or password"); 26 | } 27 | } else { 28 | 29 | console.log("Whoops, user doesn't exsist"); 30 | response.send(401, "Wrong username or password"); 31 | 32 | } 33 | }); 34 | }; 35 | 36 | 37 | module.exports.findAllInfo = function(username, response) { 38 | //finding the logged in user 39 | User.find({where: {username:username}}).then(function(user){ 40 | if (user) { 41 | //finding all their projects 42 | Project.findAll({where: {user_id: user.id}}).then(function(allProjects){ 43 | var projectsArray = []; 44 | var uniqueProjects = []; 45 | for (var i = 0; i < allProjects.length; i++) { 46 | projectsArray.push(allProjects[i].dataValues); 47 | var projectId = allProjects[i].dataValues.id; 48 | uniqueProjects.push(projectId); 49 | } 50 | //finding all their contributions 51 | Contribution.findAll({where: {contributor: user.id}}).then(function(allContributions) { 52 | var contributionsArray = []; 53 | var uniqueContributions = []; 54 | for (var j = 0; j< allContributions.length; j ++) { 55 | contributionsArray.push(allContributions[j].dataValues); 56 | var contributionId = allContributions[j].dataValues.id; 57 | if (uniqueContributions.indexOf(contributionId) < 0) { 58 | uniqueContributions.push(contributionId); 59 | } 60 | } 61 | //finding and counting all unseenhelprequests 62 | Contribution.findAndCountAll({where:{project: uniqueProjects, unseenHelp: true}}).then(function(unseenHelps) { 63 | //finding and counting all unseencomments from projects 64 | ProjectComment.findAndCountAll({where:{projectCommented: uniqueProjects, unseenComment: false}}).then(function(unseenProjectComments) { 65 | //finding and counting all unseencomments from contributions 66 | ContributionComment.findAndCountAll({where:{contributionCommented: uniqueContributions, unseenComment: false}}).then(function(unseenContributionComments) { 67 | //finding and counting all votes for projects 68 | ProjectUpvote.findAndCountAll({where:{projectupvoted: uniqueProjects}}).then(function(projectUpvotes) { 69 | //finding and counting all votes for contributions 70 | ContributionUpvote.findAndCountAll({where: {contributionupvoted: uniqueContributions}}).then(function(contributionUpvotes) { 71 | //finding all the contributions for a user - joint search 72 | Contribution.findAll({where: {contributor: user.id}, include:[User]}).then(function(userContributions) { 73 | var contributionDetails = []; 74 | for (var k = 0 ; k < userContributions.length; k++ ) { 75 | contributionDetails.push({ 76 | id: userContributions[k].dataValues.id, 77 | helperUsername: userContributions[0].dataValues.User.dataValues.username, 78 | textSnippet: userContributions[k].dataValues.contributionText, 79 | origDate: userContributions[k].dataValues.createdAt 80 | }); 81 | }; 82 | var profile = { 83 | username: user.username, 84 | email: user.email, 85 | helpRequests: projectsArray, 86 | contributions: contributionDetails, 87 | numberUnseenHelps: unseenHelps.count, 88 | numberUnseenComments: unseenProjectComments.count + unseenContributionComments.count, 89 | votes: projectUpvotes.count + contributionUpvotes.count 90 | } 91 | console.log("Delivering profile"); 92 | response.json(profile); 93 | }); 94 | }); 95 | }); 96 | }); 97 | }); 98 | }); 99 | }); 100 | }); 101 | } else { 102 | response.send(401, "Error - how can you not be found when logged in?"); 103 | } 104 | }) 105 | }; 106 | 107 | module.exports.searchOrMake = function(username, email, password, response, secret) { 108 | User.find({where: {username: username}}).then(function(user) { 109 | if(user) { 110 | console.log('User exists'); 111 | response.send(401, "That user already exists!"); 112 | } else { 113 | User.create({username: username, email: email}).then(function(user) { 114 | user.password = user.setPassword(password); 115 | var profile = { 116 | username: user.username, 117 | email: user.email 118 | }; 119 | console.log("User created"); 120 | response.json({token: jwt.sign(profile, secret, { expiresInMinutes: 60 * 5})}); 121 | }) 122 | } 123 | }); 124 | }; 125 | 126 | module.exports.helpRequest = function(username, project, response) { 127 | User.find({where: {username: username}}).then(function(user) { 128 | if(user) { 129 | Project.create({title: project.title, summary: project.summary, text: project.text, user_id: user.id}).then(function(createdProject) { 130 | var projectDetails = { 131 | id: createdProject.id, 132 | title: createdProject.title, 133 | summary: createdProject.summary, 134 | origDate: createdProject.createdAt 135 | }; 136 | console.log("Passing back token"); 137 | response.json(projectDetails); 138 | }) 139 | } else { 140 | console.log("Error while creating project"); 141 | } 142 | }); 143 | }; 144 | 145 | module.exports.projectUpvote = function(userId, projectId, response) { 146 | ProjectUpvote.create({upvoter: userId, projectupvoted: projectId}).then(function() { 147 | response.send(201, "Project upvoted"); 148 | }) 149 | }; 150 | 151 | module.exports.viewProject = function(projectId, response) { 152 | Project.find({where: {id: projectId}, include: [User]}).then(function(project) { 153 | if(project) { 154 | Contribution.findAll({where: {project: projectId}, include:[User]}).then(function(projectContributions) { 155 | var contributionDetails = []; 156 | for (var k = 0 ; k < projectContributions.length; k++ ) { 157 | contributionDetails.push({ 158 | id: projectContributions[k].dataValues.id, 159 | username: project.dataValues.User.dataValues.username, 160 | helperUsername: projectContributions[k].dataValues.User.dataValues.username, 161 | textSnippet: projectContributions[k].dataValues.contributionText, 162 | origDate: projectContributions[k].dataValues.createdAt 163 | }); 164 | }; 165 | ProjectUpvote.findAndCountAll({where:{projectupvoted: projectId}}).then(function(projectvotes) { 166 | 167 | var projectDetails = { 168 | title: project.title, 169 | summary: project.summary, 170 | text: project.text, 171 | username: project.dataValues.User.dataValues.username, 172 | votes: projectvotes.count, 173 | contributions: contributionDetails, 174 | origDate: project.createdAt 175 | }; 176 | console.log("Showing project details"); 177 | response.json(projectDetails); 178 | }); 179 | }); 180 | } else { 181 | console.log("Error while finding project"); 182 | } 183 | }); 184 | }; 185 | 186 | module.exports.makeContribution = function(username, contribution, response) { 187 | User.find({where: {username: username}}).then(function(user) { 188 | if(user) { 189 | Contribution.create({contributor: user.id, project: contribution.helpedId, contributionText: contribution.text, unseenHelp: true}).then(function(contributionCreated) { 190 | Project.find({where: {id: contribution.helpedId}, include:[User]}).then(function(project) { 191 | var projectDetails = { 192 | id: contributionCreated.id, 193 | helpedUsername: project.dataValues.User.dataValues.username, 194 | title: project.title, 195 | summary: project.summary, 196 | origDate: project.createdAt 197 | } 198 | response.json(projectDetails); 199 | }); 200 | }); 201 | } else { 202 | console.log("Error while making contribution"); 203 | } 204 | }); 205 | }; 206 | 207 | module.exports.contributionComment = function(contribution, response) { 208 | User.find({where: {username: contribution.commenter}}).then(function(user) { 209 | if(user) { 210 | ContributionComment.create({comment: contribution.text, unseenComment: false, commenter: user.id, contributionCommented: contribution.contributionId}).then(function() { 211 | response.send(201, "Contribution comment made"); 212 | }); 213 | } else { 214 | console.log("Error while making contribution comment"); 215 | } 216 | }); 217 | }; 218 | 219 | module.exports.contributionUpvote = function(userId, contributionId, response) { 220 | ContributionUpvote.create({upvoter: userId, contributionupvoted: contributionId}).then(function() { 221 | response.send(201, "Contribution upvoted"); 222 | }) 223 | }; 224 | 225 | module.exports.viewContribution = function(contributionId, decoded, response) { 226 | Contribution.find({where: {id: contributionId}}).then(function(contribution) { 227 | if(contribution) { 228 | ContributionComment.findAll({where: {contributionCommented: contributionId}, include: [User]}).then(function(contributionComments) { 229 | var allContributionComments = []; 230 | for (var i = 0; i < contributionComments.length; i++) { 231 | allContributionComments.push({ 232 | comment: contributionComments[i].dataValues.comment, 233 | unseenComment: contributionComments[i].dataValues.unseenComment, 234 | commenter: contributionComments[i].dataValues.User.username, 235 | origDate: contributionComments[i].dataValues.createdAt 236 | }); 237 | } 238 | Project.find({where: {id: contribution.project}}).then(function(project) { 239 | User.find({where: {id: contribution.contributor}}).then(function(contributingUser) { 240 | User.find({where: {id: project.user_id}}).then(function(helpedUser) { 241 | ContributionUpvote.findAndCountAll({where:{contributionupvoted: contributionId}}).then(function(contributionvotes) { 242 | var contributionDetails = { 243 | contributionText: contribution.contributionText, 244 | comments: allContributionComments, 245 | date: contribution.createdAt, 246 | helpRequestText: project.text, 247 | contributor: contributingUser.username, 248 | userHelped: helpedUser.username, 249 | votes: contributionvotes.count 250 | }; 251 | console.log("Showing contribution details"); 252 | if (decoded.username === helpedUser.username) { 253 | console.log("Help request owner has seen!"); 254 | contribution.update({unseenHelp : false}).then(function(){ 255 | response.json(contributionDetails); 256 | }); 257 | 258 | } else { 259 | response.json(contributionDetails); 260 | } 261 | 262 | }); 263 | }); 264 | }); 265 | }); 266 | }); 267 | } else { 268 | console.log("Error while finding contribution"); 269 | } 270 | }); 271 | }; 272 | 273 | module.exports.searching = function(searchString, response) { 274 | Project.findAll({where: ["title LIKE ? or summary LIKE ?", '%' + searchString+ '%', '%' + searchString+ '%'] }).then(function(projects) { 275 | if (projects) { 276 | var results = []; 277 | var projectLength; 278 | if (projects.length > 10) { 279 | projectLength = 10; 280 | } else { 281 | projectLength = projects.length; 282 | } 283 | for (var i = 0; i < projectLength; i ++) { 284 | results.push({ 285 | id: projects[i].dataValues.id, 286 | title: projects[i].dataValues.title, 287 | summary: projects[i].dataValues.summary, 288 | origDate: projects[i].dataValues.createdAt 289 | }) 290 | } 291 | console.log("Sending back search results"); 292 | response.json(results); 293 | } else { 294 | console.log("No search results"); 295 | } 296 | }); 297 | }; 298 | 299 | module.exports.getAll = function(response) { 300 | Project.findAll({include: [User]}).then(function(projects) { 301 | var allProjects = []; 302 | for (var i = 0; i < projects.length; i ++) { 303 | allProjects.push({ 304 | id: projects[i].dataValues.id, 305 | title: projects[i].dataValues.title, 306 | summary: projects[i].dataValues.summary, 307 | origDate: projects[i].dataValues.createdAt, 308 | username: projects[i].dataValues.User.dataValues.username 309 | }) 310 | } 311 | response.json(allProjects); 312 | }); 313 | } 314 | 315 | module.exports.checkUnseenContributions = function(username, response) { 316 | User.find({where: {username: username}}).then(function(user) { 317 | Project.findAll({where: {user_id: user.dataValues.id}}).then(function(userProjects){ 318 | var uniqueProjects = []; 319 | for (var i = 0; i < userProjects.length; i ++) { 320 | var projectId = userProjects[i].dataValues.id; 321 | if (uniqueProjects.indexOf(projectId) < 0) { 322 | uniqueProjects.push(projectId); 323 | } 324 | } 325 | Contribution.findAll({where: {project: uniqueProjects}, include: [Project, User]}).then(function(projectContributions) { 326 | var contributionDetails = []; 327 | for (var j = 0; j < projectContributions.length; j++) { 328 | contributionDetails.push({ 329 | contributor: projectContributions[j].dataValues.User.dataValues.username, 330 | contributionId: projectContributions[j].dataValues.id, 331 | contributionText: projectContributions[j].dataValues.contributionText, 332 | title: projectContributions[j].dataValues.Project.dataValues.title, 333 | unseen: projectContributions[j].dataValues.unseenHelp 334 | }); 335 | } 336 | response.json(contributionDetails); 337 | }); 338 | }); 339 | }); 340 | } 341 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on 2015-04-29 using generator-angular-fullstack 2.0.13 2 | 'use strict'; 3 | 4 | module.exports = function (grunt) { 5 | var localConfig; 6 | try { 7 | localConfig = require('./server/config/local.env'); 8 | } catch(e) { 9 | localConfig = {}; 10 | } 11 | 12 | // Load grunt tasks automatically, when needed 13 | require('jit-grunt')(grunt, { 14 | express: 'grunt-express-server', 15 | useminPrepare: 'grunt-usemin', 16 | ngtemplates: 'grunt-angular-templates', 17 | cdnify: 'grunt-google-cdn', 18 | protractor: 'grunt-protractor-runner', 19 | injector: 'grunt-asset-injector', 20 | buildcontrol: 'grunt-build-control' 21 | }); 22 | 23 | // Time how long tasks take. Can help when optimizing build times 24 | require('time-grunt')(grunt); 25 | 26 | // Define the configuration for all the tasks 27 | grunt.initConfig({ 28 | 29 | // Project settings 30 | pkg: grunt.file.readJSON('package.json'), 31 | yeoman: { 32 | // configurable paths 33 | client: require('./bower.json').appPath || 'client', 34 | dist: 'dist' 35 | }, 36 | express: { 37 | options: { 38 | port: process.env.PORT || 9000 39 | }, 40 | dev: { 41 | options: { 42 | script: 'server/app.js', 43 | debug: true 44 | } 45 | }, 46 | prod: { 47 | options: { 48 | script: 'dist/server/app.js' 49 | } 50 | } 51 | }, 52 | open: { 53 | server: { 54 | url: 'http://localhost:<%= express.options.port %>' 55 | } 56 | }, 57 | watch: { 58 | injectJS: { 59 | files: [ 60 | '<%= yeoman.client %>/{app,components}/**/*.js', 61 | '!<%= yeoman.client %>/{app,components}/**/*.spec.js', 62 | '!<%= yeoman.client %>/{app,components}/**/*.mock.js', 63 | '!<%= yeoman.client %>/app/app.js'], 64 | tasks: ['injector:scripts'] 65 | }, 66 | injectCss: { 67 | files: [ 68 | '<%= yeoman.client %>/{app,components}/**/*.css' 69 | ], 70 | tasks: ['injector:css'] 71 | }, 72 | mochaTest: { 73 | files: ['server/**/*.spec.js'], 74 | tasks: ['env:test', 'mochaTest'] 75 | }, 76 | jsTest: { 77 | files: [ 78 | '<%= yeoman.client %>/{app,components}/**/*.spec.js', 79 | '<%= yeoman.client %>/{app,components}/**/*.mock.js' 80 | ], 81 | tasks: ['newer:jshint:all', 'karma'] 82 | }, 83 | gruntfile: { 84 | files: ['Gruntfile.js'] 85 | }, 86 | livereload: { 87 | files: [ 88 | '{.tmp,<%= yeoman.client %>}/{app,components}/**/*.css', 89 | '{.tmp,<%= yeoman.client %>}/{app,components}/**/*.html', 90 | '{.tmp,<%= yeoman.client %>}/{app,components}/**/*.js', 91 | '!{.tmp,<%= yeoman.client %>}{app,components}/**/*.spec.js', 92 | '!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.mock.js', 93 | '<%= yeoman.client %>/assets/images/{,*//*}*.{png,jpg,jpeg,gif,webp,svg}' 94 | ], 95 | options: { 96 | livereload: true 97 | } 98 | }, 99 | express: { 100 | files: [ 101 | 'server/**/*.{js,json}' 102 | ], 103 | tasks: ['express:dev', 'wait'], 104 | options: { 105 | livereload: true, 106 | nospawn: true //Without this option specified express won't be reloaded 107 | } 108 | } 109 | }, 110 | 111 | // Make sure code styles are up to par and there are no obvious mistakes 112 | jshint: { 113 | options: { 114 | jshintrc: '<%= yeoman.client %>/.jshintrc', 115 | reporter: require('jshint-stylish') 116 | }, 117 | server: { 118 | options: { 119 | jshintrc: 'server/.jshintrc' 120 | }, 121 | src: [ 122 | 'server/**/*.js', 123 | '!server/**/*.spec.js' 124 | ] 125 | }, 126 | serverTest: { 127 | options: { 128 | jshintrc: 'server/.jshintrc-spec' 129 | }, 130 | src: ['server/**/*.spec.js'] 131 | }, 132 | all: [ 133 | '<%= yeoman.client %>/{app,components}/**/*.js', 134 | '!<%= yeoman.client %>/{app,components}/**/*.spec.js', 135 | '!<%= yeoman.client %>/{app,components}/**/*.mock.js' 136 | ], 137 | test: { 138 | src: [ 139 | '<%= yeoman.client %>/{app,components}/**/*.spec.js', 140 | '<%= yeoman.client %>/{app,components}/**/*.mock.js' 141 | ] 142 | } 143 | }, 144 | 145 | // Empties folders to start fresh 146 | clean: { 147 | dist: { 148 | files: [{ 149 | dot: true, 150 | src: [ 151 | '.tmp', 152 | '<%= yeoman.dist %>/*', 153 | '!<%= yeoman.dist %>/.git*', 154 | '!<%= yeoman.dist %>/.openshift', 155 | '!<%= yeoman.dist %>/Procfile' 156 | ] 157 | }] 158 | }, 159 | server: '.tmp' 160 | }, 161 | 162 | // Add vendor prefixed styles 163 | autoprefixer: { 164 | options: { 165 | browsers: ['last 1 version'] 166 | }, 167 | dist: { 168 | files: [{ 169 | expand: true, 170 | cwd: '.tmp/', 171 | src: '{,*/}*.css', 172 | dest: '.tmp/' 173 | }] 174 | } 175 | }, 176 | 177 | // Debugging with node inspector 178 | 'node-inspector': { 179 | custom: { 180 | options: { 181 | 'web-host': 'localhost' 182 | } 183 | } 184 | }, 185 | 186 | // Use nodemon to run server in debug mode with an initial breakpoint 187 | nodemon: { 188 | debug: { 189 | script: 'server/app.js', 190 | options: { 191 | nodeArgs: ['--debug-brk'], 192 | env: { 193 | PORT: process.env.PORT || 9000 194 | }, 195 | callback: function (nodemon) { 196 | nodemon.on('log', function (event) { 197 | console.log(event.colour); 198 | }); 199 | 200 | // opens browser on initial server start 201 | nodemon.on('config:update', function () { 202 | setTimeout(function () { 203 | require('open')('http://localhost:8080/debug?port=5858'); 204 | }, 500); 205 | }); 206 | } 207 | } 208 | } 209 | }, 210 | 211 | // Automatically inject Bower components into the app 212 | wiredep: { 213 | target: { 214 | src: '<%= yeoman.client %>/index.html', 215 | ignorePath: '<%= yeoman.client %>/', 216 | exclude: [/bootstrap-sass-official/, /bootstrap.js/, '/json3/', '/es5-shim/'] 217 | } 218 | }, 219 | 220 | // Renames files for browser caching purposes 221 | rev: { 222 | dist: { 223 | files: { 224 | src: [ 225 | '<%= yeoman.dist %>/public/{,*/}*.js', 226 | '<%= yeoman.dist %>/public/{,*/}*.css', 227 | '<%= yeoman.dist %>/public/assets/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', 228 | '<%= yeoman.dist %>/public/assets/fonts/*' 229 | ] 230 | } 231 | } 232 | }, 233 | 234 | // Reads HTML for usemin blocks to enable smart builds that automatically 235 | // concat, minify and revision files. Creates configurations in memory so 236 | // additional tasks can operate on them 237 | useminPrepare: { 238 | html: ['<%= yeoman.client %>/index.html'], 239 | options: { 240 | dest: '<%= yeoman.dist %>/public' 241 | } 242 | }, 243 | 244 | // Performs rewrites based on rev and the useminPrepare configuration 245 | usemin: { 246 | html: ['<%= yeoman.dist %>/public/{,*/}*.html'], 247 | css: ['<%= yeoman.dist %>/public/{,*/}*.css'], 248 | js: ['<%= yeoman.dist %>/public/{,*/}*.js'], 249 | options: { 250 | assetsDirs: [ 251 | '<%= yeoman.dist %>/public', 252 | '<%= yeoman.dist %>/public/assets/images' 253 | ], 254 | // This is so we update image references in our ng-templates 255 | patterns: { 256 | js: [ 257 | [/(assets\/images\/.*?\.(?:gif|jpeg|jpg|png|webp|svg))/gm, 'Update the JS to reference our revved images'] 258 | ] 259 | } 260 | } 261 | }, 262 | 263 | // The following *-min tasks produce minified files in the dist folder 264 | imagemin: { 265 | dist: { 266 | files: [{ 267 | expand: true, 268 | cwd: '<%= yeoman.client %>/assets/images', 269 | src: '{,*/}*.{png,jpg,jpeg,gif}', 270 | dest: '<%= yeoman.dist %>/public/assets/images' 271 | }] 272 | } 273 | }, 274 | 275 | svgmin: { 276 | dist: { 277 | files: [{ 278 | expand: true, 279 | cwd: '<%= yeoman.client %>/assets/images', 280 | src: '{,*/}*.svg', 281 | dest: '<%= yeoman.dist %>/public/assets/images' 282 | }] 283 | } 284 | }, 285 | 286 | // Allow the use of non-minsafe AngularJS files. Automatically makes it 287 | // minsafe compatible so Uglify does not destroy the ng references 288 | ngAnnotate: { 289 | dist: { 290 | files: [{ 291 | expand: true, 292 | cwd: '.tmp/concat', 293 | src: '*/**.js', 294 | dest: '.tmp/concat' 295 | }] 296 | } 297 | }, 298 | 299 | // Package all the html partials into a single javascript payload 300 | ngtemplates: { 301 | options: { 302 | // This should be the name of your apps angular module 303 | module: 'familyThiefApp', 304 | htmlmin: { 305 | collapseBooleanAttributes: true, 306 | collapseWhitespace: true, 307 | removeAttributeQuotes: true, 308 | removeEmptyAttributes: true, 309 | removeRedundantAttributes: true, 310 | removeScriptTypeAttributes: true, 311 | removeStyleLinkTypeAttributes: true 312 | }, 313 | usemin: 'app/app.js' 314 | }, 315 | main: { 316 | cwd: '<%= yeoman.client %>', 317 | src: ['{app,components}/**/*.html'], 318 | dest: '.tmp/templates.js' 319 | }, 320 | tmp: { 321 | cwd: '.tmp', 322 | src: ['{app,components}/**/*.html'], 323 | dest: '.tmp/tmp-templates.js' 324 | } 325 | }, 326 | 327 | // Replace Google CDN references 328 | cdnify: { 329 | dist: { 330 | html: ['<%= yeoman.dist %>/public/*.html'] 331 | } 332 | }, 333 | 334 | // Copies remaining files to places other tasks can use 335 | copy: { 336 | dist: { 337 | files: [{ 338 | expand: true, 339 | dot: true, 340 | cwd: '<%= yeoman.client %>', 341 | dest: '<%= yeoman.dist %>/public', 342 | src: [ 343 | '*.{ico,png,txt}', 344 | '.htaccess', 345 | 'bower_components/**/*', 346 | 'assets/images/{,*/}*.{webp}', 347 | 'assets/fonts/**/*', 348 | 'index.html' 349 | ] 350 | }, { 351 | expand: true, 352 | cwd: '.tmp/images', 353 | dest: '<%= yeoman.dist %>/public/assets/images', 354 | src: ['generated/*'] 355 | }, { 356 | expand: true, 357 | dest: '<%= yeoman.dist %>', 358 | src: [ 359 | 'package.json', 360 | 'server/**/*' 361 | ] 362 | }] 363 | }, 364 | styles: { 365 | expand: true, 366 | cwd: '<%= yeoman.client %>', 367 | dest: '.tmp/', 368 | src: ['{app,components}/**/*.css'] 369 | } 370 | }, 371 | 372 | buildcontrol: { 373 | options: { 374 | dir: 'dist', 375 | commit: true, 376 | push: true, 377 | connectCommits: false, 378 | message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%' 379 | }, 380 | heroku: { 381 | options: { 382 | remote: 'heroku', 383 | branch: 'master' 384 | } 385 | }, 386 | openshift: { 387 | options: { 388 | remote: 'openshift', 389 | branch: 'master' 390 | } 391 | } 392 | }, 393 | 394 | // Run some tasks in parallel to speed up the build process 395 | concurrent: { 396 | server: [ 397 | ], 398 | test: [ 399 | ], 400 | debug: { 401 | tasks: [ 402 | 'nodemon', 403 | 'node-inspector' 404 | ], 405 | options: { 406 | logConcurrentOutput: true 407 | } 408 | }, 409 | dist: [ 410 | 'imagemin', 411 | 'svgmin' 412 | ] 413 | }, 414 | 415 | // Test settings 416 | karma: { 417 | unit: { 418 | configFile: 'karma.conf.js', 419 | singleRun: true 420 | } 421 | }, 422 | 423 | mochaTest: { 424 | options: { 425 | reporter: 'spec' 426 | }, 427 | src: ['server/**/*.spec.js'] 428 | }, 429 | 430 | protractor: { 431 | options: { 432 | configFile: 'protractor.conf.js' 433 | }, 434 | chrome: { 435 | options: { 436 | args: { 437 | browser: 'chrome' 438 | } 439 | } 440 | } 441 | }, 442 | 443 | env: { 444 | test: { 445 | NODE_ENV: 'test' 446 | }, 447 | prod: { 448 | NODE_ENV: 'production' 449 | }, 450 | all: localConfig 451 | }, 452 | 453 | injector: { 454 | options: { 455 | 456 | }, 457 | // Inject application script files into index.html (doesn't include bower) 458 | scripts: { 459 | options: { 460 | transform: function(filePath) { 461 | filePath = filePath.replace('/client/', ''); 462 | filePath = filePath.replace('/.tmp/', ''); 463 | return ''; 464 | }, 465 | starttag: '', 466 | endtag: '' 467 | }, 468 | files: { 469 | '<%= yeoman.client %>/index.html': [ 470 | ['{.tmp,<%= yeoman.client %>}/{app,components}/**/*.js', 471 | '!{.tmp,<%= yeoman.client %>}/app/app.js', 472 | '!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.spec.js', 473 | '!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.mock.js'] 474 | ] 475 | } 476 | }, 477 | 478 | // Inject component css into index.html 479 | css: { 480 | options: { 481 | transform: function(filePath) { 482 | filePath = filePath.replace('/client/', ''); 483 | filePath = filePath.replace('/.tmp/', ''); 484 | return ''; 485 | }, 486 | starttag: '', 487 | endtag: '' 488 | }, 489 | files: { 490 | '<%= yeoman.client %>/index.html': [ 491 | '<%= yeoman.client %>/{app,components}/**/*.css' 492 | ] 493 | } 494 | } 495 | }, 496 | }); 497 | 498 | // Used for delaying livereload until after server has restarted 499 | grunt.registerTask('wait', function () { 500 | grunt.log.ok('Waiting for server reload...'); 501 | 502 | var done = this.async(); 503 | 504 | setTimeout(function () { 505 | grunt.log.writeln('Done waiting!'); 506 | done(); 507 | }, 1500); 508 | }); 509 | 510 | grunt.registerTask('express-keepalive', 'Keep grunt running', function() { 511 | this.async(); 512 | }); 513 | 514 | grunt.registerTask('serve', function (target) { 515 | if (target === 'dist') { 516 | return grunt.task.run(['build', 'env:all', 'env:prod', 'express:prod', 'wait', 'open', 'express-keepalive']); 517 | } 518 | 519 | if (target === 'debug') { 520 | return grunt.task.run([ 521 | 'clean:server', 522 | 'env:all', 523 | 'concurrent:server', 524 | 'injector', 525 | 'wiredep', 526 | 'autoprefixer', 527 | 'concurrent:debug' 528 | ]); 529 | } 530 | 531 | grunt.task.run([ 532 | 'clean:server', 533 | 'env:all', 534 | 'concurrent:server', 535 | 'injector', 536 | 'wiredep', 537 | 'autoprefixer', 538 | 'express:dev', 539 | 'wait', 540 | 'open', 541 | 'watch' 542 | ]); 543 | }); 544 | 545 | grunt.registerTask('server', function () { 546 | grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); 547 | grunt.task.run(['serve']); 548 | }); 549 | 550 | grunt.registerTask('test', function(target) { 551 | if (target === 'server') { 552 | return grunt.task.run([ 553 | 'env:all', 554 | 'env:test', 555 | 'mochaTest' 556 | ]); 557 | } 558 | 559 | else if (target === 'client') { 560 | return grunt.task.run([ 561 | 'clean:server', 562 | 'env:all', 563 | 'concurrent:test', 564 | 'injector', 565 | 'autoprefixer', 566 | 'karma' 567 | ]); 568 | } 569 | 570 | else if (target === 'e2e') { 571 | return grunt.task.run([ 572 | 'clean:server', 573 | 'env:all', 574 | 'env:test', 575 | 'concurrent:test', 576 | 'injector', 577 | 'wiredep', 578 | 'autoprefixer', 579 | 'express:dev', 580 | 'protractor' 581 | ]); 582 | } 583 | 584 | else grunt.task.run([ 585 | 'test:server', 586 | 'test:client' 587 | ]); 588 | }); 589 | 590 | grunt.registerTask('build', [ 591 | 'clean:dist', 592 | 'concurrent:dist', 593 | 'injector', 594 | 'wiredep', 595 | 'useminPrepare', 596 | 'autoprefixer', 597 | 'ngtemplates', 598 | 'concat', 599 | 'ngAnnotate', 600 | 'copy:dist', 601 | 'cdnify', 602 | 'cssmin', 603 | 'uglify', 604 | 'rev', 605 | 'usemin' 606 | ]); 607 | 608 | grunt.registerTask('default', [ 609 | 'newer:jshint', 610 | 'test', 611 | 'build' 612 | ]); 613 | }; 614 | -------------------------------------------------------------------------------- /client/.htaccess: -------------------------------------------------------------------------------- 1 | # Apache Configuration File 2 | 3 | # (!) Using `.htaccess` files slows down Apache, therefore, if you have access 4 | # to the main server config file (usually called `httpd.conf`), you should add 5 | # this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html. 6 | 7 | # ############################################################################## 8 | # # CROSS-ORIGIN RESOURCE SHARING (CORS) # 9 | # ############################################################################## 10 | 11 | # ------------------------------------------------------------------------------ 12 | # | Cross-domain AJAX requests | 13 | # ------------------------------------------------------------------------------ 14 | 15 | # Enable cross-origin AJAX requests. 16 | # http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity 17 | # http://enable-cors.org/ 18 | 19 | # 20 | # Header set Access-Control-Allow-Origin "*" 21 | # 22 | 23 | # ------------------------------------------------------------------------------ 24 | # | CORS-enabled images | 25 | # ------------------------------------------------------------------------------ 26 | 27 | # Send the CORS header for images when browsers request it. 28 | # https://developer.mozilla.org/en/CORS_Enabled_Image 29 | # http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html 30 | # http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/ 31 | 32 | 33 | 34 | 35 | SetEnvIf Origin ":" IS_CORS 36 | Header set Access-Control-Allow-Origin "*" env=IS_CORS 37 | 38 | 39 | 40 | 41 | # ------------------------------------------------------------------------------ 42 | # | Web fonts access | 43 | # ------------------------------------------------------------------------------ 44 | 45 | # Allow access from all domains for web fonts 46 | 47 | 48 | 49 | Header set Access-Control-Allow-Origin "*" 50 | 51 | 52 | 53 | 54 | # ############################################################################## 55 | # # ERRORS # 56 | # ############################################################################## 57 | 58 | # ------------------------------------------------------------------------------ 59 | # | 404 error prevention for non-existing redirected folders | 60 | # ------------------------------------------------------------------------------ 61 | 62 | # Prevent Apache from returning a 404 error for a rewrite if a directory 63 | # with the same name does not exist. 64 | # http://httpd.apache.org/docs/current/content-negotiation.html#multiviews 65 | # http://www.webmasterworld.com/apache/3808792.htm 66 | 67 | Options -MultiViews 68 | 69 | # ------------------------------------------------------------------------------ 70 | # | Custom error messages / pages | 71 | # ------------------------------------------------------------------------------ 72 | 73 | # You can customize what Apache returns to the client in case of an error (see 74 | # http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.: 75 | 76 | ErrorDocument 404 /404.html 77 | 78 | 79 | # ############################################################################## 80 | # # INTERNET EXPLORER # 81 | # ############################################################################## 82 | 83 | # ------------------------------------------------------------------------------ 84 | # | Better website experience | 85 | # ------------------------------------------------------------------------------ 86 | 87 | # Force IE to render pages in the highest available mode in the various 88 | # cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf. 89 | 90 | 91 | Header set X-UA-Compatible "IE=edge" 92 | # `mod_headers` can't match based on the content-type, however, we only 93 | # want to send this header for HTML pages and not for the other resources 94 | 95 | Header unset X-UA-Compatible 96 | 97 | 98 | 99 | # ------------------------------------------------------------------------------ 100 | # | Cookie setting from iframes | 101 | # ------------------------------------------------------------------------------ 102 | 103 | # Allow cookies to be set from iframes in IE. 104 | 105 | # 106 | # Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"" 107 | # 108 | 109 | # ------------------------------------------------------------------------------ 110 | # | Screen flicker | 111 | # ------------------------------------------------------------------------------ 112 | 113 | # Stop screen flicker in IE on CSS rollovers (this only works in 114 | # combination with the `ExpiresByType` directives for images from below). 115 | 116 | # BrowserMatch "MSIE" brokenvary=1 117 | # BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1 118 | # BrowserMatch "Opera" !brokenvary 119 | # SetEnvIf brokenvary 1 force-no-vary 120 | 121 | 122 | # ############################################################################## 123 | # # MIME TYPES AND ENCODING # 124 | # ############################################################################## 125 | 126 | # ------------------------------------------------------------------------------ 127 | # | Proper MIME types for all files | 128 | # ------------------------------------------------------------------------------ 129 | 130 | 131 | 132 | # Audio 133 | AddType audio/mp4 m4a f4a f4b 134 | AddType audio/ogg oga ogg 135 | 136 | # JavaScript 137 | # Normalize to standard type (it's sniffed in IE anyways): 138 | # http://tools.ietf.org/html/rfc4329#section-7.2 139 | AddType application/javascript js jsonp 140 | AddType application/json json 141 | 142 | # Video 143 | AddType video/mp4 mp4 m4v f4v f4p 144 | AddType video/ogg ogv 145 | AddType video/webm webm 146 | AddType video/x-flv flv 147 | 148 | # Web fonts 149 | AddType application/font-woff woff 150 | AddType application/vnd.ms-fontobject eot 151 | 152 | # Browsers usually ignore the font MIME types and sniff the content, 153 | # however, Chrome shows a warning if other MIME types are used for the 154 | # following fonts. 155 | AddType application/x-font-ttf ttc ttf 156 | AddType font/opentype otf 157 | 158 | # Make SVGZ fonts work on iPad: 159 | # https://twitter.com/FontSquirrel/status/14855840545 160 | AddType image/svg+xml svg svgz 161 | AddEncoding gzip svgz 162 | 163 | # Other 164 | AddType application/octet-stream safariextz 165 | AddType application/x-chrome-extension crx 166 | AddType application/x-opera-extension oex 167 | AddType application/x-shockwave-flash swf 168 | AddType application/x-web-app-manifest+json webapp 169 | AddType application/x-xpinstall xpi 170 | AddType application/xml atom rdf rss xml 171 | AddType image/webp webp 172 | AddType image/x-icon ico 173 | AddType text/cache-manifest appcache manifest 174 | AddType text/vtt vtt 175 | AddType text/x-component htc 176 | AddType text/x-vcard vcf 177 | 178 | 179 | 180 | # ------------------------------------------------------------------------------ 181 | # | UTF-8 encoding | 182 | # ------------------------------------------------------------------------------ 183 | 184 | # Use UTF-8 encoding for anything served as `text/html` or `text/plain`. 185 | AddDefaultCharset utf-8 186 | 187 | # Force UTF-8 for certain file formats. 188 | 189 | AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml 190 | 191 | 192 | 193 | # ############################################################################## 194 | # # URL REWRITES # 195 | # ############################################################################## 196 | 197 | # ------------------------------------------------------------------------------ 198 | # | Rewrite engine | 199 | # ------------------------------------------------------------------------------ 200 | 201 | # Turning on the rewrite engine and enabling the `FollowSymLinks` option is 202 | # necessary for the following directives to work. 203 | 204 | # If your web host doesn't allow the `FollowSymlinks` option, you may need to 205 | # comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the 206 | # performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks 207 | 208 | # Also, some cloud hosting services require `RewriteBase` to be set: 209 | # http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site 210 | 211 | 212 | Options +FollowSymlinks 213 | # Options +SymLinksIfOwnerMatch 214 | RewriteEngine On 215 | # RewriteBase / 216 | 217 | 218 | # ------------------------------------------------------------------------------ 219 | # | Suppressing / Forcing the "www." at the beginning of URLs | 220 | # ------------------------------------------------------------------------------ 221 | 222 | # The same content should never be available under two different URLs especially 223 | # not with and without "www." at the beginning. This can cause SEO problems 224 | # (duplicate content), therefore, you should choose one of the alternatives and 225 | # redirect the other one. 226 | 227 | # By default option 1 (no "www.") is activated: 228 | # http://no-www.org/faq.php?q=class_b 229 | 230 | # If you'd prefer to use option 2, just comment out all the lines from option 1 231 | # and uncomment the ones from option 2. 232 | 233 | # IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME! 234 | 235 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 236 | 237 | # Option 1: rewrite www.example.com → example.com 238 | 239 | 240 | RewriteCond %{HTTPS} !=on 241 | RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] 242 | RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] 243 | 244 | 245 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 246 | 247 | # Option 2: rewrite example.com → www.example.com 248 | 249 | # Be aware that the following might not be a good idea if you use "real" 250 | # subdomains for certain parts of your website. 251 | 252 | # 253 | # RewriteCond %{HTTPS} !=on 254 | # RewriteCond %{HTTP_HOST} !^www\..+$ [NC] 255 | # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L] 256 | # 257 | 258 | 259 | # ############################################################################## 260 | # # SECURITY # 261 | # ############################################################################## 262 | 263 | # ------------------------------------------------------------------------------ 264 | # | Content Security Policy (CSP) | 265 | # ------------------------------------------------------------------------------ 266 | 267 | # You can mitigate the risk of cross-site scripting and other content-injection 268 | # attacks by setting a Content Security Policy which whitelists trusted sources 269 | # of content for your site. 270 | 271 | # The example header below allows ONLY scripts that are loaded from the current 272 | # site's origin (no inline scripts, no CDN, etc). This almost certainly won't 273 | # work as-is for your site! 274 | 275 | # To get all the details you'll need to craft a reasonable policy for your site, 276 | # read: http://html5rocks.com/en/tutorials/security/content-security-policy (or 277 | # see the specification: http://w3.org/TR/CSP). 278 | 279 | # 280 | # Header set Content-Security-Policy "script-src 'self'; object-src 'self'" 281 | # 282 | # Header unset Content-Security-Policy 283 | # 284 | # 285 | 286 | # ------------------------------------------------------------------------------ 287 | # | File access | 288 | # ------------------------------------------------------------------------------ 289 | 290 | # Block access to directories without a default document. 291 | # Usually you should leave this uncommented because you shouldn't allow anyone 292 | # to surf through every directory on your server (which may includes rather 293 | # private places like the CMS's directories). 294 | 295 | 296 | Options -Indexes 297 | 298 | 299 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 300 | 301 | # Block access to hidden files and directories. 302 | # This includes directories used by version control systems such as Git and SVN. 303 | 304 | 305 | RewriteCond %{SCRIPT_FILENAME} -d [OR] 306 | RewriteCond %{SCRIPT_FILENAME} -f 307 | RewriteRule "(^|/)\." - [F] 308 | 309 | 310 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 311 | 312 | # Block access to backup and source files. 313 | # These files may be left by some text editors and can pose a great security 314 | # danger when anyone has access to them. 315 | 316 | 317 | Order allow,deny 318 | Deny from all 319 | Satisfy All 320 | 321 | 322 | # ------------------------------------------------------------------------------ 323 | # | Secure Sockets Layer (SSL) | 324 | # ------------------------------------------------------------------------------ 325 | 326 | # Rewrite secure requests properly to prevent SSL certificate warnings, e.g.: 327 | # prevent `https://www.example.com` when your certificate only allows 328 | # `https://secure.example.com`. 329 | 330 | # 331 | # RewriteCond %{SERVER_PORT} !^443 332 | # RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L] 333 | # 334 | 335 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 336 | 337 | # Force client-side SSL redirection. 338 | 339 | # If a user types "example.com" in his browser, the above rule will redirect him 340 | # to the secure version of the site. That still leaves a window of opportunity 341 | # (the initial HTTP connection) for an attacker to downgrade or redirect the 342 | # request. The following header ensures that browser will ONLY connect to your 343 | # server via HTTPS, regardless of what the users type in the address bar. 344 | # http://www.html5rocks.com/en/tutorials/security/transport-layer-security/ 345 | 346 | # 347 | # Header set Strict-Transport-Security max-age=16070400; 348 | # 349 | 350 | # ------------------------------------------------------------------------------ 351 | # | Server software information | 352 | # ------------------------------------------------------------------------------ 353 | 354 | # Avoid displaying the exact Apache version number, the description of the 355 | # generic OS-type and the information about Apache's compiled-in modules. 356 | 357 | # ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`! 358 | 359 | # ServerTokens Prod 360 | 361 | 362 | # ############################################################################## 363 | # # WEB PERFORMANCE # 364 | # ############################################################################## 365 | 366 | # ------------------------------------------------------------------------------ 367 | # | Compression | 368 | # ------------------------------------------------------------------------------ 369 | 370 | 371 | 372 | # Force compression for mangled headers. 373 | # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping 374 | 375 | 376 | SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding 377 | RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding 378 | 379 | 380 | 381 | # Compress all output labeled with one of the following MIME-types 382 | # (for Apache versions below 2.3.7, you don't need to enable `mod_filter` 383 | # and can remove the `` and `` lines 384 | # as `AddOutputFilterByType` is still in the core directives). 385 | 386 | AddOutputFilterByType DEFLATE application/atom+xml \ 387 | application/javascript \ 388 | application/json \ 389 | application/rss+xml \ 390 | application/vnd.ms-fontobject \ 391 | application/x-font-ttf \ 392 | application/x-web-app-manifest+json \ 393 | application/xhtml+xml \ 394 | application/xml \ 395 | font/opentype \ 396 | image/svg+xml \ 397 | image/x-icon \ 398 | text/css \ 399 | text/html \ 400 | text/plain \ 401 | text/x-component \ 402 | text/xml 403 | 404 | 405 | 406 | 407 | # ------------------------------------------------------------------------------ 408 | # | Content transformations | 409 | # ------------------------------------------------------------------------------ 410 | 411 | # Prevent some of the mobile network providers from modifying the content of 412 | # your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5. 413 | 414 | # 415 | # Header set Cache-Control "no-transform" 416 | # 417 | 418 | # ------------------------------------------------------------------------------ 419 | # | ETag removal | 420 | # ------------------------------------------------------------------------------ 421 | 422 | # Since we're sending far-future expires headers (see below), ETags can 423 | # be removed: http://developer.yahoo.com/performance/rules.html#etags. 424 | 425 | # `FileETag None` is not enough for every server. 426 | 427 | Header unset ETag 428 | 429 | 430 | FileETag None 431 | 432 | # ------------------------------------------------------------------------------ 433 | # | Expires headers (for better cache control) | 434 | # ------------------------------------------------------------------------------ 435 | 436 | # The following expires headers are set pretty far in the future. If you don't 437 | # control versioning with filename-based cache busting, consider lowering the 438 | # cache time for resources like CSS and JS to something like 1 week. 439 | 440 | 441 | 442 | ExpiresActive on 443 | ExpiresDefault "access plus 1 month" 444 | 445 | # CSS 446 | ExpiresByType text/css "access plus 1 year" 447 | 448 | # Data interchange 449 | ExpiresByType application/json "access plus 0 seconds" 450 | ExpiresByType application/xml "access plus 0 seconds" 451 | ExpiresByType text/xml "access plus 0 seconds" 452 | 453 | # Favicon (cannot be renamed!) 454 | ExpiresByType image/x-icon "access plus 1 week" 455 | 456 | # HTML components (HTCs) 457 | ExpiresByType text/x-component "access plus 1 month" 458 | 459 | # HTML 460 | ExpiresByType text/html "access plus 0 seconds" 461 | 462 | # JavaScript 463 | ExpiresByType application/javascript "access plus 1 year" 464 | 465 | # Manifest files 466 | ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds" 467 | ExpiresByType text/cache-manifest "access plus 0 seconds" 468 | 469 | # Media 470 | ExpiresByType audio/ogg "access plus 1 month" 471 | ExpiresByType image/gif "access plus 1 month" 472 | ExpiresByType image/jpeg "access plus 1 month" 473 | ExpiresByType image/png "access plus 1 month" 474 | ExpiresByType video/mp4 "access plus 1 month" 475 | ExpiresByType video/ogg "access plus 1 month" 476 | ExpiresByType video/webm "access plus 1 month" 477 | 478 | # Web feeds 479 | ExpiresByType application/atom+xml "access plus 1 hour" 480 | ExpiresByType application/rss+xml "access plus 1 hour" 481 | 482 | # Web fonts 483 | ExpiresByType application/font-woff "access plus 1 month" 484 | ExpiresByType application/vnd.ms-fontobject "access plus 1 month" 485 | ExpiresByType application/x-font-ttf "access plus 1 month" 486 | ExpiresByType font/opentype "access plus 1 month" 487 | ExpiresByType image/svg+xml "access plus 1 month" 488 | 489 | 490 | 491 | # ------------------------------------------------------------------------------ 492 | # | Filename-based cache busting | 493 | # ------------------------------------------------------------------------------ 494 | 495 | # If you're not using a build process to manage your filename version revving, 496 | # you might want to consider enabling the following directives to route all 497 | # requests such as `/css/style.12345.css` to `/css/style.css`. 498 | 499 | # To understand why this is important and a better idea than `*.css?v231`, read: 500 | # http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring 501 | 502 | # 503 | # RewriteCond %{REQUEST_FILENAME} !-f 504 | # RewriteCond %{REQUEST_FILENAME} !-d 505 | # RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L] 506 | # 507 | 508 | # ------------------------------------------------------------------------------ 509 | # | File concatenation | 510 | # ------------------------------------------------------------------------------ 511 | 512 | # Allow concatenation from within specific CSS and JS files, e.g.: 513 | # Inside of `script.combined.js` you could have 514 | # 515 | # 516 | # and they would be included into this single file. 517 | 518 | # 519 | # 520 | # Options +Includes 521 | # AddOutputFilterByType INCLUDES application/javascript application/json 522 | # SetOutputFilter INCLUDES 523 | # 524 | # 525 | # Options +Includes 526 | # AddOutputFilterByType INCLUDES text/css 527 | # SetOutputFilter INCLUDES 528 | # 529 | # 530 | 531 | # ------------------------------------------------------------------------------ 532 | # | Persistent connections | 533 | # ------------------------------------------------------------------------------ 534 | 535 | # Allow multiple requests to be sent over the same TCP connection: 536 | # http://httpd.apache.org/docs/current/en/mod/core.html#keepalive. 537 | 538 | # Enable if you serve a lot of static content but, be aware of the 539 | # possible disadvantages! 540 | 541 | # 542 | # Header set Connection Keep-Alive 543 | # 544 | -------------------------------------------------------------------------------- /commit_history.md: -------------------------------------------------------------------------------- 1 | 2 | Documentation for Inkwell Front-End: 3 | 4 | ---------------------------------------------------------------------------------------------- 5 | 6 | 94b201029de611eaf459f921ceb7d1126ab2c53c 7 | Generated an angular-node-express scaffold with yeoman. 8 | 9 | With this commit, we added a pre-built Angular full stack scaffolding tool to our repo 10 | from Yeoman.io. The scaffolding tool incorporates Express, Angular and Node, client and server 11 | folders will all of the necessary files pre-populated and customized with the name of our project, as well as all of the necessary dependancies (bower, grunt, package.JSON, 12 | express, Bootstrap, mocha, svg, nodemon, Ajax, Karma, jQuery, jade, coffeeScript etc.) 13 | 14 | ----------------------------------------------------------------------------------------------- 15 | 16 | 433edbf4505fba4dbc3208cbf9f19a156ecbf6f3 17 | Minor change 18 | 19 | In this commit, we udpated the client-side index.html file to include links for the stylesheets 20 | included with the scaffolding tool, script tags for all the of .js files included with the 21 | scaffolding tool, and script tags for all necessary bower components. 22 | 23 | ----------------------------------------------------------------------------------------------- 24 | 25 | d73142e93e104fdc4dc73d92b99ee9c694ca7559 26 | Added some boilerplate code on server to handle token-based 27 | auth. Added the required npm modules to package.json. 28 | 29 | In this commit, we added boiler-plate code (predominantly server-side), in order to handle 30 | token-based authorization. We also added the required npm modules to package.json 31 | 32 | ----------------------------------------------------------------------------------------------- 33 | 34 | e509adc517b10c8a77f48e6656bae9a6bf2fed38 35 | Added necessary client-side code for token authentication 36 | 37 | This commit is where we created an "account" folder, populated with all of the views and code 38 | necessary to handle user signup, user login, user settings, and user authentication. Lastly, we added all of the necessary links and sript tags to index.html 39 | 40 | ------------------------------------------------------------------------------------------------ 41 | 42 | b80b29675459dae1e61728cf71a796bb9c8a29e3 43 | Changed my error from boilerplate code 44 | 45 | This commit is pretty straight-forward: we changed some of the boiler-plate code from the 46 | scaffolding tool that we used to the name of our project (familyThiefApp), so that our Angular 47 | modules are properly named. We also added links and script tags to our index.html file. 48 | 49 | ------------------------------------------------------------------------------------------------- 50 | 51 | 30f2aa1b383b14426f5ae20cd42c8f740508d05e 52 | styling changes to main page 53 | 54 | In this commit, we changed the header, as well as the content in the first paragraph of our site's main page. 55 | 56 | ------------------------------------------------------------------------------------------------- 57 | 58 | a3d394d936ed0ba037dc12f51cf63737713104b1 59 | design changes made to main view 60 | 61 | In this commit, we changed another one of the headers on our main page, and updated the 62 | main page image. 63 | 64 | ------------------------------------------------------------------------------------------------- 65 | 66 | efa733645c67bd9b30d2c1defb53ad7a3422c8e5 67 | Updated the menu bar to show a link to sign up and login 68 | page. 69 | 70 | With this commit, we deleted unnecessary code from our signup.html file (re-purposed from another project), and we updated our navbar.html file so that links to signup and login 71 | would appear in the navigation bar at the top of our homes screen. 72 | 73 | ------------------------------------------------------------------------------------------------- 74 | 75 | febcad4caa6dc90c9b022b69010d3aac47704be1 76 | Fixed the bug that didn't allow menu items to appear. 77 | Changed top menu slightly. Added some stylings and an 78 | image to the signup and login pages. 79 | 80 | This commit is pretty self-explanatory: there was a bug that was preventing menu items from appearing in the nav bar, so we fixed it. We also added an image with this commit that appears on the signup and login pages, as well as some minor css styings. 81 | 82 | ------------------------------------------------------------------------------------------------- 83 | 84 | 4528600ef4332ca34612dd81c5a10d94f2c15c3a 85 | added dashboard components 86 | 87 | With this commit, we added a folder with all of the necessary files to create a dashboard view. The dashboard is the main view that a user sees once logging in/signing up and is the main control center to access most of the other views. We've also added links and script tags for the dashboard files to index.html, and added a dashboard link to the navbar. 88 | 89 | ------------------------------------------------------------------------------------------------- 90 | 91 | 4bc493abd52abbeab28c73ac66561a22707f9725 92 | added homepage style 93 | 94 | With this commit, we updated the html of our main page to adopt the Bootstrap framework/ grid system. 95 | 96 | ------------------------------------------------------------------------------------------------- 97 | 98 | b49d5bf04d775bb8d4261f9538ee0af329700015 99 | added css stylings to main page 100 | 101 | CSS changes to our main page (centered text and changed background banner color to match buttons in the middle of the page) 102 | 103 | ------------------------------------------------------------------------------------------------- 104 | 105 | fd4800488cda05acb5b546121526024c84f32c11 106 | dashboard preliminary features complete 107 | 108 | The primary intent with this commit was to flesh out our dashboard controller with the necessary Angular properties and methods, incorporate these methods into our dashboard.html, signup.html, and login.html, as well as apply the Bootstrap framework to our dashboard html file. 109 | 110 | ------------------------------------------------------------------------------------------------- 111 | 112 | 995b910978b4c38d532d16e5a578f18e3517c17f 113 | Added submission state to the app 114 | 115 | With this commit we added a new view to our app titled 'submission'. The submission view is the one that appears when a logged in user clicks the 'Submit new help request' button. In this commit, we also deleted some unnecessary code from our account.js file (re-purposed from another project). 116 | 117 | ------------------------------------------------------------------------------------------------- 118 | 119 | 8e846e5e7d21ed278575d3b27e845bb2f0fc2ab2 120 | Added a new service called HelpRequest. Halfway through 121 | the form logic for submitting a helpRequest. 122 | 123 | This commit builds on the the previous commit above by populating the files in the 'submission' view folder with all of the necessary code to create and submit a help request. We also applied the Bootstrap framework to the submission .html file here, as well as added the neccesary authentication routes for submissions, and links/script tags to index.html 124 | 125 | ------------------------------------------------------------------------------------------------- 126 | 127 | 3edef455f916ea900698a4e262b029178afcad87 128 | Completed basic form for the helpRequest submission. 129 | 130 | With this commit we completed the helpReqest form (i.e. submission.html), adding components that were previously missing and deleting unnecessary ones. 131 | 132 | ------------------------------------------------------------------------------------------------- 133 | 134 | f5dd139e727e58629ac1f5fdb34cea5d6a645900 135 | Changed sign up and login to be username unique instead 136 | of email 137 | 138 | This commit is self-explanaroty: we changed our signup and login authentication to check/receive usernames instead of email addresses 139 | 140 | ------------------------------------------------------------------------------------------------- 141 | 142 | 3ba52707757a48261d1ae2f1481ecb4e00bec1aa 143 | Client and server are now connected and authentication 144 | works on a basic level. A new user is created on sign-up, 145 | a token is passed to client, and the user is created 146 | in the database. 147 | 148 | Another self-explanatory commit: added code necessary to link client with server. Authentication working on a basic level. 149 | 150 | ------------------------------------------------------------------------------------------------- 151 | 152 | ca803eba42f473a01dbc573397c0a7856ee6dcf9 153 | Users can now use the form to submit a help request and 154 | it is saved in our database. 155 | 156 | Here we added code to our submission controller and submission.html to offically allow users submit help requests for their writing samples that get saved to our database. 157 | 158 | ------------------------------------------------------------------------------------------------- 159 | 160 | 4e58b39632d434fa7398cd8da3f3b685e92d8b6f 161 | Implemented basic search for a help request based on 162 | title and summary 163 | 164 | Here we added a method to our dashboard controller to allow users to search outstanding help requests submitted by other users by keyword, at the bottom of the dashboard view. 165 | 166 | ------------------------------------------------------------------------------------------------- 167 | 168 | 7e4af85b16a4a5d9ad6f3f3acc3a383bf6e45fe9 169 | Fixed path of the secret 170 | 171 | Very self-explanatory commit: only 1 line of code changed in order to properly route the secret key required for user authentication. 172 | 173 | ------------------------------------------------------------------------------------------------- 174 | 175 | fd5bb8f03d030f32e9c39407b86c1152d6ed2716 176 | Changed a few things so that the dashboardd loads data 177 | dynamically for each user. The client and server are 178 | really starting to be connected now. Also, I turned the 179 | authenticate flag on for certain pages, because now the 180 | server is doing authentication correctly. 181 | 182 | Here we deleted unnecessary code from the dashboard controller, added the correct Angular property name and directives to dashboard.html, and auth.service.js. We also added the necessary Angular properties and methods to the navbar controller, and changes 'authenticate' to true in dashboard.js and submission.js. 183 | 184 | ------------------------------------------------------------------------------------------------- 185 | 186 | 00a08280e6a41316b74a1d9d744e1df9521e7776 187 | created client-side help-request view folder with necessary files 188 | 189 | Here we created a folder with the necessary files to create a 'help-request' view. If a user would like to contribute to a piece of writing submitted by another user, the first step is to either click the 'contribute' button on the dashboard, or use the search feature to find another user's help request by keyword. Either way, a list of outstanding help requests from other writers will appear. When the user clicks on one of these links, the resulting view is what is created by this folder (i.e. an individual help request from another writer that the user can contribute to.) 190 | 191 | ------------------------------------------------------------------------------------------------- 192 | 193 | 8b7d0e2c145ed17977b83ea50c499c83bc2505bb 194 | changes to language/layout on help-request page 195 | 196 | Minor styling changes to help request view (above), as well as adding link/scripts for help-request view to index.html 197 | 198 | ------------------------------------------------------------------------------------------------- 199 | 200 | 37e42f2809f525c764030fdd7c3abc80faeca068 201 | created view for received contribution requests 202 | 203 | Created a folder with files necessary to create a 'contribution received view' (i.e. the view a user sees when reviewing a contriubution request from another writer). If a user submits a help request, and another user contributes to that help request, then a mailbox icon will appear in the top-left of the user's dashboard, indicating that someone has responded to their help request with a contribution submission. When the user clicks on the mailbox icon, she/he is taken to an 'inbox' view, listing all contributions other have made to the user's writing/help-requests. When the user clicks on one of these contributions, the 'contributionRecd' view is what he/she sees. 204 | 205 | ------------------------------------------------------------------------------------------------- 206 | 207 | 1b8f2d84679c141678f87e71e731fdf38e1016ad 208 | added contribution view links and scripts to index.html 209 | 210 | Added links/scripts to index.html for 'contributionRecd' and 'help-request' views 211 | 212 | ------------------------------------------------------------------------------------------------- 213 | 214 | 28519ff232cda099a4cf0e451d7671615c3e5c7b 215 | view updated 216 | 217 | Deleted unnecessary code from help-request view 218 | 219 | ------------------------------------------------------------------------------------------------- 220 | 221 | dbb5de2e84a78f87d1e87be6b7be9b517638bcb3 222 | Building the HelpRequest service 223 | 224 | Updated properties of dashboard controller and established helpRequest authentication route to 225 | retrieve help requests from database based on search criteria 226 | 227 | ------------------------------------------------------------------------------------------------- 228 | 229 | 08d1473a9fe8c3a3c26e7044c5e7a14a2bc43981 230 | Fixed strange merge conflict 231 | 232 | Self-explanatory. Git merging issue 233 | 234 | ------------------------------------------------------------------------------------------------- 235 | 236 | ef2c2fbd471fb04723b84118381bb1428930a25e 237 | Help request view with appropriate data now loads when 238 | a help request is clicked. Will have to work on displaying 239 | the data properly in the helpRequest view, because the text 240 | is now spilling out of its container when it loads. 241 | 242 | Fix for another merge conflict. 243 | 244 | ------------------------------------------------------------------------------------------------- 245 | 246 | a2d975cabce34739865778b078ae0d3c35d89037 247 | Fixed the bug where the help-request.js and controller 248 | were being loaded twice in index.html. 249 | 250 | Two script tags deleted from index.html 251 | 252 | ------------------------------------------------------------------------------------------------- 253 | 254 | 5f3c7dd26f47e9d52a17bcf1742a992d5e07c238 255 | Changed some structure in the view for a help request 256 | and made it possible to submit a contribution from that 257 | view. 258 | 259 | Help-request controller needed to be modified to display appropriate information and post to database. 260 | 261 | ------------------------------------------------------------------------------------------------- 262 | 263 | 82bf193e17e74c8b46d99516bc3221cc41511752 264 | Finished the controller logic and view for a contribution. 265 | Can also upvote the contribution and comment on it. I had to 266 | modify something very slight in the helper function 267 | contributionUpvote in order for the vote to get through. 268 | 269 | Self-explanatory 270 | 271 | ------------------------------------------------------------------------------------------------- 272 | 273 | 7a9f1aeec99ad267496032710c255675eb95f219 274 | created view for page with 25 most reecent help requests 275 | 276 | Here we created a folder with all of the files necessary for a 'allHelpRequest' view (i.e. the view that appears when a user clicks the 'contribute' button on the dashboard). This view lists all outstanding help requests from other writers. 277 | 278 | ------------------------------------------------------------------------------------------------- 279 | 280 | 00fd72222e04751a17d284cfbcee1d27d664a268 281 | created all help request view and linked to dashboard 282 | 283 | changes to copy/pasted code in 'allHelpRequest' folder make code applicable to 'allHelpRequest' view. 284 | 285 | ------------------------------------------------------------------------------------------------- 286 | 287 | e4d5c0b7228b3f81fc7262a11a3106c2ca7518b9 288 | 289 | Deleted unnecesary code from main.html; minor changes to dashboad.css, updated dashboard.html 290 | to comply with Bootstrap framework 291 | 292 | ------------------------------------------------------------------------------------------------- 293 | 294 | 40ddf6a2cf89f24321ef32858c88fcd1073bb807 295 | cleaned up submission view 296 | 297 | Stylistic changes to dashboard and submission views 298 | 299 | ------------------------------------------------------------------------------------------------- 300 | 301 | 796b49b9494a809b6a5dea1d0b3ef28ccd5500c7 302 | updated allHelpRequest view 303 | 304 | Stylistic changes to allHelpRequest view 305 | 306 | ------------------------------------------------------------------------------------------------- 307 | 308 | e7c35fd80e8357a2639f5453d43925fc5d24913c 309 | linked buttons on main page; other minor updates to views 310 | 311 | Stylistic changes to 'contributionRecd' and 'dashboard' views; linked buttons on main page to appropriate views. 312 | 313 | ------------------------------------------------------------------------------------------------- 314 | 315 | 1c54c7ef5e0f34a0b3e4f0c0b32d3175012a9863 316 | Added some voting functionality on the client, but now 317 | the votes are not being retrieved correctly from the server 318 | after votes are made. Will talk about this in our meeting. 319 | 320 | Self-explanatory 321 | 322 | ------------------------------------------------------------------------------------------------- 323 | 324 | b65677f76eef8e63d7d00d52a9fc98dc7b9983a5 325 | Added isOwner var to contribution controller 326 | 327 | Self-Explanatory 328 | 329 | ------------------------------------------------------------------------------------------------- 330 | 331 | 7e3ccf026fe2a2d7b677081af07767a055462dec 332 | Added ng-show to the button 333 | 334 | Ng-hide added to button in contribution.html 335 | 336 | ------------------------------------------------------------------------------------------------- 337 | 338 | cd0496f051ffc96a45c7d27399407c9be7acfb92 339 | Fixed the client issues when searching the database on the 340 | dashboard 341 | 342 | Self-explanatory 343 | 344 | ------------------------------------------------------------------------------------------------- 345 | 346 | d7f5355087da02add8bcbe6450c87c3859e94416 347 | Fixed voting problems on client side. The body of the 348 | voting requests was empty before. Now it's sending the right 349 | id and the voting works. 350 | 351 | Self-explanatory 352 | 353 | ------------------------------------------------------------------------------------------------- 354 | 355 | 6dad8e259a40dc9f823ed7da3aaddee54373832a 356 | Made a small change to make voting for a contribution 357 | work in the client 358 | 359 | Self-explanatory 360 | 361 | ------------------------------------------------------------------------------------------------- 362 | 363 | 2e08377419a0d79a9290cb16582c40604c0b5004 364 | created inbox view 365 | 366 | This is the view that appears when a user clicks on the mail icon at the top left of the dashboard. It represents an inbox of all contributuions other writers have submitted for the user's help requests. 367 | 368 | ------------------------------------------------------------------------------------------------- 369 | 370 | a08e5b375878cce673b053c6ff4c30ee4cc6c5b3 371 | updates to inbox view, incl. adding scripts/link for view to index.html 372 | 373 | Self-explanatory 374 | 375 | ------------------------------------------------------------------------------------------------- 376 | 377 | 27f20e6d231edc9ccf44dd37ae48743df192dd20 378 | added search functionality to allHelpRequest view 379 | 380 | Self-explanatory. Incorporated Bootstrap framework into allHelpRequest.html 381 | 382 | ------------------------------------------------------------------------------------------------- 383 | 384 | 93738832567b9b1ca74eef07358daf260234e020 385 | minor code change trying to get search to work in allHelpRequest view 386 | 387 | One line of code changed. 388 | 389 | ------------------------------------------------------------------------------------------------- 390 | 391 | 31406a99d1e823ac72d1b32dbf0cd9ecf0b626db 392 | added functionality to inbox view to differentiate between seen and unseen messages 393 | 394 | Self-explanatory 395 | 396 | ------------------------------------------------------------------------------------------------- 397 | 398 | 478c3e9a6ac4c5cf3bc505957bda54ab0e31517b 399 | Made the dashboard update when a new project is submitted 400 | 401 | Self-explanatory 402 | 403 | ------------------------------------------------------------------------------------------------- 404 | 405 | 9f7c1b6b33a02d6146da95c999c43ac40c23ad24 406 | Made a new contribution appear both in the help request 407 | view and in the user's dashboard view when a help request 408 | is submitted. Realized that the dashboard needs a couple more 409 | pieces of data in each object of the contributions array 410 | to display meaningful data. We'll talk about that minor change 411 | tomorrow 412 | 413 | Self-explanatory 414 | 415 | ------------------------------------------------------------------------------------------------- 416 | 417 | 2cdd1f3c3c930f41de18ac2b2642c3893f949c6a 418 | Brought back Craig's work that I accidently erased 419 | 420 | Self-explanatory 421 | 422 | ------------------------------------------------------------------------------------------------- 423 | 424 | ccd91e80563544ad580d6e162b8aca8c2574d6af 425 | Made the 25 most recently submitted help requests display 426 | the data correctly. Tomorrow will work on prettifying it. 427 | 428 | Self-explanatory 429 | 430 | ------------------------------------------------------------------------------------------------- 431 | 432 | afbd6acc67c5c547449fe88c07b14ec18486207c 433 | minor change 434 | 435 | commented our search feature in allHelpRequest; attempted bug fix 436 | 437 | ------------------------------------------------------------------------------------------------- 438 | 439 | 9d9cddc3e8f515e3cc39d03d5ad4916d29cdf069 440 | switched api route in inbox.controller.js to api/mailbox 441 | 442 | Self-explanatory 443 | 444 | ------------------------------------------------------------------------------------------------- 445 | 446 | a89f0ebda6c1fbf022239bfdaad8afc8cbdc6e89 447 | Changed the dashboard layout to have a more intuitive layout. 448 | Now it's more clear what to do when visiting it. Also, I made 449 | the notifications for help requests display only if the number 450 | of unseen is greater than zero. 451 | 452 | Self-explanatory 453 | 454 | ------------------------------------------------------------------------------------------------- 455 | 456 | fbd489dbaec12a248db8b4846bcdbf8328223e80 457 | Made the looking for contributions view look better. A 458 | user can optionally now view the 25 most recent help requests, 459 | or do a search of the database. 460 | 461 | Self-explanatory 462 | 463 | ------------------------------------------------------------------------------------------------- 464 | 465 | ce37a528d28c24f28055e97c83aa3daf27a1a3c3 466 | Removed the search bar in the dashboard because now it works 467 | in the same way in the "Looking for contribution" view. 468 | 469 | Self-explanatory 470 | 471 | ------------------------------------------------------------------------------------------------- 472 | 473 | 98907b79b3e61b971bd7e10833b6377addb12441 474 | Just made the mailbox display all the time. Conditional 475 | display was buggy. 476 | 477 | Self-explanatory 478 | 479 | ------------------------------------------------------------------------------------------------- 480 | 481 | 904b598cd8d02258b4bc5f3d8e419aa593cd0aae 482 | Implemented a simple mailbox view where unseen contribution 483 | are bordered in red. They no longer become bordered by red 484 | for the first time they are clicked, and the number of unseen 485 | in the user's dashboard does change. 486 | 487 | Self-explanatory 488 | 489 | ------------------------------------------------------------------------------------------------- 490 | 491 | 8c53d151b2d9122aee5efddd34e419443633cb93 492 | Made submission of both a help request and contribution 493 | show a success message if successful. Made the view of 494 | a help request differ depending on whether logged in user 495 | is owner of the help request 496 | 497 | Self-explanatory 498 | 499 | ------------------------------------------------------------------------------------------------- 500 | 501 | 2fb9aeaa8f2cbf4c883be94b270c595bd0fb5476 502 | Added a litte style to contribution view and made sure 503 | text of contribution displays. But noticed that comments are 504 | not persisting in the database. 505 | 506 | Self-explanatory 507 | 508 | 509 | ------------------------------------------------------------------------------------------------- 510 | ------------------------------------------------------------------------------------------------- 511 | ------------------------------------------------------------------------------------------------- 512 | 513 | Documentation for Inkwell Back-End: 514 | 515 | 2318ef7ab3d924876381e6be5d0ce1da40a9c9f7 516 | server merge 517 | 518 | We were initially using a back-end template but found it very difficult to navigate around, so we decided to scrap the back-end template and start from scratch. This commit shows the beginnings of our server. We chose to use token authentication through jsonwebtoken and express-jwt as the middleware. In order to have access the protected routes, once the user has authenticated, the expressJwt middleware will use our secret token to pass the user a unique token to prove their identity. After authentication, the client will request for the dashboard of that particular user, and therefore pass back that token to the server, which will be located in the request header. In order to decode this token into the respective user details, we use the jwt.decode function. 519 | 520 | Our models start off very basic with just the User table and Project table. When the client communicates to the server, we have helper functions that register the user, authenticate the user, or find all the user’s dashboard data. 521 | 522 | ------------------------------------------------------------------------------------------------- 523 | 524 | 69dd4717a8a10842e30c4c1df96218dbbd335952 525 | changes to directory 526 | 527 | Pretty straightforward commit - as we were merging the server files with the client files, we changed the directory to refer to the right files. 528 | 529 | ------------------------------------------------------------------------------------------------- 530 | 531 | d5eb75d3ff216dfef9b375224d040405c7347dc4 532 | adding posting HR 533 | 534 | Here we elaborate on our Project model with more fields that we need. We also finish off the path for submitting a help request (for their writing) and use the helper function helpRequest to create the project in our database. 535 | 536 | ------------------------------------------------------------------------------------------------- 537 | 538 | 228ba68e0707ec3bc0c01011a81556602f8dbe9e 539 | progress with models and allowing submission 540 | 541 | Here we organize the code out better to put the User and the Projects model in two separate files. We also elaborate on our helper function to identify the user’s dashboard information using findAllInfo. 542 | 543 | ------------------------------------------------------------------------------------------------- 544 | 545 | 83ddf1d30aca86cb2a29934e557e75fb3a0fa440 546 | Contribution Model 547 | 548 | We create the contribution model (this is for when someone contributes to a piece of writing). The relationship between a user and a contribution is one to many. This is defined as a contributor in our model (we will realize later that this code doesn’t actually create associations, just references, which is not what we want). A project is also related to a contribution via a one to many relationship. This is defined project in our model. 549 | 550 | ------------------------------------------------------------------------------------------------- 551 | 552 | 4f49841222eb78e4b6223cd97907020cad57b481 553 | project upvotes model 554 | 555 | We create the project upvotes model. Project upvotes have a many to one relationship between users, and also a many to one relationship between projects To understand how our tables look, it may be helpful to refer to our tables diagram. 556 | 557 | ------------------------------------------------------------------------------------------------- 558 | 559 | a3ebf87423fd2460c4d3ef69ae570a0cccb8fe74 560 | project comments table 561 | 562 | We create the project comments model. It has a many to one relationship between the user and also a many to one relationship between the project. 563 | 564 | ------------------------------------------------------------------------------------------------- 565 | 566 | 18ed3a705068edaf2a240bfec2f3215b80fc7a66 567 | contribution upvotes table done 568 | 569 | We create the contribution upvotes model. It has a many to one relationship between the user and also a many to one relationship between the contribution. 570 | 571 | ------------------------------------------------------------------------------------------------- 572 | 573 | 56f18e62c421a71d783c5cdb4db2a4e3caf836d0 574 | contribution comments table done 575 | 576 | We create the contribution comments model. It has a many to one relationship between the user and also a many to one relationship between the contribution. 577 | 578 | ------------------------------------------------------------------------------------------------- 579 | 580 | 091aaaf7378013072ff50caa95080346dd2b1df4 581 | counting number of unseenHelps 582 | 583 | We create the helper function and paths for find all contributions that the user has made, and also the unseen help requests that a user has. 584 | 585 | ------------------------------------------------------------------------------------------------- 586 | 587 | e3527f64bd947e01f22b3b7c1e407e7e24fee0fe 588 | Finished the dashboard profile 589 | 590 | The previous dashboard was not sending the client an object that they were expecting. This could probably be refactored using table joins. The comments in the code describe what each function’s purpose is. 591 | 592 | ------------------------------------------------------------------------------------------------- 593 | 594 | f5bfcf0ce0e414c2fdc4387f6d8227a7dabff2b2 595 | fixes to creating a user and also project upvoting 596 | Added the project upvote helper function that upvotes a project 597 | 598 | ------------------------------------------------------------------------------------------------- 599 | 600 | 829e8f3fac2de86a99b76dd701fe43e69e8680dc 601 | route for viewing all project details 602 | When a user clicks on a project, we want to show them the details associated with that project as well as its corresponding contributions, votes, etc.. Here we complete the viewProject helper function and route for that purpose. Notice that the project id will be within parameters of the search url. Therefore, to obtain the project id, we must look at the params property of the request. 603 | 604 | ------------------------------------------------------------------------------------------------- 605 | 606 | 6de093d5d6c7f3a783106d2d7d5a2b10d799e6df 607 | contribution post done 608 | 609 | We add the ability for a user to create a contribution to a project, as well as the route for it. 610 | 611 | ------------------------------------------------------------------------------------------------- 612 | 613 | e749fdfe78e7096d2fb11011d9e5bde3c51bd148 614 | contribution comment path done 615 | 616 | We add the ability for a user to create a contribution comment to a contribution, as well as the route for it. 617 | 618 | ------------------------------------------------------------------------------------------------- 619 | 620 | 13ed751d455657b49066132f34f4ea9cb7fce6c6 621 | votes and contribution view complete 622 | 623 | When the user clicks on a contribution, similar to how they click on a project, we want to show them the details of that contribution, the user, the comments, etc. Here we create the helper function and path for it. 624 | 625 | ------------------------------------------------------------------------------------------------- 626 | 627 | 86df514025eda8077df5bb98f73554a0aaa68bbb 628 | Many changes because of associations and table joins 629 | 630 | While trying to implement a joined table query to send to the client the expected details of the contribution array, we realized that the error message was that the tables were not associated with each other. The code in the models only references the other tables, without making associations. Therefore, we added an associations document to show the relationships that each model had with each other through which key. The code starting on line 72 searches for the contributions made by the user, but includes the username as a part of the table join. We therefore have access to the username of that particular contribution which is expected on the client side. 631 | 632 | ------------------------------------------------------------------------------------------------- 633 | 634 | bdb10c88cd204885bcab997d4cb1a172dd46056b 635 | searching database 636 | 0d13d506a27d8e3f787b4ab8f918681f6a599327 637 | fixed query for database 638 | 639 | Here we create the helper function to query the database where a string is contained in the title or summary of the project for when users want to look for a project to contribute to. The syntax was initially not doing what we had expected it to, which was to search in the title and summary for the string being passed through. The string is passed through in the URL query, so will need to find that search string in the query property of the request. 640 | 641 | ------------------------------------------------------------------------------------------------- 642 | 643 | be47a0abd21aba6de5676addc48aedf6d7483913 644 | fixing viewprojectcontributiondetails 645 | 646 | The properties on the contributions array as part of the projectDetails was changed to meet the client’s expectations. 647 | 648 | ------------------------------------------------------------------------------------------------- 649 | 650 | 46f5ccb8b312ac3361fcba2e21d68e0038854cc7 651 | all changes ok 652 | 653 | Added a route so that we could get all the help requests in the database and return this to the client in the requested properties. 654 | 655 | Also made some small changes to properties in other routes to meet client expectations. 656 | 657 | ------------------------------------------------------------------------------------------------- 658 | 659 | b6b78d2ec3d120b58d9b0159171f2da83d652078 660 | some client side changes for craig and completed new route for mailbox 661 | 662 | Incorporated some changes on the client side to display the Received Contributions page to test back-end functionality. 663 | 664 | Added a route to show which contributions made to the user’s projects have already been viewed. 665 | 666 | In the dashboard view, unseenhelp counts were counting all helps that were already seen, whereas they should be counting those that were unseen. This fix was done by changing the unseenHelp property to true. 667 | --------------------------------------------------------------------------------