├── .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 |
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-2 O`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�-2 5r�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��&��)��p 4���&��-���_������������������]���]�������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�&