├── .buildignore ├── .gitattributes ├── client ├── robots.txt ├── assets │ └── images │ │ └── yeoman.png ├── app │ ├── main │ │ ├── main.js │ │ ├── main.controller.js │ │ ├── main.css │ │ ├── main.controller.spec.js │ │ └── main.html │ ├── app.js │ └── app.css ├── components │ ├── navbar │ │ ├── navbar.controller.js │ │ └── navbar.html │ └── modal │ │ ├── modal.css │ │ ├── modal.html │ │ └── modal.service.js ├── .jshintrc ├── index.html ├── favicon.ico └── .htaccess ├── .bowerrc ├── README.md ├── .travis.yml ├── .gitignore ├── server ├── .jshintrc-spec ├── api │ ├── thing │ │ ├── index.js │ │ ├── thing.spec.js │ │ └── thing.controller.js │ └── user │ │ ├── index.js │ │ ├── user.model.js │ │ ├── user.model.spec.js │ │ └── user.controller.js ├── config │ ├── environment │ │ ├── test.js │ │ ├── development.js │ │ ├── production.js │ │ └── index.js │ ├── local.env.sample.js │ ├── dbConfig.example.js │ ├── users.js │ └── express.js ├── .jshintrc ├── auth │ ├── index.js │ ├── local │ │ ├── index.js │ │ └── passport.js │ └── auth.service.js ├── components │ └── errors │ │ └── index.js ├── app.js ├── routes.js └── views │ └── 404.html ├── e2e └── main │ ├── main.po.js │ └── main.spec.js ├── .editorconfig ├── bower.json ├── LICENSE ├── .yo-rc.json ├── protractor.conf.js ├── karma.conf.js ├── package.json └── Gruntfile.js /.buildignore: -------------------------------------------------------------------------------- 1 | *.coffee -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /client/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "client/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # family-thief 2 | Collaborative writing platform 3 | -------------------------------------------------------------------------------- /client/assets/images/yeoman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henryng24/family-thief/HEAD/client/assets/images/yeoman.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 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | public 3 | .tmp 4 | .idea 5 | client/bower_components 6 | dist 7 | /server/config/local.env.js 8 | 9 | # Ingore actual db config 10 | /server/config/dbConfig.js 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 | -------------------------------------------------------------------------------- /server/api/thing/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var controller = require('./thing.controller'); 5 | 6 | var router = express.Router(); 7 | 8 | router.get('/', controller.index); 9 | 10 | module.exports = router; -------------------------------------------------------------------------------- /server/config/environment/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Test specific configuration 4 | // =========================== 5 | module.exports = { 6 | // MongoDB connection options 7 | mongo: { 8 | uri: 'mongodb://localhost/familythief-test' 9 | } 10 | }; -------------------------------------------------------------------------------- /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/config/environment/development.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Development specific configuration 4 | // ================================== 5 | module.exports = { 6 | // MongoDB connection options 7 | mongo: { 8 | uri: 'mongodb://localhost/familythief-dev' 9 | }, 10 | 11 | seedDB: true 12 | }; 13 | -------------------------------------------------------------------------------- /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/main/main.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .controller('MainCtrl', function ($scope, $http) { 5 | $scope.awesomeThings = []; 6 | 7 | $http.get('/api/things').success(function(awesomeThings) { 8 | $scope.awesomeThings = awesomeThings; 9 | }); 10 | 11 | }); 12 | -------------------------------------------------------------------------------- /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) { 11 | $urlRouterProvider 12 | .otherwise('/'); 13 | 14 | $locationProvider.html5Mode(true); 15 | }); -------------------------------------------------------------------------------- /client/components/navbar/navbar.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('familyThiefApp') 4 | .controller('NavbarCtrl', function ($scope, $location) { 5 | $scope.menu = [{ 6 | 'title': 'Home', 7 | 'link': '/' 8 | }]; 9 | 10 | $scope.isCollapsed = true; 11 | 12 | $scope.isActive = function(route) { 13 | return route === $location.path(); 14 | }; 15 | }); -------------------------------------------------------------------------------- /server/auth/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var passport = require('passport'); 5 | var config = require('../config/environment'); 6 | var User = require('../api/user/user.model'); 7 | 8 | // Passport Configuration 9 | require('./local/passport').setup(User, config); 10 | 11 | var router = express.Router(); 12 | 13 | router.use('/local', require('./local')); 14 | 15 | module.exports = router; -------------------------------------------------------------------------------- /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/components/errors/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Error responses 3 | */ 4 | 5 | 'use strict'; 6 | 7 | module.exports[404] = function pageNotFound(req, res) { 8 | var viewFilePath = '404'; 9 | var statusCode = 404; 10 | var result = { 11 | status: statusCode 12 | }; 13 | 14 | res.status(result.status); 15 | res.render(viewFilePath, function (err) { 16 | if (err) { return res.json(result, result.status); } 17 | 18 | res.render(viewFilePath); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /server/config/local.env.sample.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use local.env.js for environment variables that grunt will set when the server starts locally. 4 | // Use for your api keys, secrets, etc. This file should not be tracked by git. 5 | // 6 | // You will need to set these on the server you deploy to. 7 | 8 | module.exports = { 9 | DOMAIN: 'http://localhost:9000', 10 | SESSION_SECRET: 'familythief-secret', 11 | 12 | // Control debug level for modules using visionmedia/debug 13 | DEBUG: '' 14 | }; 15 | -------------------------------------------------------------------------------- /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/api/thing/thing.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var app = require('../../app'); 5 | var request = require('supertest'); 6 | 7 | describe('GET /api/things', function() { 8 | 9 | it('should respond with JSON array', function(done) { 10 | request(app) 11 | .get('/api/things') 12 | .expect(200) 13 | .expect('Content-Type', /json/) 14 | .end(function(err, res) { 15 | if (err) return done(err); 16 | res.body.should.be.instanceof(Array); 17 | done(); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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: #4393B9; 23 | } 24 | 25 | .footer { 26 | text-align: center; 27 | padding: 30px 0; 28 | margin-top: 70px; 29 | border-top: 1px solid #E5E5E5; 30 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /server/auth/local/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var passport = require('passport'); 5 | var auth = require('../auth.service'); 6 | 7 | var router = express.Router(); 8 | 9 | router.post('/', function(req, res, next) { 10 | passport.authenticate('local', function (err, user, info) { 11 | var error = err || info; 12 | if (error) return res.json(401, error); 13 | if (!user) return res.json(404, {message: 'Something went wrong, please try again.'}); 14 | 15 | var token = auth.signToken(user.id); 16 | res.json({token: token}); 17 | })(req, res, next) 18 | }); 19 | 20 | module.exports = router; -------------------------------------------------------------------------------- /server/api/user/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var express = require('express'); 4 | var controller = require('./user.controller'); 5 | var config = require('../../config/environment'); 6 | var auth = require('../../auth/auth.service'); 7 | 8 | var router = express.Router(); 9 | 10 | router.get('/', controller.index); 11 | //router.delete('/:id', controller.destroy); 12 | //router.get('/me', auth.isAuthenticated(), controller.me); 13 | //router.put('/:id/password', auth.isAuthenticated(), controller.changePassword); 14 | //router.get('/:id', auth.isAuthenticated(), controller.show); 15 | 16 | router.post('/', controller.create); 17 | 18 | module.exports = router; 19 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main application file 3 | */ 4 | 5 | 'use strict'; 6 | 7 | // Set default node environment to development 8 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'; 9 | 10 | var express = require('express'); 11 | var config = require('./config/environment'); 12 | // Setup server 13 | var app = express(); 14 | var server = require('http').createServer(app); 15 | require('./config/express')(app); 16 | require('./routes')(app); 17 | 18 | // Start server 19 | server.listen(config.port, config.ip, function () { 20 | console.log('Express server listening on %d, in %s mode', config.port, app.get('env')); 21 | }); 22 | 23 | // Expose app 24 | exports = module.exports = app; -------------------------------------------------------------------------------- /server/config/environment/production.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Production specific configuration 4 | // ================================= 5 | module.exports = { 6 | // Server IP 7 | ip: process.env.OPENSHIFT_NODEJS_IP || 8 | process.env.IP || 9 | undefined, 10 | 11 | // Server port 12 | port: process.env.OPENSHIFT_NODEJS_PORT || 13 | process.env.PORT || 14 | 8080, 15 | 16 | // MongoDB connection options 17 | mongo: { 18 | uri: process.env.MONGOLAB_URI || 19 | process.env.MONGOHQ_URL || 20 | process.env.OPENSHIFT_MONGODB_DB_URL+process.env.OPENSHIFT_APP_NAME || 21 | 'mongodb://localhost/familythief' 22 | } 23 | }; -------------------------------------------------------------------------------- /server/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main application routes 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var errors = require('./components/errors'); 8 | 9 | module.exports = function(app) { 10 | 11 | // Insert routes below 12 | app.use('/api/things', require('./api/thing')); 13 | app.use('/api/users', require('./api/user')); 14 | 15 | app.use('/auth', require('./auth')); 16 | 17 | // All undefined asset or api routes should return a 404 18 | app.route('/:url(api|auth|components|app|bower_components|assets)/*') 19 | .get(errors[404]); 20 | 21 | // All other routes should redirect to the index.html 22 | app.route('/*') 23 | .get(function(req, res) { 24 | res.sendfile(app.get('appPath') + '/index.html'); 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/auth/local/passport.js: -------------------------------------------------------------------------------- 1 | var passport = require('passport'); 2 | var LocalStrategy = require('passport-local').Strategy; 3 | 4 | exports.setup = function (User, config) { 5 | passport.use(new LocalStrategy({ 6 | usernameField: 'email', 7 | passwordField: 'password' // this is the virtual field on the model 8 | }, 9 | function(email, password, done) { 10 | User.findOne({ 11 | email: email.toLowerCase() 12 | }, function(err, user) { 13 | if (err) return done(err); 14 | 15 | if (!user) { 16 | return done(null, false, { message: 'This email is not registered.' }); 17 | } 18 | if (!user.authenticate(password)) { 19 | return done(null, false, { message: 'This password is not correct.' }); 20 | } 21 | return done(null, user); 22 | }); 23 | } 24 | )); 25 | }; -------------------------------------------------------------------------------- /client/components/navbar/navbar.html: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /server/config/users.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 | instanceMethods: { 16 | 17 | encryptPassword : function(newPassword, callback) { 18 | var self = this; 19 | bcrypt.hash(newPassword, null, null, function(err, hash){ 20 | if (!err) { 21 | self.update({password: hash}).then(callback); 22 | } 23 | }); 24 | }, 25 | 26 | authenticate: function(attemptedPassword) { 27 | return bcrypt.compareSync(attemptedPassword, this.get('password')); 28 | } 29 | } 30 | } 31 | 32 | ); 33 | 34 | db.sync(); 35 | 36 | module.exports = User; 37 | -------------------------------------------------------------------------------- /server/api/user/user.model.js: -------------------------------------------------------------------------------- 1 | var db = require('../../config/dbConfig.js'); 2 | var Sequelize = require('sequelize'); 3 | var bcrypt = require('bcrypt-nodejs'); 4 | 5 | 6 | var User = db.define('User', 7 | { 8 | 9 | username: Sequelize.STRING, 10 | email: Sequelize.STRING, 11 | password: Sequelize.STRING 12 | 13 | }, 14 | 15 | { 16 | instanceMethods: { 17 | 18 | encryptPassword : 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 | authenticate: function(attemptedPassword) { 28 | return bcrypt.compareSync(attemptedPassword, this.get('password')); 29 | } 30 | } 31 | } 32 | 33 | ); 34 | 35 | db.sync(); 36 | 37 | module.exports = User; 38 | -------------------------------------------------------------------------------- /client/app/main/main.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 10 | 11 |
12 |
13 |
14 |

Features:

15 | 18 |
19 |
20 |
21 | 22 | 29 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /server/config/environment/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var _ = require('lodash'); 5 | 6 | function requiredProcessEnv(name) { 7 | if(!process.env[name]) { 8 | throw new Error('You must set the ' + name + ' environment variable'); 9 | } 10 | return process.env[name]; 11 | } 12 | 13 | // All configurations will extend these options 14 | // ============================================ 15 | var all = { 16 | env: process.env.NODE_ENV, 17 | 18 | // Root path of server 19 | root: path.normalize(__dirname + '/../../..'), 20 | 21 | // Server port 22 | port: process.env.PORT || 9000, 23 | 24 | // Should we populate the DB with sample data? 25 | seedDB: false, 26 | 27 | // Secret for session, you will want to change this and make it an environment variable 28 | secrets: { 29 | session: 'family-thief-secret' 30 | }, 31 | 32 | // List of user roles 33 | userRoles: ['guest', 'user', 'admin'], 34 | 35 | // MongoDB connection options 36 | mongo: { 37 | options: { 38 | db: { 39 | safe: true 40 | } 41 | } 42 | }, 43 | 44 | }; 45 | 46 | // Export the config object based on the NODE_ENV 47 | // ============================================== 48 | module.exports = _.merge( 49 | all, 50 | require('./' + process.env.NODE_ENV + '.js') || {}); -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /server/api/thing/thing.controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Using Rails-like standard naming convention for endpoints. 3 | * GET /things -> index 4 | * POST /things -> create 5 | * GET /things/:id -> show 6 | * PUT /things/:id -> update 7 | * DELETE /things/:id -> destroy 8 | */ 9 | 10 | 'use strict'; 11 | 12 | var _ = require('lodash'); 13 | 14 | // Get list of things 15 | exports.index = function(req, res) { 16 | res.json([ 17 | { 18 | name : 'Development Tools', 19 | info : 'Integration with popular tools such as Bower, Grunt, Karma, Mocha, JSHint, Node Inspector, Livereload, Protractor, Jade, Stylus, Sass, CoffeeScript, and Less.' 20 | }, { 21 | name : 'Server and Client integration', 22 | info : 'Built with a powerful and fun stack: MongoDB, Express, AngularJS, and Node.' 23 | }, { 24 | name : 'Smart Build System', 25 | info : 'Build system ignores `spec` files, allowing you to keep tests alongside code. Automatic injection of scripts and styles into your index.html' 26 | }, { 27 | name : 'Modular Structure', 28 | info : 'Best practice client and server structures allow for more code reusability and maximum scalability' 29 | }, { 30 | name : 'Optimized Build', 31 | info : 'Build process packs up your templates as a single JavaScript payload, minifies your scripts/css/images, and rewrites asset names for caching.' 32 | },{ 33 | name : 'Deployment Ready', 34 | info : 'Easily deploy your app to Heroku or Openshift with the heroku and openshift subgenerators' 35 | } 36 | ]); 37 | }; -------------------------------------------------------------------------------- /server/config/express.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Express configuration 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var express = require('express'); 8 | var favicon = require('serve-favicon'); 9 | var morgan = require('morgan'); 10 | var compression = require('compression'); 11 | var bodyParser = require('body-parser'); 12 | var methodOverride = require('method-override'); 13 | var cookieParser = require('cookie-parser'); 14 | var errorHandler = require('errorhandler'); 15 | var path = require('path'); 16 | var config = require('./environment'); 17 | 18 | module.exports = function(app) { 19 | var env = app.get('env'); 20 | 21 | app.set('views', config.root + '/server/views'); 22 | app.engine('html', require('ejs').renderFile); 23 | app.set('view engine', 'html'); 24 | app.use(compression()); 25 | app.use(bodyParser.urlencoded({ extended: false })); 26 | app.use(bodyParser.json()); 27 | app.use(methodOverride()); 28 | app.use(cookieParser()); 29 | 30 | if ('production' === env) { 31 | app.use(favicon(path.join(config.root, 'public', 'favicon.ico'))); 32 | app.use(express.static(path.join(config.root, 'public'))); 33 | app.set('appPath', config.root + '/public'); 34 | app.use(morgan('dev')); 35 | } 36 | 37 | if ('development' === env || 'test' === env) { 38 | app.use(require('connect-livereload')()); 39 | app.use(express.static(path.join(config.root, '.tmp'))); 40 | app.use(express.static(path.join(config.root, 'client'))); 41 | app.set('appPath', 'client'); 42 | app.use(morgan('dev')); 43 | app.use(errorHandler()); // Error handler - has to be last 44 | } 45 | }; -------------------------------------------------------------------------------- /server/api/user/user.model.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('should'); 4 | var app = require('../../app'); 5 | var User = require('./user.model'); 6 | 7 | var user = new User({ 8 | provider: 'local', 9 | name: 'Fake User', 10 | email: 'test@test.com', 11 | password: 'password' 12 | }); 13 | 14 | describe('User Model', function() { 15 | before(function(done) { 16 | // Clear users before testing 17 | User.remove().exec().then(function() { 18 | done(); 19 | }); 20 | }); 21 | 22 | afterEach(function(done) { 23 | User.remove().exec().then(function() { 24 | done(); 25 | }); 26 | }); 27 | 28 | it('should begin with no users', function(done) { 29 | User.find({}, function(err, users) { 30 | users.should.have.length(0); 31 | done(); 32 | }); 33 | }); 34 | 35 | it('should fail when saving a duplicate user', function(done) { 36 | user.save(function() { 37 | var userDup = new User(user); 38 | userDup.save(function(err) { 39 | should.exist(err); 40 | done(); 41 | }); 42 | }); 43 | }); 44 | 45 | it('should fail when saving without an email', function(done) { 46 | user.email = ''; 47 | user.save(function(err) { 48 | should.exist(err); 49 | done(); 50 | }); 51 | }); 52 | 53 | it("should authenticate user if password is valid", function() { 54 | return user.authenticate('password').should.be.true; 55 | }); 56 | 57 | it("should not authenticate user if password is invalid", function() { 58 | return user.authenticate('blah').should.not.be.true; 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /server/auth/auth.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var passport = require('passport'); 4 | var config = require('../config/environment'); 5 | var jwt = require('jsonwebtoken'); 6 | var expressJwt = require('express-jwt'); 7 | var compose = require('composable-middleware'); 8 | var User = require('../api/user/user.model'); 9 | var validateJwt = expressJwt({ secret: config.secrets.session }); 10 | 11 | /** 12 | * Attaches the user object to the request if authenticated 13 | * Otherwise returns 403 14 | */ 15 | function isAuthenticated() { 16 | return compose() 17 | // Validate jwt 18 | .use(function(req, res, next) { 19 | // allow access_token to be passed through query parameter as well 20 | if(req.query && req.query.hasOwnProperty('access_token')) { 21 | req.headers.authorization = 'Bearer ' + req.query.access_token; 22 | } 23 | validateJwt(req, res, next); 24 | }) 25 | // Attach user to request 26 | .use(function(req, res, next) { 27 | User.findById(req.user._id, function (err, user) { 28 | if (err) return next(err); 29 | if (!user) return res.send(401); 30 | 31 | req.user = user; 32 | next(); 33 | }); 34 | }); 35 | } 36 | 37 | /** 38 | * Checks if the user role meets the minimum requirements of the route 39 | */ 40 | function hasRole(roleRequired) { 41 | if (!roleRequired) throw new Error('Required role needs to be set'); 42 | 43 | return compose() 44 | .use(isAuthenticated()) 45 | .use(function meetsRequirements(req, res, next) { 46 | if (config.userRoles.indexOf(req.user.role) >= config.userRoles.indexOf(roleRequired)) { 47 | next(); 48 | } 49 | else { 50 | res.send(403); 51 | } 52 | }); 53 | } 54 | 55 | /** 56 | * Returns a jwt token signed by the app secret 57 | */ 58 | function signToken(id) { 59 | return jwt.sign({ _id: id }, config.secrets.session, { expiresInMinutes: 60*5 }); 60 | } 61 | 62 | /** 63 | * Set token cookie directly for oAuth strategies 64 | */ 65 | function setTokenCookie(req, res) { 66 | if (!req.user) return res.json(404, { message: 'Something went wrong, please try again.'}); 67 | var token = signToken(req.user._id, req.user.role); 68 | res.cookie('token', JSON.stringify(token)); 69 | res.redirect('/'); 70 | } 71 | 72 | exports.isAuthenticated = isAuthenticated; 73 | exports.hasRole = hasRole; 74 | exports.signToken = signToken; 75 | exports.setTokenCookie = setTokenCookie; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /server/api/user/user.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var User = require('./user.model'); 4 | var passport = require('passport'); 5 | var config = require('../../config/environment'); 6 | var jwt = require('jsonwebtoken'); 7 | 8 | var validationError = function(res, err) { 9 | return res.json(422, err); 10 | }; 11 | 12 | /** 13 | * Get list of users 14 | * restriction: 'admin' 15 | */ 16 | exports.index = function(req, res) { 17 | User.find({}, '-salt -hashedPassword', function (err, users) { 18 | if(err) return res.send(500, err); 19 | res.json(200, users); 20 | }); 21 | }; 22 | 23 | /** 24 | * Creates a new user 25 | */ 26 | exports.create = function (req, res, next) { 27 | User.create({username: req.body.username, email: req.body.email}) 28 | .then(function(newUser) { 29 | newUser.encryptPassword(req.body.password, function(newUser) { 30 | var token = jwt.sign({_id: newUser.id}, config.secrets.session, { expiresInMinutes: 60*5 }); 31 | res.json({ token: token }); 32 | }); 33 | }); 34 | } 35 | 36 | /** 37 | * Get a single user 38 | */ 39 | exports.show = function (req, res, next) { 40 | var userId = req.params.id; 41 | 42 | User.findById(userId, function (err, user) { 43 | if (err) return next(err); 44 | if (!user) return res.send(401); 45 | res.json(user.profile); 46 | }); 47 | }; 48 | 49 | /** 50 | * Deletes a user 51 | * restriction: 'admin' 52 | */ 53 | exports.destroy = function(req, res) { 54 | User.findByIdAndRemove(req.params.id, function(err, user) { 55 | if(err) return res.send(500, err); 56 | return res.send(204); 57 | }); 58 | }; 59 | 60 | /** 61 | * Change a users password 62 | */ 63 | exports.changePassword = function(req, res, next) { 64 | var userId = req.user._id; 65 | var oldPass = String(req.body.oldPassword); 66 | var newPass = String(req.body.newPassword); 67 | 68 | User.findById(userId, function (err, user) { 69 | if(user.authenticate(oldPass)) { 70 | user.password = newPass; 71 | user.save(function(err) { 72 | if (err) return validationError(res, err); 73 | res.send(200); 74 | }); 75 | } else { 76 | res.send(403); 77 | } 78 | }); 79 | }; 80 | 81 | /** 82 | * Get my info 83 | */ 84 | exports.me = function(req, res, next) { 85 | var userId = req.user._id; 86 | User.findOne({ 87 | _id: userId 88 | }, '-salt -hashedPassword', function(err, user) { // don't ever give out the password or salt 89 | if (err) return next(err); 90 | if (!user) return res.json(401); 91 | res.json(user); 92 | }); 93 | }; 94 | 95 | /** 96 | * Authentication callback 97 | */ 98 | exports.authCallback = function(req, res, next) { 99 | res.redirect('/'); 100 | }; 101 | -------------------------------------------------------------------------------- /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/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 | 33 | 34 | 35 |
36 | 37 | 38 | 47 | 48 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /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  ������������������������������?��� -------------------------------------------------------------------------------- /server/views/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found :( 6 | 141 | 142 | 143 |
144 |

Not found :(

145 |

Sorry, but the page you were trying to view does not exist.

146 |

It looks like this was the result of either:

147 | 151 | 154 | 155 |
156 | 157 | 158 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------