├── public ├── static │ ├── css │ │ └── style.css │ ├── robots.txt │ ├── images │ │ ├── favicon.ico │ │ └── favicon-196.png │ └── js │ │ └── script.js ├── docs │ ├── readme.md │ ├── JS-Spark-main-page.png │ └── JS-Spark-computing-que-view.png └── index.html ├── private ├── src │ ├── client │ │ ├── app │ │ │ ├── clients │ │ │ │ ├── clients.css │ │ │ │ ├── clients.js │ │ │ │ ├── clients.controller.spec.js │ │ │ │ ├── clients.controller.js │ │ │ │ └── clients.html │ │ │ ├── admin │ │ │ │ ├── admin.css │ │ │ │ ├── admin.js │ │ │ │ ├── admin.controller.js │ │ │ │ └── admin.html │ │ │ ├── main │ │ │ │ ├── main.js │ │ │ │ ├── main.controller.spec.js │ │ │ │ ├── main.controller.js │ │ │ │ ├── main.css │ │ │ │ └── main.html │ │ │ ├── account │ │ │ │ ├── login │ │ │ │ │ ├── login.css │ │ │ │ │ ├── login.controller.js │ │ │ │ │ └── login.html │ │ │ │ ├── account.js │ │ │ │ ├── settings │ │ │ │ │ ├── settings.controller.js │ │ │ │ │ └── settings.html │ │ │ │ └── signup │ │ │ │ │ ├── signup.controller.js │ │ │ │ │ └── signup.html │ │ │ ├── app.css │ │ │ └── app.js │ │ ├── robots.txt │ │ ├── readme.md │ │ ├── favicon.ico │ │ ├── assets │ │ │ └── images │ │ │ │ └── 2014_js_spark.png │ │ ├── components │ │ │ ├── config │ │ │ │ └── config.js │ │ │ ├── jquery │ │ │ │ └── jQuery.js │ │ │ ├── socket │ │ │ │ ├── socket.mock.js │ │ │ │ └── socket.service.js │ │ │ ├── mongoose-error │ │ │ │ └── mongoose-error.directive.js │ │ │ ├── underscore │ │ │ │ └── underscore.js │ │ │ ├── auth │ │ │ │ ├── user.service.js │ │ │ │ └── auth.service.js │ │ │ └── navbar │ │ │ │ ├── navbar.controller.js │ │ │ │ └── navbar.html │ │ ├── .jshintrc │ │ ├── index.html │ │ └── .htaccess │ └── server │ │ ├── readme.md │ │ ├── config │ │ ├── production.js │ │ ├── environment │ │ │ ├── test.js │ │ │ ├── development.js │ │ │ ├── production.js │ │ │ └── index.js │ │ ├── local.env.js │ │ ├── config.js │ │ ├── seedDb.js │ │ ├── express.js │ │ └── di.js │ │ ├── api │ │ ├── thing │ │ │ ├── thing.model.js │ │ │ ├── index.js │ │ │ ├── thing.socket.js │ │ │ ├── thing.spec.js │ │ │ └── thing.controller.js │ │ ├── client │ │ │ ├── client.model.js │ │ │ ├── client.controller.js │ │ │ ├── client.socket.js │ │ │ └── index.js │ │ └── user │ │ │ ├── index.js │ │ │ ├── user.model.spec.js │ │ │ ├── user.controller.js │ │ │ └── user.model.js │ │ ├── controller │ │ ├── index.js │ │ └── di.js │ │ ├── service │ │ ├── index.js │ │ ├── defer.js │ │ ├── fork.js │ │ ├── logging.js │ │ ├── serializer.js │ │ ├── taskManager.js │ │ ├── express.js │ │ ├── jsSpark.js │ │ ├── workers.js │ │ └── dispatcher.js │ │ ├── components │ │ └── errors │ │ │ └── index.js │ │ ├── auth │ │ ├── twitter │ │ │ ├── index.js │ │ │ └── passport.js │ │ ├── facebook │ │ │ ├── index.js │ │ │ └── passport.js │ │ ├── google │ │ │ ├── index.js │ │ │ └── passport.js │ │ ├── local │ │ │ ├── index.js │ │ │ └── passport.js │ │ ├── index.js │ │ └── auth.service.js │ │ ├── routes.js │ │ ├── .jshintrc │ │ ├── app.js │ │ └── views │ │ └── 404.html ├── data │ ├── log │ │ ├── README.md │ │ └── error.log │ └── readme.md ├── test │ ├── readme.md │ ├── functional │ │ └── clientServerArrayMapFilterReduceTest.js │ ├── bootstrap.js │ └── server │ │ └── service │ │ ├── jsSparkTest.js │ │ ├── taskManagerTest.js │ │ └── forkWorkerTest.js └── docs │ └── readme.md ├── .bowerrc ├── .gulprc ├── .travis.yml ├── .gitignore ├── client.js ├── .editorconfig ├── bower.json ├── .yo-rc.json ├── protractor.conf.js ├── karma.conf.js ├── index.js ├── package.json ├── README.md └── Gruntfile.js /public/static/css/style.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /private/src/client/app/clients/clients.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/docs/readme.md: -------------------------------------------------------------------------------- 1 | generated docs 2 | -------------------------------------------------------------------------------- /private/data/log/README.md: -------------------------------------------------------------------------------- 1 | create here 2 | error.log 3 | -------------------------------------------------------------------------------- /public/static/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "private/src/client/component" 3 | } 4 | -------------------------------------------------------------------------------- /.gulprc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "private/src/client/component" 3 | } 4 | -------------------------------------------------------------------------------- /private/src/client/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /private/data/readme.md: -------------------------------------------------------------------------------- 1 | ./drop 2 | ./pickup 3 | 4 | data drop and pickup 5 | -------------------------------------------------------------------------------- /private/src/client/app/admin/admin.css: -------------------------------------------------------------------------------- 1 | .trash { color:rgb(209, 91, 71); } 2 | -------------------------------------------------------------------------------- /private/src/client/readme.md: -------------------------------------------------------------------------------- 1 | code for client 2 | ./controller 3 | ./directive 4 | ./service 5 | -------------------------------------------------------------------------------- /private/src/client/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syzer/JS-Spark/HEAD/private/src/client/favicon.ico -------------------------------------------------------------------------------- /private/test/readme.md: -------------------------------------------------------------------------------- 1 | ./benchmark 2 | ./client 3 | ./server 4 | ./functional 5 | 6 | TODO describe test runners 7 | -------------------------------------------------------------------------------- /public/docs/JS-Spark-main-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syzer/JS-Spark/HEAD/public/docs/JS-Spark-main-page.png -------------------------------------------------------------------------------- /public/static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syzer/JS-Spark/HEAD/public/static/images/favicon.ico -------------------------------------------------------------------------------- /public/static/images/favicon-196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syzer/JS-Spark/HEAD/public/static/images/favicon-196.png -------------------------------------------------------------------------------- /public/docs/JS-Spark-computing-que-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syzer/JS-Spark/HEAD/public/docs/JS-Spark-computing-que-view.png -------------------------------------------------------------------------------- /private/src/client/assets/images/2014_js_spark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syzer/JS-Spark/HEAD/private/src/client/assets/images/2014_js_spark.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 -------------------------------------------------------------------------------- /private/src/server/readme.md: -------------------------------------------------------------------------------- 1 | ./config - all configs 2 | ./controller - uses services 3 | ./model - may be ugly, but sometimes required 4 | ./service - may delegate or even proxy to model, make sure to design good API 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | private/data/drop 3 | private/data/pickup 4 | private/src/client/component 5 | .idea 6 | npm-debug.log 7 | .*.sw[a-p] 8 | private/data/log/error.log 9 | .tmp/ 10 | dist/ 11 | trash/testOfFunctionSerialization.js 12 | trash/ 13 | -------------------------------------------------------------------------------- /private/src/server/config/production.js: -------------------------------------------------------------------------------- 1 | /** 2 | * adapter, overwrite config variables 3 | */ 4 | module.exports = function productionConfig(config) { 5 | 'use strict'; 6 | 7 | config.DOMAIN = "http://localhost:9000"; 8 | 9 | return config; 10 | }; 11 | 12 | -------------------------------------------------------------------------------- /private/src/client/components/config/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 10/24/2014. 3 | */ 4 | angular 5 | .module('config', []) 6 | .factory('config', function () { 7 | return { 8 | dateFormat: 'yyyy-MM-dd HH:mm:ss Z' 9 | }; 10 | }); 11 | -------------------------------------------------------------------------------- /private/src/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/jssparkui-test' 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /private/src/server/api/thing/thing.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'), 4 | Schema = mongoose.Schema; 5 | 6 | var ThingSchema = new Schema({ 7 | name: String, 8 | info: String, 9 | active: Boolean 10 | }); 11 | 12 | module.exports = mongoose.model('Thing', ThingSchema); 13 | -------------------------------------------------------------------------------- /private/src/server/controller/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 7/24/2014. 3 | */ 4 | module.exports = function indexController() { 5 | 6 | return { 7 | 8 | defaultRoute: function (req, res, next) { 9 | console.log('\n'); 10 | return next(); 11 | } 12 | 13 | 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 7/24/2014. 3 | */ 4 | var io = require('socket.io-client'), 5 | ioClient = io.connect('http://localhost:9000'), 6 | _ = require('lodash'); 7 | 8 | var jsSparkClient = require('./private/src/client/components/socket/socket.service')(_); 9 | jsSparkClient.registerJsSparkTaskHandler(ioClient); 10 | -------------------------------------------------------------------------------- /private/src/server/api/client/client.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // TODO finish this 3 | 4 | var mongoose = require('mongoose'), 5 | Schema = mongoose.Schema; 6 | 7 | var ClientSchema = new Schema({ 8 | name: String, 9 | info: String, 10 | active: Boolean 11 | }); 12 | 13 | module.exports = mongoose.model('Client', ClientSchema); 14 | -------------------------------------------------------------------------------- /private/src/client/app/main/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('jsSparkUiApp') 4 | .config(function ($stateProvider) { 5 | $stateProvider 6 | .state('main', { 7 | url: '/', 8 | templateUrl: 'app/main/main.html', 9 | controller: 'MainCtrl' 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /private/src/client/app/admin/admin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('jsSparkUiApp') 4 | .config(function ($stateProvider) { 5 | $stateProvider 6 | .state('admin', { 7 | url: '/admin', 8 | templateUrl: 'app/admin/admin.html', 9 | controller: 'AdminCtrl' 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /private/src/server/api/client/client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | /** 5 | * Get list of users 6 | * restriction: 'admin' 7 | */ 8 | module.exports = function (taskManagerService) { 9 | 10 | return { 11 | index: function (req, res) { 12 | res.status(200).json(taskManagerService.getWorkers()); 13 | } 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /private/src/server/api/client/client.socket.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Broadcast updates to client when the model changes 3 | */ 4 | module.exports = function clientSocketApi(workers) { 5 | 'use strict'; 6 | 7 | return { 8 | //TODO check authorized 9 | register: function (socket) { 10 | workers.addListener(socket); 11 | } 12 | }; 13 | 14 | }; 15 | -------------------------------------------------------------------------------- /private/data/log/error.log: -------------------------------------------------------------------------------- 1 | {"level":"error","message":"client q9iy_GG6EZ118onAAAAB , task q9iy_GG6EZ118onAAAAB10 , reports error: TypeError: undefined is not a function","timestamp":"2015-04-09T18:59:04.251Z"} 2 | {"level":"error","message":"client 5Bg8DVvr3PxvP8IqAAAB , task 5Bg8DVvr3PxvP8IqAAAB10 , reports error: TypeError: undefined is not a function","timestamp":"2015-04-09T18:59:34.994Z"} 3 | -------------------------------------------------------------------------------- /private/src/server/service/index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Created by syzer on 4/16/2014. 4 | */ 5 | module.exports = function indexService(root) { 6 | 'use strict'; 7 | return { 8 | defaultRoute: function (req, res) { 9 | "use strict"; 10 | res.contentType("text/html"); 11 | res.sendfile("public/index.html"); 12 | }, 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /private/test/functional/clientServerArrayMapFilterReduceTest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 8/28/2014. 3 | */ 4 | describe('Client Executes Computation', function () { 5 | 6 | afterEach(function (done) { 7 | setTimeout(done, 60); 8 | }); 9 | 10 | it('TODO', function (done) { 11 | expect(true).eql(true); 12 | 13 | done(); 14 | }); 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /private/src/client/components/jquery/jQuery.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 9/22/2014. 3 | */ 4 | angular 5 | .module('jQuery', []) 6 | .factory('$', function () { 7 | 'use strict'; 8 | 9 | if (!window.$) { 10 | console.error('No jquery!'); 11 | } 12 | 13 | return window.$; // assumes underscore has already been loaded on the page 14 | }); 15 | -------------------------------------------------------------------------------- /private/src/client/components/socket/socket.mock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('socketMock', []) 4 | .factory('socket', function() { 5 | return { 6 | socket: { 7 | connect: function() {}, 8 | on: function() {}, 9 | emit: function() {}, 10 | receive: function() {} 11 | }, 12 | 13 | syncUpdates: function() {}, 14 | unsyncUpdates: function() {} 15 | }; 16 | }); -------------------------------------------------------------------------------- /private/src/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/jssparkui-dev', 9 | options: { 10 | user: 'js-spark', 11 | pass: 'js-spark1' 12 | } 13 | }, 14 | 15 | seedDB: true 16 | //seedDB: false 17 | }; 18 | -------------------------------------------------------------------------------- /private/src/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 | router.get('/:id', controller.show); 10 | router.post('/', controller.create); 11 | router.put('/:id', controller.update); 12 | router.patch('/:id', controller.update); 13 | router.delete('/:id', controller.destroy); 14 | 15 | module.exports = router; -------------------------------------------------------------------------------- /private/src/client/app/account/login/login.css: -------------------------------------------------------------------------------- 1 | .btn-facebook { 2 | color: #fff; 3 | background-color: #3B5998; 4 | border-color: #133783; 5 | } 6 | 7 | .btn-twitter { 8 | color: #fff; 9 | background-color: #2daddc; 10 | border-color: #0271bf; 11 | } 12 | 13 | .btn-google-plus { 14 | color: #fff; 15 | background-color: #dd4b39; 16 | border-color: #c53727; 17 | } 18 | 19 | .btn-github { 20 | color: #fff; 21 | background-color: #fafafa; 22 | border-color: #ccc; 23 | } 24 | -------------------------------------------------------------------------------- /private/src/server/api/client/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function clientIndex(di) { 2 | 'use strict'; 3 | 4 | var express = require('express'); 5 | var taskManagerService = di.get('service.taskManager'); 6 | var controller = require('./client.controller')(taskManagerService); 7 | var auth = require('../../auth/auth.service'); 8 | 9 | var router = express.Router(); 10 | 11 | router.get('/', auth.hasRole('admin'), controller.index); 12 | 13 | return router; 14 | }; 15 | -------------------------------------------------------------------------------- /.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 = 4 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 | -------------------------------------------------------------------------------- /private/src/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) { 17 | return res.json(result, result.status); 18 | } 19 | 20 | res.render(viewFilePath); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /private/src/server/auth/twitter/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 10 | .get('/', passport.authenticate('twitter', { 11 | failureRedirect: '/signup', 12 | session: false 13 | })) 14 | 15 | .get('/callback', passport.authenticate('twitter', { 16 | failureRedirect: '/signup', 17 | session: false 18 | }), auth.setTokenCookie); 19 | 20 | module.exports = router; 21 | -------------------------------------------------------------------------------- /private/src/client/app/clients/clients.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 8/28/2014. 3 | */ 4 | angular.module('jsSparkUiApp') 5 | .config(function ($stateProvider) { 6 | $stateProvider 7 | .state('clients', { 8 | url: '/clients', 9 | templateUrl: 'app/clients/clients.html', 10 | controller: 'ClientsCtrl' 11 | }); 12 | }) 13 | .filter('boolean', function (_, $filter) { 14 | return function booleanFilter(input) { 15 | return input ? '\u2713' : '\u2718'; 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /private/src/server/service/defer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 10/9/2014. 3 | */ 4 | module.exports = function deferService(Promise) { 5 | 6 | return function defer() { 7 | var resolve, reject; 8 | // i highly doubt that it will be memory efficient 9 | var promise = new Promise(function () { 10 | resolve = arguments[0]; 11 | reject = arguments[1]; 12 | }); 13 | 14 | return { 15 | resolve: resolve, 16 | reject: reject, 17 | promise: promise 18 | }; 19 | } 20 | }; 21 | 22 | -------------------------------------------------------------------------------- /private/src/client/components/mongoose-error/mongoose-error.directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Removes server error when user updates input 5 | */ 6 | angular.module('jsSparkUiApp') 7 | .directive('mongooseError', function () { 8 | return { 9 | restrict: 'A', 10 | require: 'ngModel', 11 | link: function (scope, element, attrs, ngModel) { 12 | element.on('keydown', function () { 13 | return ngModel.$setValidity('mongoose', true); 14 | }); 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /private/src/client/app/admin/admin.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('jsSparkUiApp') 4 | .controller('AdminCtrl', function ($scope, $http, Auth, User) { 5 | 6 | // Use the User $resource to fetch all users 7 | $scope.users = User.query(); 8 | 9 | $scope.delete = function (user) { 10 | User.remove({ id: user._id }); 11 | angular.forEach($scope.users, function (u, i) { 12 | if (u === user) { 13 | $scope.users.splice(i, 1); 14 | } 15 | }); 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /private/src/client/app/admin/admin.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

The delete user and user index api routes are restricted to users with the 'admin' role.

5 | 12 |
13 | -------------------------------------------------------------------------------- /private/src/server/service/fork.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 12/28/2014. 3 | */ 4 | module.exports = function forkService(log, fork, _, q, workerCode) { 5 | 'use strict'; 6 | 7 | return { 8 | forkWorker: forkWorker 9 | }; 10 | 11 | // TODO client.js->bower 12 | function forkWorker(config) { 13 | var todos = []; 14 | config = config || {times: 1}; 15 | 16 | _.times(config.times, function () { 17 | // 'client.js' 18 | todos.push(fork(workerCode, [])); 19 | }); 20 | 21 | return q.all(todos); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /private/src/server/api/thing/thing.socket.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Broadcast updates to client when the model changes 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var thing = require('./thing.model'); 8 | 9 | exports.register = function (socket) { 10 | thing.schema.post('save', function (doc) { 11 | onSave(socket, doc); 12 | }); 13 | thing.schema.post('remove', function (doc) { 14 | onRemove(socket, doc); 15 | }); 16 | }; 17 | 18 | function onSave(socket, doc, cb) { 19 | socket.emit('thing:save', doc); 20 | } 21 | 22 | function onRemove(socket, doc, cb) { 23 | socket.emit('thing:remove', doc); 24 | } 25 | -------------------------------------------------------------------------------- /private/src/server/auth/facebook/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 10 | .get('/', passport.authenticate('facebook', { 11 | scope: ['email', 'user_about_me'], 12 | failureRedirect: '/signup', 13 | session: false 14 | })) 15 | 16 | .get('/callback', passport.authenticate('facebook', { 17 | failureRedirect: '/signup', 18 | session: false 19 | }), auth.setTokenCookie); 20 | 21 | module.exports = router; 22 | -------------------------------------------------------------------------------- /private/src/client/app/clients/clients.controller.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: ClientsCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('jsSparkUiApp')); 7 | 8 | var ClientsCtrl, scope; 9 | 10 | // Initialize the controller and a mock scope 11 | beforeEach(inject(function ($controller, $rootScope) { 12 | scope = $rootScope.$new(); 13 | ClientsCtrl = $controller('ClientsCtrl', { 14 | $scope: scope 15 | }); 16 | })); 17 | 18 | it('should ...', function () { 19 | expect(1).toEqual(1); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /private/src/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 | -------------------------------------------------------------------------------- /private/test/bootstrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 8/28/2014. 3 | */ 4 | var expect = require('chai').expect; 5 | var jsSpark, di; 6 | global.ROOT_PATH = __dirname + '/../src/server/'; 7 | global.TEST_PATH = './'; 8 | global.expect = expect; 9 | 10 | function initialize() { 11 | var services = require(ROOT_PATH + 'config/di').services; 12 | di = require(ROOT_PATH + 'controller/di')(services); 13 | di.get('server').listen(di.get('port')); 14 | return di.get('service.jsSpark'); 15 | } 16 | 17 | global.initialize = initialize; 18 | global.jsSpark = jsSpark || initialize(); 19 | global.di = di; 20 | 21 | module.exports = global; 22 | -------------------------------------------------------------------------------- /private/src/client/app/clients/clients.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('jsSparkUiApp') 4 | .controller('ClientsCtrl', function ($scope, $http, socket) { 5 | $scope.clients = []; 6 | 7 | $http.get('/api/clients').success(function (clients) { 8 | $scope.clients = clients; 9 | socket.syncUpdates('client', $scope.clients); 10 | }); 11 | 12 | //TODO move to alert service 13 | $scope.closeAlert = function (index) { 14 | console.warn('closing', index); 15 | }; 16 | 17 | $scope.$on('$destroy', function () { 18 | socket.unsyncUpdates('client'); 19 | }); 20 | }); 21 | 22 | -------------------------------------------------------------------------------- /private/src/client/components/underscore/underscore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 5/12/2014. 3 | */ 4 | // assumes underscore has already been loaded on the page 5 | angular 6 | .module('_', []) 7 | .factory('_', function () { 8 | var _ = window._; 9 | var isNumerical = function (string) { 10 | return !_.isNaN(_.parseInt(string)); 11 | }; 12 | // new Date will not interpolate 13 | var isISODate = function (string) { 14 | return !_.isNaN(Date.parse(string)); 15 | }; 16 | _.mixin({ 17 | isNumerical: isNumerical, 18 | isISODate: isISODate 19 | }); 20 | 21 | return _; 22 | }); 23 | -------------------------------------------------------------------------------- /private/src/client/components/auth/user.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('jsSparkUiApp') 4 | .factory('User', function ($resource) { 5 | return $resource('/api/users/:id/:controller', { 6 | id: '@_id' 7 | }, 8 | { 9 | changePassword: { 10 | method: 'PUT', 11 | params: { 12 | controller: 'password' 13 | } 14 | }, 15 | get: { 16 | method: 'GET', 17 | params: { 18 | id: 'me' 19 | } 20 | } 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /private/src/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('/', auth.hasRole('admin'), controller.index); 11 | router.delete('/:id', auth.hasRole('admin'), 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 | router.post('/', controller.create); 16 | 17 | module.exports = router; 18 | -------------------------------------------------------------------------------- /private/src/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/jssparkui' 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /private/src/server/config/local.env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Environment variables that grunt will set when the server starts locally. Use for your api keys, secrets, etc. 4 | // You will need to set these on the server you deploy to. 5 | // 6 | // This file should not be tracked by git. 7 | 8 | module.exports = { 9 | DOMAIN: 'http://localhost:9000', 10 | SESSION_SECRET: "jssparkui-secret", 11 | 12 | FACEBOOK_ID: 'app-id', 13 | FACEBOOK_SECRET: 'secret', 14 | 15 | TWITTER_ID: 'app-id', 16 | TWITTER_SECRET: 'secret', 17 | 18 | GOOGLE_ID: 'app-id', 19 | GOOGLE_SECRET: 'secret', 20 | 21 | // Control debug level for modules using visionmedia/debug 22 | DEBUG: 'http*,socket.io:socket' 23 | }; 24 | -------------------------------------------------------------------------------- /private/src/server/auth/google/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 10 | .get('/', passport.authenticate('google', { 11 | failureRedirect: '/signup', 12 | scope: [ 13 | 'https://www.googleapis.com/auth/userinfo.profile', 14 | 'https://www.googleapis.com/auth/userinfo.email' 15 | ], 16 | session: false 17 | })) 18 | 19 | .get('/callback', passport.authenticate('google', { 20 | failureRedirect: '/signup', 21 | session: false 22 | }), auth.setTokenCookie); 23 | 24 | module.exports = router; 25 | -------------------------------------------------------------------------------- /private/src/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.status(401).json(error); 13 | if (!user) return res.status(404).json({message: 'Something went wrong, please try again.'}); 14 | 15 | var token = auth.signToken(user._id, user.role); 16 | console.warn('token', token); 17 | res.json({token: token}); 18 | })(req, res, next) 19 | }); 20 | 21 | module.exports = router; 22 | -------------------------------------------------------------------------------- /private/src/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 | require('./facebook/passport').setup(User, config); 11 | require('./google/passport').setup(User, config); 12 | require('./twitter/passport').setup(User, config); 13 | 14 | var router = express.Router(); 15 | 16 | router.use('/local', require('./local')); 17 | router.use('/facebook', require('./facebook')); 18 | router.use('/twitter', require('./twitter')); 19 | router.use('/google', require('./google')); 20 | 21 | module.exports = router; 22 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSPark client 6 | 7 | 13 | 14 | 15 | 20 |
21 | 22 | 23 | 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /private/test/server/service/jsSparkTest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 8/28/2014. 3 | */ 4 | describe('Js-Spark Service', function () { 5 | 6 | afterEach(function (done) { 7 | setTimeout(done, 60); 8 | }); 9 | 10 | it('can initialize', function (done) { 11 | expect(jsSpark).to.be.an.instanceof(Function); 12 | 13 | done(); 14 | }); 15 | 16 | it('run() returns promise', function (done) { 17 | var promise = jsSpark([1,2,3]).map(function multiplyBy2(el) { 18 | return el * 2; 19 | }).run(); 20 | 21 | expect(promise.then).to.be.an('function'); 22 | expect(promise.catch).to.be.an('function'); 23 | expect(promise.all).to.be.an('function'); 24 | 25 | done(); 26 | }); 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /private/test/server/service/taskManagerTest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 12/27/2014. 3 | */ 4 | describe('Task Manager Service', function () { 5 | var taskManager; 6 | 7 | afterEach(function (done) { 8 | setTimeout(done, 60); 9 | }); 10 | 11 | it('can initialize', function (done) { 12 | taskManager = di.get('service.taskManager'); 13 | expect(taskManager.init).to.be.an.instanceof(Function); 14 | 15 | done(); 16 | }); 17 | 18 | it('can stop()', function (done) { 19 | expect(taskManager.stop).to.be.an.instanceof(Function); 20 | var stopDate = taskManager.stop(); 21 | 22 | taskManager.init(); 23 | expect(stopDate).to.be.below(new Date()); 24 | 25 | done(); 26 | }); 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /private/src/client/components/navbar/navbar.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('jsSparkUiApp') 4 | .controller('NavbarCtrl', function ($scope, $location, Auth) { 5 | $scope.menu = [ 6 | { 7 | 'title': 'Home', 8 | 'link': '/' 9 | } 10 | ]; 11 | 12 | $scope.isCollapsed = true; 13 | $scope.isLoggedIn = Auth.isLoggedIn; 14 | $scope.isAdmin = Auth.isAdmin; 15 | $scope.getCurrentUser = Auth.getCurrentUser; 16 | 17 | $scope.logout = function () { 18 | Auth.logout(); 19 | $location.path('/login'); 20 | }; 21 | 22 | $scope.isActive = function (route) { 23 | return route === $location.path(); 24 | }; 25 | }); 26 | -------------------------------------------------------------------------------- /private/test/server/service/forkWorkerTest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 12/28/2014. 3 | */ 4 | describe('Forks new workers', function () { 5 | 'use strict'; 6 | 7 | var forkService; 8 | 9 | afterEach(function (done) { 10 | setTimeout(done, 60); 11 | }); 12 | 13 | it('can fork new worker and return new promise', function (done) { 14 | forkService = di.get('service.fork'); 15 | expect(forkService.forkWorker().then).to.be.a('function'); 16 | 17 | done(); 18 | }); 19 | 20 | it('can fork new worker 2 workers', function (done) { 21 | forkService = di.get('service.fork'); 22 | var promise = forkService.forkWorker({times: 2}); 23 | expect(promise.then).to.be.a('function'); 24 | 25 | done(); 26 | }); 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /private/src/server/config/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 4/24/2014. 3 | */ 4 | module.exports = function (ROOT_PATH, DATA_PATH, MAIN_PATH) { 5 | 'use strict'; 6 | 7 | // Avoids DEPTH_ZERO_SELF_SIGNED_CERT error for self-signed certs 8 | // process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; 9 | 10 | return { 11 | 12 | api: { 13 | url: 'https://localhost:3000/api/' 14 | }, 15 | ssl: { 16 | key: 'data/ssl/key.pem', 17 | cert: 'data/ssl/key-cert.pem' 18 | }, 19 | log: { 20 | error: { 21 | file: (function () { 22 | return DATA_PATH + '/log/error.log'; 23 | })() 24 | }, 25 | level: 'warn' 26 | } 27 | } 28 | }; 29 | 30 | 31 | -------------------------------------------------------------------------------- /private/src/client/app/account/account.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('jsSparkUiApp') 4 | .config(function ($stateProvider) { 5 | $stateProvider 6 | .state('login', { 7 | url: '/login', 8 | templateUrl: 'app/account/login/login.html', 9 | controller: 'LoginCtrl' 10 | }) 11 | .state('signup', { 12 | url: '/signup', 13 | templateUrl: 'app/account/signup/signup.html', 14 | controller: 'SignupCtrl' 15 | }) 16 | .state('settings', { 17 | url: '/settings', 18 | templateUrl: 'app/account/settings/settings.html', 19 | controller: 'SettingsCtrl', 20 | authenticate: true 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /private/src/client/app/account/settings/settings.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('jsSparkUiApp') 4 | .controller('SettingsCtrl', function ($scope, User, Auth) { 5 | $scope.errors = {}; 6 | 7 | $scope.changePassword = function (form) { 8 | $scope.submitted = true; 9 | if (form.$valid) { 10 | Auth.changePassword($scope.user.oldPassword, $scope.user.newPassword) 11 | .then(function () { 12 | $scope.message = 'Password successfully changed.'; 13 | }) 14 | .catch(function () { 15 | form.password.$setValidity('mongoose', false); 16 | $scope.errors.other = 'Incorrect password'; 17 | $scope.message = ''; 18 | }); 19 | } 20 | }; 21 | }); 22 | -------------------------------------------------------------------------------- /private/src/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, di) { 10 | 11 | // Insert routes below 12 | app.use('/api/clients', require('./api/client')(di)); 13 | app.use('/api/things', require('./api/thing')); 14 | app.use('/api/users', require('./api/user')); 15 | 16 | app.use('/auth', require('./auth')); 17 | 18 | // All undefined asset or api routes should return a 404 19 | app.route('/:url(api|auth|component|app|components|assets)/*') 20 | .get(errors[404]); 21 | 22 | // TODO deprecation warning on sendfile-> sendFile 23 | // All other routes should redirect to the index.html 24 | app.route('/*') 25 | .get(function (req, res) { 26 | res.sendfile(app.get('appPath') + '/index.html'); 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /private/src/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('jsSparkUiApp')); 7 | beforeEach(module('socketMock')); 8 | 9 | var MainCtrl, 10 | scope, 11 | $httpBackend; 12 | 13 | // Initialize the controller and a mock scope 14 | beforeEach(inject(function (_$httpBackend_, $controller, $rootScope) { 15 | $httpBackend = _$httpBackend_; 16 | $httpBackend.expectGET('/api/things') 17 | .respond(['HTML5 Boilerplate', 'AngularJS', 'Karma', 'Express']); 18 | 19 | scope = $rootScope.$new(); 20 | MainCtrl = $controller('MainCtrl', { 21 | $scope: scope 22 | }); 23 | })); 24 | 25 | it('should attach a list of things to the scope', function () { 26 | $httpBackend.flush(); 27 | expect(scope.awesomeThings.length).toBe(4); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /private/src/client/app/main/main.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('jsSparkUiApp') 4 | .controller('MainCtrl', function ($scope, $http, socket) { 5 | $scope.awesomeThings = []; 6 | 7 | $http.get('/api/things').success(function (awesomeThings) { 8 | $scope.awesomeThings = awesomeThings; 9 | socket.syncUpdates('thing', $scope.awesomeThings); 10 | }); 11 | 12 | $scope.addThing = function () { 13 | if ($scope.newThing === '') { 14 | return; 15 | } 16 | $http.post('/api/things', { name: $scope.newThing }); 17 | $scope.newThing = ''; 18 | }; 19 | 20 | $scope.deleteThing = function (thing) { 21 | $http.delete('/api/things/' + thing._id); 22 | }; 23 | 24 | $scope.$on('$destroy', function () { 25 | socket.unsyncUpdates('thing'); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /private/src/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": 4, 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 | -------------------------------------------------------------------------------- /private/src/server/.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": 4, 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 | -------------------------------------------------------------------------------- /private/src/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 | }; 26 | -------------------------------------------------------------------------------- /private/src/server/service/logging.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 4/10/2014. 3 | * newrelic dependence is required 4 | */ 5 | module.exports = function logService(config, winston, newrelic) { 6 | 'use strict'; 7 | 8 | var fileName = config.log.error.file; 9 | 10 | var consoleLogConfig = { 11 | transports: [ 12 | new (winston.transports.Console)({ level: config.log.level, colorize: true }) 13 | ] 14 | }; 15 | 16 | var productionLogConfig = { 17 | transports: [ 18 | new (winston.transports.Console)({ level: config.log.level, colorize: true }), 19 | new (winston.transports.File)({ filename: fileName, level: 'error' }) 20 | ] 21 | }; 22 | 23 | var createLog = function (fileName, configInjected) { 24 | var config = productionLogConfig || configInjected; 25 | return new (winston.Logger)(config); 26 | }; 27 | 28 | var createConsoleLog = function () { 29 | return createLog('', consoleLogConfig); 30 | }; 31 | 32 | return { 33 | createLog: createLog, 34 | createConsoleLog: createConsoleLog 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JS-Spark", 3 | "version": "0.0.0", 4 | "homepage": "https://github.com/syzer/JS-Spark", 5 | "authors": [ 6 | "syzer " 7 | ], 8 | "description": "Distributed computing cluster using web browsers", 9 | "moduleType": [ 10 | "node" 11 | ], 12 | "keywords": [ 13 | "spark", 14 | "distributed", 15 | "computing", 16 | "rpc", 17 | "hadoop", 18 | "hdfs" 19 | ], 20 | "license": "MIT", 21 | "private": true, 22 | "dependencies": { 23 | "papa-parse": "~3.0.1", 24 | "json3": "~3.3.2", 25 | "angular": "~1.4.2", 26 | "angular-resource": "~1.4.2", 27 | "angular-sanitize": "~1.4.2", 28 | "angular-bootstrap": "~0.11.2", 29 | "angular-cookies": "~1.4.2", 30 | "angular-mocks": "~1.4.2", 31 | "angular-scenario": "~1.4.2", 32 | "angular-socket-io": "~0.6.0", 33 | "angular-ui-router": "~0.2.11", 34 | "bootstrap": "~3.2.0", 35 | "es5-shim": "~4.0.3", 36 | "font-awesome": "~4.2.0", 37 | "lodash": "~3.6.0" 38 | }, 39 | "devDependencies": { 40 | "angular-mocks": ">=1.2.*", 41 | "angular-scenario": ">=1.2.*" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /private/src/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: #000000; 20 | text-align: center; 21 | text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2); 22 | background: white; 23 | } 24 | 25 | .footer { 26 | text-align: center; 27 | padding: 30px 0; 28 | margin-top: 70px; 29 | border-top: 1px solid #E5E5E5; 30 | background: #e7e7e7; 31 | } 32 | 33 | .animate-repeat { 34 | line-height:40px; 35 | list-style:none; 36 | box-sizing:border-box; 37 | } 38 | 39 | .animate-repeat.ng-move, 40 | .animate-repeat.ng-enter, 41 | .animate-repeat.ng-leave { 42 | -webkit-transition:all linear 0.5s; 43 | transition:all linear 0.5s; 44 | } 45 | 46 | .animate-repeat.ng-leave.ng-leave-active, 47 | .animate-repeat.ng-move, 48 | .animate-repeat.ng-enter { 49 | opacity:0; 50 | max-height:0; 51 | } 52 | 53 | .animate-repeat.ng-leave, 54 | .animate-repeat.ng-move.ng-move-active, 55 | .animate-repeat.ng-enter.ng-enter-active { 56 | opacity:1; 57 | max-height:40px; 58 | } 59 | 60 | -------------------------------------------------------------------------------- /private/src/server/auth/google/passport.js: -------------------------------------------------------------------------------- 1 | var passport = require('passport'); 2 | var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy; 3 | 4 | exports.setup = function (User, config) { 5 | passport.use(new GoogleStrategy({ 6 | clientID: config.google.clientID, 7 | clientSecret: config.google.clientSecret, 8 | callbackURL: config.google.callbackURL 9 | }, 10 | function (accessToken, refreshToken, profile, done) { 11 | User.findOne({ 12 | 'google.id': profile.id 13 | }, function (err, user) { 14 | if (!user) { 15 | user = new User({ 16 | name: profile.displayName, 17 | email: profile.emails[0].value, 18 | role: 'user', 19 | username: profile.username, 20 | provider: 'google', 21 | google: profile._json 22 | }); 23 | user.save(function (err) { 24 | if (err) done(err); 25 | return done(err, user); 26 | }); 27 | } else { 28 | return done(err, user); 29 | } 30 | }); 31 | } 32 | )); 33 | }; 34 | -------------------------------------------------------------------------------- /private/src/server/auth/twitter/passport.js: -------------------------------------------------------------------------------- 1 | exports.setup = function (User, config) { 2 | var passport = require('passport'); 3 | var TwitterStrategy = require('passport-twitter').Strategy; 4 | 5 | passport.use(new TwitterStrategy({ 6 | consumerKey: config.twitter.clientID, 7 | consumerSecret: config.twitter.clientSecret, 8 | callbackURL: config.twitter.callbackURL 9 | }, 10 | function (token, tokenSecret, profile, done) { 11 | User.findOne({ 12 | 'twitter.id_str': profile.id 13 | }, function (err, user) { 14 | if (err) { 15 | return done(err); 16 | } 17 | if (!user) { 18 | user = new User({ 19 | name: profile.displayName, 20 | username: profile.username, 21 | role: 'user', 22 | provider: 'twitter', 23 | twitter: profile._json 24 | }); 25 | user.save(function (err) { 26 | if (err) return done(err); 27 | return done(err, user); 28 | }); 29 | } else { 30 | return done(err, user); 31 | } 32 | }); 33 | } 34 | )); 35 | }; 36 | -------------------------------------------------------------------------------- /private/src/client/app/clients/clients.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

Here are all clients that are connected:

5 | 6 | These clients have no tasks 7 | These clients are calculating 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
#IdEmailIPFreeConnected @Computing quesPoints
{{$index+1}}{{client._id}}{{client.email}}{{client.handshake.address}}{{client.free|boolean}}{{client.connectedAt|date : 'medium'}}{{client.rooms|json}}{{client.points}}
34 |
35 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-angular-fullstack": { 3 | "insertRoutes": true, 4 | "registerRoutesFile": "private/src/server/routes.js", 5 | "routesNeedle": "// Insert routes below", 6 | "routesBase": "/api/", 7 | "pluralizeRoutes": true, 8 | "insertSockets": true, 9 | "registerSocketsFile": "private/src/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 | "socketio": true, 19 | "mongoose": true, 20 | "auth": true, 21 | "oauth": true, 22 | "googleAuth": true, 23 | "facebookAuth": true, 24 | "twitterAuth": true 25 | } 26 | }, 27 | "generator-ng-component": { 28 | "routeDirectory": "private/src/client/app/", 29 | "directiveDirectory": "private/src/client/app/", 30 | "filterDirectory": "private/src/client/app/", 31 | "serviceDirectory": "private/src/client/app/", 32 | "basePath": "private/src/client", 33 | "filters": [ 34 | "uirouter" 35 | ], 36 | "extensions": [ 37 | "js", 38 | "html", 39 | "css" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /public/static/js/script.js: -------------------------------------------------------------------------------- 1 | /* Author: YOUR NAME HERE 2 | */ 3 | 4 | (function() { 5 | 6 | var ioClient = io.connect(); 7 | var seq = 0; 8 | 9 | ioClient.on('task', function (serializedTask) { 10 | seq++; 11 | console.log(serializedTask); 12 | document.querySelector("#seq").textContent = seq; 13 | document.querySelector("#task").textContent = serializedTask; 14 | var task, 15 | response; 16 | try { 17 | task = JSON.parse(serializedTask, functionCreate); 18 | response = task.execute(_, task.data, task.callbacks).value(); 19 | document.querySelector("#result").textContent = response; 20 | } catch (e) { 21 | console.log(e.toString()); 22 | document.querySelector("#result").textContent = e.toString(); 23 | } 24 | ioClient.emit('response', response.toString()); 25 | console.log('Client response', response); 26 | }); 27 | 28 | // CSP may block Function call, function used not to use eval 29 | function functionCreate(key, value) { 30 | 31 | if (!key) { 32 | return value; 33 | } 34 | 35 | if ('string' === typeof value) { 36 | var funcRegExp = /function[^\(]*\(([^\)]*)\)[^\{]*{([^\}]*)\}/, 37 | match = value.match(funcRegExp); 38 | if (match) { 39 | var args = match[1] 40 | .split(',') 41 | .map(function (arg) { 42 | return arg.replace(/\s+/, ''); 43 | }); 44 | return new Function(args, match[2]); 45 | } 46 | } 47 | return value; 48 | } 49 | 50 | })(); 51 | -------------------------------------------------------------------------------- /private/src/client/app/account/signup/signup.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('jsSparkUiApp') 4 | .controller('SignupCtrl', function ($scope, Auth, $location, $window) { 5 | $scope.user = {}; 6 | $scope.errors = {}; 7 | 8 | $scope.register = function (form) { 9 | $scope.submitted = true; 10 | 11 | if (form.$valid) { 12 | Auth.createUser({ 13 | name: $scope.user.name, 14 | email: $scope.user.email, 15 | password: $scope.user.password 16 | }) 17 | .then(function () { 18 | // Account created, redirect to home 19 | $location.path('/'); 20 | }) 21 | .catch(function (err) { 22 | err = err.data; 23 | $scope.errors = {}; 24 | 25 | // Update validity of form fields that match the mongoose errors 26 | angular.forEach(err.errors, function (error, field) { 27 | form[field].$setValidity('mongoose', false); 28 | $scope.errors[field] = error.message; 29 | }); 30 | }); 31 | } 32 | }; 33 | 34 | $scope.loginOauth = function (provider) { 35 | $window.location.href = '/auth/' + provider; 36 | }; 37 | }); 38 | -------------------------------------------------------------------------------- /private/src/client/app/app.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Bootstrap Fonts 4 | */ 5 | 6 | @font-face { 7 | font-family: 'Glyphicons Halflings'; 8 | src: url('../component/bootstrap/fonts/glyphicons-halflings-regular.eot'); 9 | src: url('../component/bootstrap/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), 10 | url('../component/bootstrap/fonts/glyphicons-halflings-regular.woff') format('woff'), 11 | url('../component/bootstrap/fonts/glyphicons-halflings-regular.ttf') format('truetype'), 12 | url('../component/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('../component/font-awesome/fonts/fontawesome-webfont.eot?v=4.1.0'); 22 | src: url('../component/font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'), 23 | url('../component/font-awesome/fonts/fontawesome-webfont.woff?v=4.1.0') format('woff'), 24 | url('../component/font-awesome/fonts/fontawesome-webfont.ttf?v=4.1.0') format('truetype'), 25 | url('../component/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 | -------------------------------------------------------------------------------- /private/src/server/auth/facebook/passport.js: -------------------------------------------------------------------------------- 1 | var passport = require('passport'); 2 | var FacebookStrategy = require('passport-facebook').Strategy; 3 | 4 | exports.setup = function (User, config) { 5 | passport.use(new FacebookStrategy({ 6 | clientID: config.facebook.clientID, 7 | clientSecret: config.facebook.clientSecret, 8 | callbackURL: config.facebook.callbackURL 9 | }, 10 | function (accessToken, refreshToken, profile, done) { 11 | User.findOne({ 12 | 'facebook.id': profile.id 13 | }, 14 | function (err, user) { 15 | if (err) { 16 | return done(err); 17 | } 18 | if (!user) { 19 | user = new User({ 20 | name: profile.displayName, 21 | email: profile.emails[0].value, 22 | role: 'user', 23 | username: profile.username, 24 | provider: 'facebook', 25 | facebook: profile._json 26 | }); 27 | user.save(function (err) { 28 | if (err) done(err); 29 | return done(err, user); 30 | }); 31 | } else { 32 | return done(err, user); 33 | } 34 | }) 35 | } 36 | )); 37 | }; 38 | -------------------------------------------------------------------------------- /private/src/server/service/serializer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 7/24/2014. 3 | */ 4 | module.exports = function serializerService(serializerInj) { 5 | 6 | var JSON = serializerInj || JSON; 7 | var LAME_ERROR = 'To unserialize please give a string'; 8 | 9 | return { 10 | 11 | // serializes objects 12 | // TODO type check 13 | stringify: function (object) { 14 | return JSON.stringify(object, functionStringify); 15 | }, 16 | 17 | parse: function (string) { 18 | if ('string' !== typeof string) { 19 | throw new Error(LAME_ERROR); 20 | } 21 | return JSON.parse(string, functionCreate); 22 | } 23 | }; 24 | 25 | // JSON serializer helpers 26 | function functionStringify(key, value) { 27 | if ('function' === typeof(value)) { 28 | return value.toString(); 29 | } 30 | return value; 31 | } 32 | 33 | function functionCreate(key, value) { 34 | if (!key) { 35 | return value; 36 | } 37 | 38 | if (typeof value === 'string') { 39 | var funcRegExp = /function[^\(]*\(([^\)]*)\)[^\{]*{([^\}]*)\}/, 40 | match = value.match(funcRegExp); 41 | if (match) { 42 | var args = match[1] 43 | .split(',') 44 | .map(function (arg) { 45 | return arg.replace(/\s+/, ''); 46 | }); 47 | return new Function(args, match[2]); 48 | } 49 | } 50 | return value; 51 | } 52 | 53 | }; 54 | -------------------------------------------------------------------------------- /private/src/client/app/account/login/login.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('jsSparkUiApp') 4 | .controller('LoginCtrl', function ($scope, Auth, $location, $window) { 5 | 6 | $scope.user = {password: '', email: ''}; 7 | $scope.errors = {}; 8 | $scope.cannotSubbmit = !$scope.user.password || !scope.user.email; 9 | 10 | $scope.login = function (form) { 11 | $scope.submitted = true; 12 | 13 | if (!form.$valid) { 14 | return; 15 | } 16 | 17 | Auth 18 | .login({ 19 | email: $scope.user.email, 20 | password: $scope.user.password 21 | }) 22 | .then(function () { 23 | // Logged in, redirect to Search 24 | $location.path('/search/'); 25 | }) 26 | .catch(function (err) { 27 | $scope.errors.other = err.message; 28 | }); 29 | }; 30 | 31 | $scope.loginOauth = function (provider) { 32 | $window.location.href = '/auth/' + provider; 33 | }; 34 | 35 | // fix on change of user name if user had stored password then will auto fill password 36 | $scope.validateModel = function () { 37 | setTimeout(function () { 38 | $scope.$apply(function () { 39 | $scope.user.password = $('input[type=password]').val(); 40 | $scope.cannotSubbmit = !$scope.user.password || !$scope.user.email; 41 | }); 42 | }, 200); 43 | }; 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /private/src/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 mongoose = require('mongoose'); 12 | var config = require('./config/environment'); 13 | 14 | // Connect to database 15 | mongoose.connect(config.mongo.uri, config.mongo.options); 16 | 17 | // Populate DB with sample data 18 | if (config.seedDB) { 19 | require('./config/seedDb'); 20 | } 21 | 22 | // setup Dependencies 23 | var ROOT = './'; 24 | var di = require(ROOT + 'controller/di')( 25 | require(ROOT + 'config/di').services 26 | ); 27 | 28 | // Setup server 29 | var app = di.get('app'); 30 | var server = di.get('server'); 31 | var socketio = di.get('io.server'); 32 | require('./config/express')(app); 33 | require('./routes')(app, di); 34 | 35 | // Start server 36 | server.listen(config.port, config.ip, function () { 37 | console.log('Express server listening on %d, in %s mode', config.port, app.get('env')); 38 | }); 39 | 40 | // Expose app 41 | exports = module.exports = app; 42 | 43 | var _ = di.get('_'); 44 | var jsSpark = di.get('service.jsSpark'); 45 | 46 | setInterval( 47 | function delayedTask() { 48 | console.log('Sending task to calculate'); 49 | jsSpark(_.range(1000)) 50 | .filter(function isOdd(num) { 51 | return num % 2; 52 | }) 53 | .reduce(function sumUp(sum, num) { 54 | return sum + num; 55 | }) 56 | .run() 57 | .then(function (data) { 58 | console.log('Total sum of 1 to 1000 odd numbers is:', data); 59 | }); 60 | }, 5000 61 | ); 62 | 63 | 64 | -------------------------------------------------------------------------------- /private/src/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 | user.authenticate('password').should.be.true; 55 | }); 56 | 57 | it("should not authenticate user if password is invalid", function () { 58 | user.authenticate('blah').should.not.be.true; 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /private/src/client/app/account/settings/settings.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |

Change Password

7 |
8 |
9 |
10 | 11 |
12 | 13 | 14 | 17 | 18 |

19 | {{ errors.other }} 20 |

21 |
22 | 23 |
24 | 25 | 26 | 30 | 31 |

33 | Password must be at least 3 characters. 34 |

35 |
36 | 37 |

{{ message }}

38 | 39 | 40 |
41 |
42 |
43 |
44 | -------------------------------------------------------------------------------- /private/src/client/components/navbar/navbar.html: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /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 | 'private/src/test/**/*.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 | -------------------------------------------------------------------------------- /private/src/client/app/main/main.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 14 | 15 |
16 |
17 |
18 |

Features:

19 | 26 |
27 |
28 | 29 |
30 | 31 | 32 |

33 | 35 | 36 | 38 | 39 |

40 |
41 |
42 | 43 | 50 | -------------------------------------------------------------------------------- /private/src/server/config/seedDb.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Populate DB with sample data on server start 3 | * to disable, edit config/environment/index.js, and set `seedDB: false` 4 | */ 5 | 6 | 'use strict'; 7 | 8 | var Thing = require('../api/thing/thing.model'); 9 | var User = require('../api/user/user.model'); 10 | 11 | Thing.find({}).remove(function () { 12 | Thing.create({ 13 | name: 'Development Tools', 14 | info: 'Integration with popular tools such as Bower, Grunt, Karma, Mocha, JSHint, Node Inspector, Livereload, Protractor, Jade, Stylus, Sass, CoffeeScript, and Less.' 15 | }, { 16 | name: 'Server and Client integration', 17 | info: 'Built with a powerful and fun stack: MongoDB, Express, AngularJS, and Node.' 18 | }, { 19 | name: 'Smart Build System', 20 | info: 'Build system ignores `spec` files, allowing you to keep tests alongside code. Automatic injection of scripts and styles into your index.html' 21 | }, { 22 | name: 'Modular Structure', 23 | info: 'Best practice client and server structures allow for more code reusability and maximum scalability' 24 | }, { 25 | name: 'Optimized Build', 26 | info: 'Build process packs up your templates as a single JavaScript payload, minifies your scripts/css/images, and rewrites asset names for caching.' 27 | }, { 28 | name: 'Deployment Ready', 29 | info: 'Easily deploy your app to Heroku or Openshift with the heroku and openshift subgenerators' 30 | }); 31 | }); 32 | 33 | User.find({}).remove(function () { 34 | User.create({ 35 | provider: 'local', 36 | name: 'Test User', 37 | email: 'test@test.com', 38 | password: 'test' 39 | }, { 40 | provider: 'local', 41 | role: 'admin', 42 | name: 'Admin', 43 | email: 'admin@admin.com', 44 | password: 'admin' 45 | }, function () { 46 | console.log('finished populating users'); 47 | } 48 | ); 49 | }); 50 | -------------------------------------------------------------------------------- /private/src/client/app/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var jSSparkUi = angular.module('jsSparkUiApp', [ 4 | 'ngCookies', 5 | 'ngResource', 6 | 'ngSanitize', 7 | 'btford.socket-io', 8 | 'ui.router', 9 | 'ui.bootstrap', 10 | 'jQuery', 11 | '_', 12 | 'config' 13 | ]) 14 | .config(function ($stateProvider, $urlRouterProvider, $locationProvider, $httpProvider) { 15 | $urlRouterProvider 16 | .otherwise('/'); 17 | 18 | $locationProvider.html5Mode(true); 19 | $httpProvider.interceptors.push('authInterceptor'); 20 | }) 21 | 22 | .factory('authInterceptor', function ($rootScope, $q, $cookieStore, $location) { 23 | return { 24 | // Add authorization token to headers 25 | request: function (config) { 26 | config.headers = config.headers || {}; 27 | if ($cookieStore.get('token')) { 28 | config.headers.Authorization = 'Bearer ' + $cookieStore.get('token'); 29 | } 30 | return config; 31 | }, 32 | 33 | // Intercept 401s and redirect you to login 34 | responseError: function (response) { 35 | if (response.status === 401) { 36 | $location.path('/login'); 37 | // remove any stale tokens 38 | $cookieStore.remove('token'); 39 | return $q.reject(response); 40 | } 41 | else { 42 | return $q.reject(response); 43 | } 44 | } 45 | }; 46 | }) 47 | 48 | .run(function ($rootScope, $location, Auth) { 49 | // Redirect to login if route requires auth and you're not logged in 50 | $rootScope.$on('$stateChangeStart', function (event, next) { 51 | Auth.isLoggedInAsync(function (loggedIn) { 52 | if (next.authenticate && !loggedIn) { 53 | $location.path('/login'); 54 | } 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /private/src/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 | ip: 'localhost', 25 | 26 | // Should we populate the DB with sample data? 27 | seedDB: false, 28 | 29 | // Secret for session, you will want to change this and make it an environment variable 30 | secrets: { 31 | session: 'js-spark-ui-secret' 32 | }, 33 | 34 | // List of user roles 35 | userRoles: ['guest', 'user', 'admin'], 36 | 37 | // MongoDB connection options 38 | mongo: { 39 | options: { 40 | db: { 41 | safe: true 42 | } 43 | } 44 | }, 45 | 46 | facebook: { 47 | clientID: process.env.FACEBOOK_ID || 'id', 48 | clientSecret: process.env.FACEBOOK_SECRET || 'secret', 49 | callbackURL: process.env.DOMAIN + '/auth/facebook/callback' 50 | }, 51 | 52 | twitter: { 53 | clientID: process.env.TWITTER_ID || 'id', 54 | clientSecret: process.env.TWITTER_SECRET || 'secret', 55 | callbackURL: process.env.DOMAIN + '/auth/twitter/callback' 56 | }, 57 | 58 | google: { 59 | clientID: process.env.GOOGLE_ID || 'id', 60 | clientSecret: process.env.GOOGLE_SECRET || 'secret', 61 | callbackURL: process.env.DOMAIN + '/auth/google/callback' 62 | } 63 | }; 64 | 65 | // Export the config object based on the NODE_ENV 66 | // ============================================== 67 | module.exports = _.merge( 68 | all, 69 | require('./' + process.env.NODE_ENV + '.js') || {}); 70 | -------------------------------------------------------------------------------- /private/src/server/config/express.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Express configuration 3 | */ 4 | 5 | 'use strict'; 6 | 7 | var express = require('express'); 8 | var favicon = require('static-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 | var passport = require('passport'); 18 | var session = require('express-session'); 19 | var mongoStore = require('connect-mongo')(session); 20 | var mongoose = require('mongoose'); 21 | 22 | module.exports = function (app) { 23 | var env = app.get('env'); 24 | 25 | app.set('views', config.root + '/server/views'); 26 | app.engine('html', require('ejs').renderFile); 27 | app.set('view engine', 'html'); 28 | app.use(compression()); 29 | app.use(bodyParser.urlencoded({ extended: false })); 30 | app.use(bodyParser.json()); 31 | app.use(methodOverride()); 32 | app.use(cookieParser()); 33 | app.use(passport.initialize()); 34 | 35 | // Persist sessions with mongoStore 36 | // We need to enable sessions for passport twitter because its an oauth 1.0 strategy 37 | app.use(session({ 38 | secret: config.secrets.session, 39 | resave: true, 40 | saveUninitialized: true, 41 | store: new mongoStore({ mongoose_connection: mongoose.connection }) 42 | })); 43 | 44 | if ('production' === env) { 45 | app.use(favicon(path.join(config.root, 'public', 'favicon.ico'))); 46 | app.use(express.static(path.join(config.root, 'public'))); 47 | app.set('appPath', config.root + '/public'); 48 | app.use(morgan('dev')); 49 | } 50 | 51 | 52 | if ('development' === env || 'test' === env) { 53 | app.use(require('connect-livereload')()); 54 | app.use(express.static(path.join(config.root, '.tmp'))); 55 | app.use(express.static(path.join(config.root, 'client'))); 56 | app.set('appPath', 'private/src/client'); 57 | app.use(morgan('dev')); 58 | app.use(errorHandler()); // Error handler - has to be last 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /private/src/server/controller/di.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 4/16/2014. 3 | */ 4 | module.exports = function diController(initialServices) { 5 | 'use strict'; 6 | 7 | var SERVICE_INVALID = 'Invalid service'; 8 | var SERVICE_DOES_NOT_EXISTS = 'Service with this name does not exist'; 9 | var SERVICE_WAS_EXISTING = 'Cannot add service that exists'; 10 | var services = initialServices || {}; 11 | 12 | return { 13 | 14 | add: function (serviceName, service) { 15 | if (!service || !serviceName) { 16 | throw new Error(SERVICE_INVALID); 17 | } 18 | if (this.hasService(serviceName)) { 19 | throw new Error(SERVICE_WAS_EXISTING + ': ' + serviceName); 20 | } 21 | services[serviceName] = service; 22 | }, 23 | 24 | /** 25 | * return service , if service is curred returns auto-curred/resolved version 26 | * @param serviceName 27 | * @returns {*} 28 | */ 29 | get: function (serviceName) { 30 | if (!this.hasService(serviceName)) { 31 | throw new Error(SERVICE_DOES_NOT_EXISTS + ': ' + serviceName); 32 | } 33 | // auto-curry anonymous addService function 34 | // not sure if Function.name will be legal in next ECMA script 35 | if (('function' === typeof services[serviceName]) && 36 | ('addService' === services[serviceName].name)) { 37 | //console.log('function ', services[serviceName].name, serviceName); 38 | services[serviceName] = services[serviceName](this); 39 | } 40 | return services[serviceName]; 41 | }, 42 | 43 | /** use full for testing */ 44 | replace: function (serviceName, service) { 45 | if (!this.hasService(serviceName)) { 46 | throw new Error(SERVICE_DOES_NOT_EXISTS + ': ' + serviceName); 47 | } 48 | delete services[serviceName]; 49 | this.add(serviceName, service); 50 | }, 51 | 52 | hasService: function (serviceName) { 53 | return services.hasOwnProperty(serviceName); 54 | }, 55 | 56 | //TODO merge services between modules also might merge initialServices 57 | mergeServices: function () {} 58 | }; 59 | }; 60 | -------------------------------------------------------------------------------- /private/docs/readme.md: -------------------------------------------------------------------------------- 1 | internal docs like design docs 2 | + markups for auto generated user docs 3 | 4 | rules 5 | ===== 6 | 7 | codding style 8 | ============= 9 | tab => 4 spaces 10 | 11 | module + DI + config 12 | 13 | DI 14 | module pattern: 15 | 16 | file `strategy.js` in `clients/push/strategy` 17 | ```JavaScript 18 | 19 | module.exports = function strategyPushClients(arraySerializerService, clientQueService, when) { 20 | 21 | //private 22 | var array = []; 23 | 24 | return { 25 | 26 | // this method comment 27 | /** is also allowed */ 28 | method1: function(param) { 29 | // code 30 | }, 31 | 32 | // some comments 33 | push: publicFunction 34 | }; 35 | 36 | // public methods 37 | function publicFunction(params) { 38 | 39 | } 40 | 41 | // private methods 42 | function privateFunction(params) { 43 | 44 | } 45 | }; 46 | ``` 47 | 48 | usage 49 | ===== 50 | 51 | ```JavaScript 52 | 53 | var clientsPushStrategy = sm.get('model.clients.push.strategy'); 54 | 55 | clientsPushStrategy.push(tasks) 56 | .then(function (clients) { 57 | // 58 | }); 59 | ``` 60 | 61 | instantiate 62 | ----------- 63 | 64 | ```JavaScript 65 | 66 | //... 67 | 'model.strategy.push.clients': function addService(sm) { 68 | var model = require(ROOT + 'clients/push/strategy') ( 69 | sm.get('service.serializer.array'), 70 | sm.get('service.que.client') 71 | sm.get('when') 72 | ); 73 | return model; 74 | } 75 | ``` 76 | 77 | that means we try NOT to call `new` outside object/service registry 78 | that will remove usage of singletons, also will allow easier testing 79 | and portability 80 | 81 | naming convention 82 | ----------------- 83 | file `private/src/server/service/serializer/function` 84 | module name: 85 | camel case: `functionSerializerService` 86 | pascal case: `FunctionSerializerService` (if to be called with `new`) 87 | 88 | unit test file: `private/test/server/service/serializer/function` 89 | module name: `functionSerializerServiceTest` 90 | 91 | 92 | bower components are in /src/client/component 93 | 94 | why `/node_modules`? 95 | -------------------- 96 | short answer: because npm is limited. 97 | 98 | promises: 99 | --------- 100 | https://github.com/cujojs/when/wiki/Examples 101 | -------------------------------------------------------------------------------- /private/src/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 | var Thing = require('./thing.model'); 14 | 15 | // Get list of things 16 | exports.index = function (req, res) { 17 | Thing.find(function (err, things) { 18 | if (err) { 19 | return handleError(res, err); 20 | } 21 | return res.status(200).json(things); 22 | }); 23 | }; 24 | 25 | // Get a single thing 26 | exports.show = function (req, res) { 27 | Thing.findById(req.params.id, function (err, thing) { 28 | if (err) { 29 | return handleError(res, err); 30 | } 31 | if (!thing) { 32 | return res.status(404).end(); 33 | } 34 | return res.json(thing); 35 | }); 36 | }; 37 | 38 | // Creates a new thing in the DB. 39 | exports.create = function (req, res) { 40 | Thing.create(req.body, function (err, thing) { 41 | if (err) { 42 | return handleError(res, err); 43 | } 44 | return res.status(201).json(thing); 45 | }); 46 | }; 47 | 48 | // Updates an existing thing in the DB. 49 | exports.update = function (req, res) { 50 | if (req.body._id) { 51 | delete req.body._id; 52 | } 53 | Thing.findById(req.params.id, function (err, thing) { 54 | if (err) { 55 | return handleError(res, err); 56 | } 57 | if (!thing) { 58 | return res.status(404).end(); 59 | } 60 | var updated = _.merge(thing, req.body); 61 | updated.save(function (err) { 62 | if (err) { 63 | return handleError(res, err); 64 | } 65 | return res.status(200).json(thing); 66 | }); 67 | }); 68 | }; 69 | 70 | // Deletes a thing from the DB. 71 | exports.destroy = function (req, res) { 72 | Thing.findById(req.params.id, function (err, thing) { 73 | if (err) { 74 | return handleError(res, err); 75 | } 76 | if (!thing) { 77 | return res.status(404).end(); 78 | } 79 | thing.remove(function (err) { 80 | if (err) { 81 | return handleError(res, err); 82 | } 83 | return res.status(204).end(); 84 | }); 85 | }); 86 | }; 87 | 88 | function handleError(res, err) { 89 | return res.status(500).send(err); 90 | } 91 | -------------------------------------------------------------------------------- /private/src/server/auth/auth.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var passport = require('passport'); 5 | var config = require('../config/environment'); 6 | var jwt = require('jsonwebtoken'); 7 | var expressJwt = require('express-jwt'); 8 | var compose = require('composable-middleware'); 9 | var User = require('../api/user/user.model'); 10 | var validateJwt = expressJwt({ secret: config.secrets.session }); 11 | 12 | /** 13 | * Attaches the user object to the request if authenticated 14 | * Otherwise returns 403 15 | */ 16 | function isAuthenticated() { 17 | return compose() 18 | // Validate jwt 19 | .use(function (req, res, next) { 20 | // allow access_token to be passed through query parameter as well 21 | if (req.query && req.query.hasOwnProperty('access_token')) { 22 | req.headers.authorization = 'Bearer ' + req.query.access_token; 23 | } 24 | validateJwt(req, res, next); 25 | }) 26 | // Attach user to request 27 | .use(function (req, res, next) { 28 | User.findById(req.user._id, function (err, user) { 29 | if (err) return next(err); 30 | if (!user) return res.status(401).end(); 31 | 32 | req.user = user; 33 | next(); 34 | }); 35 | }); 36 | } 37 | 38 | /** 39 | * Checks if the user role meets the minimum requirements of the route 40 | */ 41 | function hasRole(roleRequired) { 42 | if (!roleRequired) throw new Error('Required role needs to be set'); 43 | 44 | return compose() 45 | .use(isAuthenticated()) 46 | .use(function meetsRequirements(req, res, next) { 47 | if (config.userRoles.indexOf(req.user.role) >= config.userRoles.indexOf(roleRequired)) { 48 | next(); 49 | } 50 | else { 51 | res.send(403); 52 | } 53 | }); 54 | } 55 | 56 | /** 57 | * Returns a jwt token signed by the app secret 58 | */ 59 | function signToken(id) { 60 | return jwt.sign({ _id: id }, config.secrets.session, { expiresInMinutes: 60 * 5 }); 61 | } 62 | 63 | /** 64 | * Set token cookie directly for oAuth strategies 65 | */ 66 | function setTokenCookie(req, res) { 67 | if (!req.user) return res.status(404).json({ message: 'Something went wrong, please try again.'}); 68 | var token = signToken(req.user._id, req.user.role); 69 | res.cookie('token', JSON.stringify(token)); 70 | res.redirect('/'); 71 | } 72 | 73 | exports.isAuthenticated = isAuthenticated; 74 | exports.hasRole = hasRole; 75 | exports.signToken = signToken; 76 | exports.setTokenCookie = setTokenCookie; 77 | -------------------------------------------------------------------------------- /private/src/server/service/taskManager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TODO 3 | */ 4 | module.exports = function taskManagerService(config, log, dispatcher, workersService, defer, promise, server, port, _) { 5 | 6 | return { 7 | init: init, 8 | addTask: addTask, 9 | getWorkers: workersService.get, 10 | stop: stop 11 | }; 12 | 13 | function stop() { 14 | var stopDate = new Date(); 15 | server.close(); 16 | log.info('server stopped @ ' + stopDate); 17 | 18 | //dispatcher.stop(); // is closed by server 19 | return stopDate; 20 | } 21 | 22 | // TODO start server(if not started) 23 | // public methods 24 | function init() { 25 | dispatcher.start(); 26 | server.listen(port); 27 | } 28 | 29 | function addTask(task, taskConfig) { 30 | taskConfig = taskConfig || ''; 31 | var deferred = defer(), 32 | todos = []; 33 | 34 | dispatcher.addTask(task, deferred); 35 | 36 | if (!taskConfig.times) { 37 | return deferred.promise; 38 | } 39 | 40 | todos.push(deferred.promise); 41 | 42 | _.times(taskConfig.times - 1, function (n) { 43 | deferred = defer(); 44 | dispatcher.addTask(task, deferred); 45 | todos.push(deferred.promise); 46 | }); 47 | 48 | return promise 49 | .settle(todos) 50 | .then(filterFullFilled) 51 | .then(checkMajority); 52 | } 53 | 54 | function filterFullFilled(promises) { 55 | return promises 56 | .filter(function (promise) { 57 | return promise.isFulfilled(); // can be also isRejected() 58 | }) 59 | .map(function (promise) { 60 | return promise.value(); 61 | }); 62 | } 63 | 64 | function first2MostCommon(arr) { 65 | return _(arr) 66 | .groupBy() 67 | .sortBy(function (el) { 68 | return -el.length; 69 | }) 70 | .map(function firstTwo(data) { 71 | return [data[0], data[1]]; 72 | }) 73 | .value(); 74 | } 75 | 76 | function compare2Answers(data) { 77 | if (_.isEqual(data[0], data[1])) { 78 | return data[0]; 79 | } else { 80 | throw new Error('Clients calculated different things, ' + 81 | 'some may even could not finish calculations'); 82 | } 83 | } 84 | 85 | // maybe would be better to send calculations to arbiter 86 | //:: array[data] -> array 87 | function checkMajority(data) { 88 | var requiredOK = parseInt(data.length / 2 + 1); 89 | if (2 === requiredOK) { 90 | return compare2Answers(data); 91 | } 92 | return first2MostCommon(data); 93 | } 94 | }; 95 | -------------------------------------------------------------------------------- /private/src/server/service/express.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 4/16/2014. 3 | */ 4 | module.exports = function expressService(app, port, cookieParser, compression, http/*, validator*/, session, static_) { 5 | 'use strict'; 6 | 7 | var expressApp; 8 | var server; 9 | // var isValidMethod = validator.isValidMethod; 10 | 11 | return { 12 | 13 | getApp: function () { 14 | return expressApp; 15 | }, 16 | 17 | getServer: function () { 18 | return server; 19 | }, 20 | 21 | registerRouteHandler: function (route, handler, methodArr) { 22 | var methods = methodArr || ['GET']; 23 | 24 | if (!handler) { 25 | throw new Error('Handler invalid'); 26 | } 27 | methods.forEach(function (method) { 28 | // if (!isValidMethod(method)) { 29 | // throw new Error('Method ' + method + ' disallowed!'); 30 | // } 31 | console.log('Registered ', method, ' route', route, 32 | 'handler with context: ', typeof handler 33 | ); 34 | if (method === 'GET') { 35 | expressApp.get(route, handler); 36 | } 37 | if (method === 'POST') { 38 | expressApp.post(route, handler); 39 | } 40 | }); 41 | }, 42 | 43 | setup: function () { 44 | app.use(cookieParser); 45 | app.use(compression); 46 | app.use(session); 47 | app.use('/static', static_); 48 | 49 | server = http 50 | .createServer(app) 51 | .listen(port); 52 | console.log('Listening on https://0.0.0.0:' + port); 53 | 54 | expressApp = app; 55 | }, 56 | 57 | registerErrors: function () { 58 | function NotFound(msg) { 59 | this.name = 'NotFound'; 60 | Error.call(this, msg); 61 | Error.captureStackTrace(this, arguments.callee); 62 | } 63 | 64 | expressApp.use(function (err, req, res, next) { 65 | console.error(err); 66 | if (err instanceof NotFound) { 67 | res.render('404.jade', { locals: { 68 | title: '404 - Not Found', 69 | description: '', 70 | author: '', 71 | analyticssiteid: 'XXXXXXX' 72 | }, status: 404 }); 73 | } else { 74 | res.render('500.jade', { locals: { 75 | title: 'The Server Encountered an Error', 76 | description: '', 77 | author: '', 78 | analyticssiteid: 'XXXXXXX', 79 | error: err 80 | }, status: 500 }); 81 | } 82 | }); 83 | } 84 | }; 85 | }; 86 | -------------------------------------------------------------------------------- /private/src/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.status(500).send(err); 19 | res.status(200).json(users); 20 | }); 21 | }; 22 | 23 | /** 24 | * Creates a new user 25 | */ 26 | exports.create = function (req, res, next) { 27 | var newUser = new User(req.body); 28 | newUser.provider = 'local'; 29 | newUser.role = 'user'; 30 | newUser.save(function (err, user) { 31 | if (err) return validationError(res, err); 32 | var token = jwt.sign({_id: user._id }, config.secrets.session, { expiresInMinutes: 60 * 5 }); 33 | res.json({ token: token }); 34 | }); 35 | }; 36 | 37 | /** 38 | * Get a single user 39 | */ 40 | exports.show = function (req, res, next) { 41 | var userId = req.params.id; 42 | 43 | User.findById(userId, function (err, user) { 44 | if (err) return next(err); 45 | if (!user) return res.send(401); 46 | res.json(user.profile); 47 | }); 48 | }; 49 | 50 | /** 51 | * Deletes a user 52 | * restriction: 'admin' 53 | */ 54 | exports.destroy = function (req, res) { 55 | User.findByIdAndRemove(req.params.id, function (err, user) { 56 | if (err) return res.status(500).send(err); 57 | return res.status(204).end(); 58 | }); 59 | }; 60 | 61 | /** 62 | * Change a users password 63 | */ 64 | exports.changePassword = function (req, res, next) { 65 | var userId = req.user._id; 66 | var oldPass = String(req.body.oldPassword); 67 | var newPass = String(req.body.newPassword); 68 | 69 | User.findById(userId, function (err, user) { 70 | if (user.authenticate(oldPass)) { 71 | user.password = newPass; 72 | user.save(function (err) { 73 | if (err) return validationError(res, err); 74 | res.status(200).end(); 75 | }); 76 | } else { 77 | res.status(403).end(); 78 | } 79 | }); 80 | }; 81 | 82 | /** 83 | * Get my info 84 | */ 85 | exports.me = function (req, res, next) { 86 | var userId = req.user._id; 87 | User.findOne({ 88 | _id: userId 89 | }, '-salt -hashedPassword', function (err, user) { // don't ever give out the password or salt 90 | if (err) return next(err); 91 | if (!user) return res.json(401); 92 | res.json(user); 93 | }); 94 | }; 95 | 96 | /** 97 | * Authentication callback 98 | */ 99 | exports.authCallback = function (req, res, next) { 100 | res.redirect('/'); 101 | }; 102 | -------------------------------------------------------------------------------- /private/src/client/app/account/login/login.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |

Login

7 | 8 |

Accounts are reset on server restart from 9 | server/config/seed.js. Default account is 10 | test@test.com / test 11 |

12 | 13 |

Admin account is admin@admin.com / admin

14 |
15 |
16 | 17 |
18 | 19 |
20 | 21 | 22 | 24 |
25 | 26 |
27 | 28 | 29 | 31 |
32 | 33 |
34 |

36 | Please enter your email and password. 37 |

38 | 39 |

40 | Please enter a valid email. 41 |

42 | 43 |

{{ errors.other }}

44 |
45 | 46 |
47 | 50 | 51 | Register 52 | 53 |
54 | 55 |
56 | 68 |
69 |
70 |
71 |
72 |
73 | -------------------------------------------------------------------------------- /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 | 'private/src/client/components/jquery/dist/jquery.js', 15 | 'private/src/client/components/angular/angular.js', 16 | 'private/src/client/components/angular-mocks/angular-mocks.js', 17 | 'private/src/client/components/angular-resource/angular-resource.js', 18 | 'private/src/client/components/angular-cookies/angular-cookies.js', 19 | 'private/src/client/components/angular-sanitize/angular-sanitize.js', 20 | 'private/src/client/components/angular-route/angular-route.js', 21 | 'private/src/client/components/angular-bootstrap/ui-bootstrap-tpls.js', 22 | 'private/src/client/components/lodash/dist/lodash.compat.js', 23 | 'private/src/client/components/angular-socket-io/socket.js', 24 | 'private/src/client/components/angular-ui-router/release/angular-ui-router.js', 25 | 'private/src/client/app/app.js', 26 | 'private/src/client/app/app.coffee', 27 | 'private/src/client/app/**/*.js', 28 | 'private/src/client/app/**/*.coffee', 29 | 'private/src/client/components/**/*.js', 30 | 'private/src/client/components/**/*.coffee', 31 | 'private/src/client/app/**/*.jade', 32 | 'private/src/client/components/**/*.jade', 33 | 'private/src/client/app/**/*.html', 34 | 'private/src/client/components/**/*.html' 35 | ], 36 | 37 | preprocessors: { 38 | '**/*.jade': 'ng-jade2js', 39 | '**/*.html': 'html2js', 40 | '**/*.coffee': 'coffee', 41 | }, 42 | 43 | ngHtml2JsPreprocessor: { 44 | stripPrefix: 'private/src/client/' 45 | }, 46 | 47 | ngJade2JsPreprocessor: { 48 | stripPrefix: 'private/src/client/' 49 | }, 50 | 51 | // list of files / patterns to exclude 52 | exclude: [], 53 | 54 | // web server port 55 | port: 8080, 56 | 57 | // level of logging 58 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 59 | logLevel: config.LOG_INFO, 60 | 61 | 62 | // enable / disable watching file and executing tests whenever any file changes 63 | autoWatch: false, 64 | 65 | 66 | // Start these browsers, currently available: 67 | // - Chrome 68 | // - ChromeCanary 69 | // - Firefox 70 | // - Opera 71 | // - Safari (only Mac) 72 | // - PhantomJS 73 | // - IE (only Windows) 74 | browsers: ['PhantomJS'], 75 | 76 | 77 | // Continuous Integration mode 78 | // if true, it capture browsers, run tests and exit 79 | singleRun: false 80 | }); 81 | }; 82 | -------------------------------------------------------------------------------- /private/src/server/service/jsSpark.js: -------------------------------------------------------------------------------- 1 | module.exports = function jsSParkService(taskManager, _) { 2 | 3 | // is a monad 4 | // TODO use newChainedMethod not to double code and change run() 5 | return function jsSpark(data) { 6 | 7 | var operations = [], 8 | array = data; 9 | 10 | return { 11 | 12 | // TODO move dynamic method dispatch to apply/call 13 | // array generics are still not available in node 14 | // add lodash function 15 | add: function (/*args*/) { 16 | operations.push({ 17 | chaining: function (chain, functions, _) { 18 | return chain[functions[0]](functions[1]); 19 | }, 20 | callback: Array.prototype.slice.call(arguments) 21 | }); 22 | 23 | return this; 24 | }, 25 | 26 | map: function (callback) { 27 | operations.push({ 28 | chaining: function (chain, callback) { 29 | return chain.map(callback); 30 | }, 31 | callback: callback 32 | }); 33 | 34 | return this; 35 | }, 36 | 37 | filter: function (callback) { 38 | operations.push({ 39 | chaining: function (chain, callback) { 40 | return chain.filter(callback); 41 | }, 42 | callback: callback 43 | }); 44 | 45 | return this; 46 | }, 47 | 48 | reduce: function (callback) { 49 | operations.push({ 50 | chaining: function (chain, callback) { 51 | return chain.reduce(callback); 52 | }, 53 | callback: callback 54 | }); 55 | 56 | return this; 57 | }, 58 | 59 | thru: function (callback) { 60 | operations.push({ 61 | chaining: function (chain, callback) { 62 | return chain.thru(callback); 63 | }, 64 | callback: callback 65 | }); 66 | 67 | return this; 68 | }, 69 | 70 | run: run, 71 | 72 | stop: taskManager.stop 73 | }; 74 | 75 | // TODO move forEach to reduce? 76 | // TODO args = options{timeout,...} 77 | // factory method 78 | // :: object -> deferred 79 | function run(taskConfig) { 80 | var task = { 81 | 82 | operations: operations, 83 | 84 | execute: function (_, data) { 85 | var chain = _.chain(data); 86 | this.operations.forEach(function (operation, i) { 87 | chain = operation.chaining(chain, operation.callback.bind(_), _); 88 | }); 89 | 90 | return chain; 91 | }, 92 | 93 | data: array 94 | }; 95 | 96 | return taskManager.addTask(task, taskConfig); 97 | } 98 | } 99 | }; 100 | -------------------------------------------------------------------------------- /private/src/client/app/account/signup/signup.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |

Sign up

7 |
8 |
9 |
10 | 11 |
13 | 14 | 15 | 17 |

18 | A name is required 19 |

20 |
21 | 22 |
24 | 25 | 26 | 29 |

30 | Doesn't look like a valid email. 31 |

32 |

33 | What's your email address? 34 |

35 |

36 | {{ errors.email }} 37 |

38 |
39 | 40 |
42 | 43 | 44 | 48 |

50 | Password must be at least 3 characters. 51 |

52 |

53 | {{ errors.password }} 54 |

55 |
56 | 57 |
58 | 61 | 62 | Login 63 | 64 |
65 | 66 |
67 | 78 |
79 |
80 |
81 |
82 |
83 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 7/24/2014. 3 | */ 4 | 'use strict'; 5 | 6 | var jsSpark, 7 | taskManager, 8 | _; 9 | 10 | var ROOT = './private/src/server/'; 11 | // DI container 12 | var services = require(ROOT + 'config/di').services; 13 | 14 | // setup Dependencies 15 | var di = require(ROOT + 'controller/di')(services); 16 | 17 | // start listening on given port 18 | //di.get('server').listen(di.get('port')); 19 | 20 | jsSpark = di.get('service.jsSpark'); 21 | 22 | // lodash 23 | _ = di.get('_'); 24 | 25 | taskManager = di.get('service.taskManager'); 26 | 27 | module.exports = function (config) { 28 | config = config || {}; 29 | 30 | if (config.workers) { 31 | di.get('service.fork').forkWorker({times: config.workers}); 32 | } 33 | 34 | return { 35 | di: di, 36 | jsSpark: jsSpark, 37 | taskManager: taskManager, 38 | q: di.get('promise') 39 | } 40 | }; 41 | 42 | // TODO move to examples 43 | /** 44 | module.exports({workers: 2}); 45 | 46 | var task, task2, task3, doElections; 47 | 48 | task = jsSpark(_.range(10)) 49 | // https://lodash.com/docs#sortBy 50 | .thru(function (arr) { 51 | return this.sortBy(arr, function _sortBy(el) { 52 | return Math.sin(el); 53 | }) 54 | }) 55 | .map(function multiplyBy2(el) { 56 | return el * 2; 57 | }) 58 | .filter(function remove5and10(el) { 59 | return el % 5 !== 0; 60 | }) 61 | // sum of [ 2, 4, 6, 8, 12, 14, 16, 18 ] => 80 62 | .reduce(function sumUp(arr, el) { 63 | return arr + el; 64 | }) 65 | .run(); 66 | 67 | // client side heavy CPU computation 68 | task2 = jsSpark([20]) 69 | .map(function addOne(num) { 70 | return num + 1; 71 | }) 72 | .run(); 73 | 74 | task3 = task 75 | .then(function serverSideComputingOfData(data) { 76 | var basesNumber = data + 21; 77 | // All your 101 base are belong to us 78 | console.log('All your ' + basesNumber + ' base are belong to us'); 79 | return basesNumber; 80 | }) 81 | .catch(function (reason) { 82 | console.log('Task could not compute ' + reason.toString()); 83 | }); 84 | 85 | di.get('promise') 86 | .all([task, task2, task3]) 87 | .then(function (data) { 88 | console.log('Tasks 1 to 3 done', data); 89 | }); 90 | 91 | doElections = jsSpark(_.range(10)) 92 | .reduce(function sumUp(sum, num) { 93 | return sum + num; 94 | }) 95 | // how many times repeat calculations 96 | .run({times: 3}) 97 | .then(function whenClientsFinished(data) { 98 | // may also get 2 most relevant answers 99 | console.log('Most clients believe that:'); 100 | console.log('Total sum of numbers from 1 to 10 is:', data); 101 | }) 102 | .catch(function whenClientsArgue(reason) { 103 | console.log('Most clients could not agree, ', +reason.toString()); 104 | }); 105 | 106 | setTimeout( 107 | function delayedTask() { 108 | jsSpark(_.range(1000)) 109 | .filter(function isOdd(num) { 110 | return num % 2; 111 | }) 112 | .reduce(function sumUp(sum, num) { 113 | return sum + num; 114 | }) 115 | .thru(function plusOne(sum) { 116 | return sum + 1; 117 | }) 118 | .run() 119 | .then(function (data) { 120 | console.log('Total sum of 1 to 1000 odd numbers +1 is:', data); 121 | }) 122 | .catch(function (reason) { 123 | console.log('Task could not compute ' + reason.toString()); 124 | }); 125 | }, 5000 126 | ); 127 | */ 128 | -------------------------------------------------------------------------------- /private/src/server/service/workers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 7/24/2014. 3 | * workersSocket - utilized for observing changes in workers 4 | */ 5 | module.exports = function workersService(log, _) { 6 | 'use strict'; 7 | 8 | var workers = []; 9 | var listeners = []; 10 | 11 | return { 12 | create: create, 13 | getFreeWorkers: getFreeWorkers, 14 | remove: remove, 15 | get: get, 16 | getFirstFree: getFirstFree, 17 | busy: busy, 18 | free: free, 19 | addListener: addListener 20 | }; 21 | 22 | //TODO notify change 23 | function create(socket) { 24 | var worker = { 25 | socket: socket, 26 | free: true, 27 | points: 0, 28 | _id: socket.id 29 | }; 30 | workers.push(worker); 31 | notifyThat('client:save', worker); 32 | 33 | return worker; 34 | } 35 | 36 | function remove(worker) { 37 | var index = workers.indexOf(worker); 38 | if (index != -1) { 39 | log.info('RIP client', workers[index].socket.id); 40 | workers.splice(index, 1); 41 | } 42 | notifyThat('client:remove', worker); 43 | } 44 | 45 | // { socket: 46 | // { nsp: [Object], 47 | // server: [Object], 48 | // adapter: [Object], 49 | // id: 'haQJ-zLwkTFof2RVAAAB', 50 | // client: [Object], 51 | // conn: [Object], 52 | // rooms: [Object], 53 | // acks: {}, 54 | // connected: true, 55 | // disconnected: false, 56 | // handshake: [Object], 57 | // address: 'undefined:undefined', 58 | // connectedAt: Mon Oct 27 2014 21:37:46 GMT+0100 (W. Europe Standard Time), 59 | // _events: [Object] 60 | // }, 61 | // free: false, 62 | // points: 4, 63 | // id: 'haQJ-zLwkTFof2RVAAAB' }, 64 | function get() { 65 | return _(workers) 66 | .map(extractImportantWorkerInfo) 67 | .value(); 68 | } 69 | 70 | function extractImportantWorkerInfo(worker) { 71 | return { 72 | _id: worker._id, 73 | connectedAt: worker.socket.connectedAt, 74 | connected: worker.socket.connected, 75 | handshake: worker.socket.handshake, 76 | free: worker.free, 77 | rooms: worker.socket.rooms, 78 | points: worker.points, 79 | benchmark: worker.benchmark 80 | } 81 | } 82 | 83 | // + getFirstFree::array -> object 84 | function getFirstFree() { 85 | return _.findWhere(workers, { free: true }); 86 | } 87 | 88 | //TODO getBest 89 | function getFreeWorkers() { 90 | return workers.filter(function (worker) { 91 | return worker.free; 92 | }); 93 | } 94 | 95 | // maybe make a worker class 96 | // free worker from busy state 97 | function free(worker, points) { 98 | points = points || 0; 99 | worker.free = true; 100 | worker.points += points; 101 | notifyThat('client:save', worker); 102 | } 103 | 104 | function busy(worker) { 105 | worker.free = false; 106 | notifyThat('client:save', worker); 107 | } 108 | 109 | //TODO maybe room broadcast 110 | function notifyThat(action, actor) { 111 | actor = extractImportantWorkerInfo(actor); 112 | listeners.forEach(function(listener){ 113 | listener.emit(action, actor); 114 | }); 115 | } 116 | 117 | function addListener(socket) { 118 | listeners.push(socket); 119 | } 120 | 121 | }; 122 | -------------------------------------------------------------------------------- /private/src/server/api/user/user.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var Schema = mongoose.Schema; 5 | var crypto = require('crypto'); 6 | var authTypes = ['github', 'twitter', 'facebook', 'google']; 7 | 8 | var UserSchema = new Schema({ 9 | name: String, 10 | email: { type: String, lowercase: true }, 11 | role: { 12 | type: String, 13 | default: 'user' 14 | }, 15 | hashedPassword: String, 16 | provider: String, 17 | salt: String, 18 | facebook: {}, 19 | twitter: {}, 20 | google: {}, 21 | github: {} 22 | }); 23 | 24 | /** 25 | * Virtuals 26 | */ 27 | UserSchema 28 | .virtual('password') 29 | .set(function (password) { 30 | this._password = password; 31 | this.salt = this.makeSalt(); 32 | this.hashedPassword = this.encryptPassword(password); 33 | }) 34 | .get(function () { 35 | return this._password; 36 | }); 37 | 38 | // Public profile information 39 | UserSchema 40 | .virtual('profile') 41 | .get(function () { 42 | return { 43 | 'name': this.name, 44 | 'role': this.role 45 | }; 46 | }); 47 | 48 | // Non-sensitive info we'll be putting in the token 49 | UserSchema 50 | .virtual('token') 51 | .get(function () { 52 | return { 53 | '_id': this._id, 54 | 'role': this.role 55 | }; 56 | }); 57 | 58 | /** 59 | * Validations 60 | */ 61 | 62 | // Validate empty email 63 | UserSchema 64 | .path('email') 65 | .validate(function (email) { 66 | if (authTypes.indexOf(this.provider) !== -1) return true; 67 | return email.length; 68 | }, 'Email cannot be blank'); 69 | 70 | // Validate empty password 71 | UserSchema 72 | .path('hashedPassword') 73 | .validate(function (hashedPassword) { 74 | if (authTypes.indexOf(this.provider) !== -1) return true; 75 | return hashedPassword.length; 76 | }, 'Password cannot be blank'); 77 | 78 | // Validate email is not taken 79 | UserSchema 80 | .path('email') 81 | .validate(function (value, respond) { 82 | var self = this; 83 | this.constructor.findOne({email: value}, function (err, user) { 84 | if (err) throw err; 85 | if (user) { 86 | if (self.id === user.id) return respond(true); 87 | return respond(false); 88 | } 89 | respond(true); 90 | }); 91 | }, 'The specified email address is already in use.'); 92 | 93 | var validatePresenceOf = function (value) { 94 | return value && value.length; 95 | }; 96 | 97 | /** 98 | * Pre-save hook 99 | */ 100 | UserSchema 101 | .pre('save', function (next) { 102 | if (!this.isNew) return next(); 103 | 104 | if (!validatePresenceOf(this.hashedPassword) && authTypes.indexOf(this.provider) === -1) 105 | next(new Error('Invalid password')); 106 | else 107 | next(); 108 | }); 109 | 110 | /** 111 | * Methods 112 | */ 113 | UserSchema.methods = { 114 | /** 115 | * Authenticate - check if the passwords are the same 116 | * 117 | * @param {String} plainText 118 | * @return {Boolean} 119 | * @api public 120 | */ 121 | authenticate: function (plainText) { 122 | return this.encryptPassword(plainText) === this.hashedPassword; 123 | }, 124 | 125 | /** 126 | * Make salt 127 | * 128 | * @return {String} 129 | * @api public 130 | */ 131 | makeSalt: function () { 132 | return crypto.randomBytes(16).toString('base64'); 133 | }, 134 | 135 | /** 136 | * Encrypt password 137 | * 138 | * @param {String} password 139 | * @return {String} 140 | * @api public 141 | */ 142 | encryptPassword: function (password) { 143 | if (!password || !this.salt) return ''; 144 | var salt = new Buffer(this.salt, 'base64'); 145 | return crypto.pbkdf2Sync(password, salt, 10000, 64).toString('base64'); 146 | } 147 | }; 148 | 149 | module.exports = mongoose.model('User', UserSchema); 150 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-spark", 3 | "description": "Distributed calculation / data processing system. Run computation/jobs on 1000+ cores", 4 | "author": "syzer ", 5 | "version": "0.5.1", 6 | "dependencies": { 7 | "bluebird": "latest", 8 | "body-parser": "^1.5.0", 9 | "chai": "^1.9.1", 10 | "composable-middleware": "^0.3.0", 11 | "compression": "^1.0.9", 12 | "connect": "^1.8.5", 13 | "connect-mongo": "^0.4.1", 14 | "cookie-parser": "^1.3.2", 15 | "ejs": "~0.8.4", 16 | "errorhandler": "~1.0.0", 17 | "express": "^4.6.1", 18 | "express-jwt": "^0.4.0", 19 | "express-session": "^1.7.0", 20 | "jade": "0.20.0", 21 | "jsonwebtoken": "^1.1.2", 22 | "lodash": "^3.6.0", 23 | "method-override": "^2.1.2", 24 | "mocha": "^1.21.4", 25 | "mongoose": "latest", 26 | "morgan": "^1.4.1", 27 | "passport": "~0.2.0", 28 | "passport-facebook": "latest", 29 | "passport-google-oauth": "latest", 30 | "passport-local": "~0.1.6", 31 | "passport-twitter": "latest", 32 | "socket.io": "^1.0.6", 33 | "socket.io-client": "^1.0.6", 34 | "socketio-jwt": "^2.3.5", 35 | "static-favicon": "^2.0.0-alpha", 36 | "winston": "^0.8.0" 37 | }, 38 | "devDependencies": { 39 | "bower": "~1.3.12", 40 | "grunt": "~0.4.4", 41 | "grunt-autoprefixer": "~0.7.2", 42 | "grunt-bower-install": "~1.4.0", 43 | "grunt-concurrent": "~0.5.0", 44 | "grunt-contrib-clean": "~0.5.0", 45 | "grunt-contrib-concat": "~0.4.0", 46 | "grunt-contrib-copy": "~0.5.0", 47 | "grunt-contrib-cssmin": "~0.9.0", 48 | "grunt-contrib-htmlmin": "~0.2.0", 49 | "grunt-contrib-imagemin": "~0.7.1", 50 | "grunt-contrib-jshint": "~0.10.0", 51 | "grunt-contrib-uglify": "~0.4.0", 52 | "grunt-contrib-watch": "~0.6.1", 53 | "grunt-google-cdn": "~0.4.0", 54 | "grunt-newer": "~0.7.0", 55 | "grunt-ng-annotate": "^0.2.3", 56 | "grunt-rev": "~0.1.0", 57 | "grunt-svgmin": "~0.4.0", 58 | "grunt-usemin": "~2.1.1", 59 | "grunt-env": "~0.4.1", 60 | "grunt-node-inspector": "~0.1.5", 61 | "grunt-nodemon": "~0.2.0", 62 | "grunt-angular-templates": "^0.5.4", 63 | "grunt-dom-munger": "^3.4.0", 64 | "grunt-protractor-runner": "^1.1.0", 65 | "grunt-asset-injector": "^0.1.0", 66 | "grunt-karma": "~0.8.2", 67 | "grunt-mocha-test": "~0.10.2", 68 | "jit-grunt": "^0.5.0", 69 | "time-grunt": "~0.3.1", 70 | "grunt-express-server": "~0.4.17", 71 | "grunt-open": "~0.2.3", 72 | "open": "~0.0.4", 73 | "jshint-stylish": "~0.1.5", 74 | "connect-livereload": "~0.4.0", 75 | "karma-ng-scenario": "~0.1.0", 76 | "karma-firefox-launcher": "~0.1.3", 77 | "karma-script-launcher": "~0.1.0", 78 | "karma-html2js-preprocessor": "~0.1.0", 79 | "karma-ng-jade2js-preprocessor": "^0.1.2", 80 | "karma-jasmine": "~0.1.5", 81 | "karma-chrome-launcher": "~0.1.3", 82 | "requirejs": "~2.1.11", 83 | "karma-requirejs": "~0.2.1", 84 | "karma-coffee-preprocessor": "~0.2.1", 85 | "karma-jade-preprocessor": "0.0.11", 86 | "karma-phantomjs-launcher": "~0.1.4", 87 | "karma": "~0.12.9", 88 | "karma-ng-html2js-preprocessor": "~0.1.0", 89 | "supertest": "~0.11.0", 90 | "should": "~3.3.1" 91 | }, 92 | "engine": "node >= 0.6.6", 93 | "repository": { 94 | "type": "git", 95 | "url": "http://github.com/syzer/JS-Spark" 96 | }, 97 | "keywords": [ 98 | "multicore", 99 | "js-spark", 100 | "spark", 101 | "spark-js", 102 | "computation", 103 | "job", 104 | "que", 105 | "distributed", 106 | "lodash" 107 | ], 108 | "scripts": { 109 | "start": "node index & node client & node client", 110 | "test": "mocha private/test/ --recursive --require private/test/bootstrap.js", 111 | "postinstall": "bower install" 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /private/src/server/views/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JS-Spark: Page Not Found :( 6 | 141 | 142 | 143 |
144 |

Not found :(

145 | 146 |

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

147 | 148 |

It looks like this was the result of either:

149 |
    150 |
  • a mistyped address
  • 151 |
  • an out-of-date link
  • 152 |
153 | 156 | 157 |
158 | 159 | 160 | -------------------------------------------------------------------------------- /private/src/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | JS-Spark 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 41 | 42 | 43 |
44 | 45 | 46 | 62 | 63 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /private/src/client/components/socket/socket.service.js: -------------------------------------------------------------------------------- 1 | /* global io */ 2 | 'use strict'; 3 | 4 | // hack to use with node 5 | var angular = angular || { 6 | module: function () { 7 | return this; 8 | }, 9 | factory: function () { 10 | return this; 11 | } 12 | }; 13 | 14 | // hack to use with node (own browserify) 15 | var module = module || {}; 16 | module.exports = function (_) { 17 | return { 18 | registerJsSparkTaskHandler: registerJsSparkTaskHandler 19 | }; 20 | 21 | function registerJsSparkTaskHandler(ioClient) { 22 | ioClient.on('task', function (receivedTask) { 23 | var task, 24 | response; 25 | try { 26 | task = JSON.parse(receivedTask.task, functionCreate); 27 | response = task.execute(_, task.data).value(); 28 | ioClient.emit('response', 29 | {id: receivedTask.id, resp: response} 30 | ); 31 | //console.log('Client response', response); 32 | } catch (error) { 33 | console.error('Error:', error.stack); 34 | if ('SyntaxError' === error.name) { 35 | return ioClient.emit('syntaxError', {id: receivedTask.id, resp: error.toString()}); 36 | } 37 | ioClient.emit('clientError', {id: receivedTask.id, resp: error.toString()}); 38 | } 39 | }); 40 | } 41 | 42 | // CSP may block Function call, function used not to use eval 43 | function functionCreate(key, value) { 44 | if (!key) { 45 | return value; 46 | } 47 | 48 | if ('string' === typeof value) { 49 | var funcRegExp = /^function[^\(]*\(([^\)]*)\)[^\{]*{(([^\}]*|\}[^$])*)\}$/, 50 | match = value.match(funcRegExp); 51 | if (match) { 52 | var args = match[1] 53 | .split(',') 54 | .map(function (arg) { 55 | return arg.replace(/\s+/, ''); 56 | }); 57 | return new Function(args, match[2]); 58 | } 59 | } 60 | return value; 61 | } 62 | }; 63 | 64 | angular.module('jsSparkUiApp') 65 | .factory('socket', function (socketFactory, _) { 66 | 67 | // socket.io now auto-configures its connection when we omit a connection url 68 | var ioSocket = io(null, { 69 | // Send auth token on connection, you will need to DI the Auth service above 70 | // 'query': 'token=' + Auth.getToken() 71 | }); 72 | 73 | var socket = socketFactory({ 74 | ioSocket: ioSocket 75 | }); 76 | 77 | module.exports(_).registerJsSparkTaskHandler(socket); 78 | 79 | return { 80 | socket: socket, 81 | 82 | /** 83 | * Register listeners to sync an array with updates on a model 84 | * 85 | * Takes the array we want to sync, the model name that socket updates are sent from, 86 | * and an optional callback function after new items are updated. 87 | * 88 | * @param {String} modelName 89 | * @param {Array} array 90 | * @param {Function} cb 91 | */ 92 | syncUpdates: function (modelName, array, cb) { 93 | cb = cb || angular.noop; 94 | 95 | //TODO use _.merge 96 | /** 97 | * Syncs item creation/updates on 'model:save' 98 | */ 99 | socket.on(modelName + ':save', function (item) { 100 | var oldItem = _.find(array, {_id: item._id}); 101 | var index = array.indexOf(oldItem); 102 | var event = 'created'; 103 | 104 | // replace oldItem if it exists 105 | // otherwise just add item to the collection 106 | if (oldItem) { 107 | array.splice(index, 1, item); 108 | event = 'updated'; 109 | } else { 110 | array.push(item); 111 | } 112 | 113 | cb(event, item, array); 114 | }); 115 | 116 | /** 117 | * Syncs removed items on 'model:remove' 118 | */ 119 | socket.on(modelName + ':remove', function (item) { 120 | var event = 'deleted'; 121 | _.remove(array, {_id: item._id}); 122 | cb(event, item, array); 123 | }); 124 | }, 125 | 126 | /** 127 | * Removes listeners for a models updates on the socket 128 | * 129 | * @param modelName 130 | */ 131 | unsyncUpdates: function (modelName) { 132 | socket.removeAllListeners(modelName + ':save'); 133 | socket.removeAllListeners(modelName + ':remove'); 134 | } 135 | }; 136 | }); 137 | 138 | -------------------------------------------------------------------------------- /private/src/client/components/auth/auth.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('jsSparkUiApp') 4 | .factory('Auth', function Auth($location, $rootScope, $http, User, $cookieStore, $q) { 5 | var currentUser = {}; 6 | if ($cookieStore.get('token')) { 7 | currentUser = User.get(); 8 | } 9 | 10 | return { 11 | 12 | /** 13 | * Authenticate user and save token 14 | * 15 | * @param {Object} user - login info 16 | * @param {Function} callback - optional 17 | * @return {Promise} 18 | */ 19 | login: function (user, callback) { 20 | var cb = callback || angular.noop; 21 | var deferred = $q.defer(); 22 | 23 | $http.post('/auth/local', { 24 | email: user.email, 25 | password: user.password 26 | }). 27 | success(function (data) { 28 | $cookieStore.put('token', data.token); 29 | currentUser = User.get(); 30 | deferred.resolve(data); 31 | return cb(); 32 | }). 33 | error(function (err) { 34 | this.logout(); 35 | deferred.reject(err); 36 | return cb(err); 37 | }.bind(this)); 38 | 39 | return deferred.promise; 40 | }, 41 | 42 | /** 43 | * Delete access token and user info 44 | * 45 | * @param {Function} 46 | */ 47 | logout: function () { 48 | $cookieStore.remove('token'); 49 | currentUser = {}; 50 | }, 51 | 52 | /** 53 | * Create a new user 54 | * 55 | * @param {Object} user - user info 56 | * @param {Function} callback - optional 57 | * @return {Promise} 58 | */ 59 | createUser: function (user, callback) { 60 | var cb = callback || angular.noop; 61 | 62 | return User.save(user, 63 | function (data) { 64 | $cookieStore.put('token', data.token); 65 | currentUser = User.get(); 66 | return cb(user); 67 | }, 68 | function (err) { 69 | this.logout(); 70 | return cb(err); 71 | }.bind(this)).$promise; 72 | }, 73 | 74 | /** 75 | * Change password 76 | * 77 | * @param {String} oldPassword 78 | * @param {String} newPassword 79 | * @param {Function} callback - optional 80 | * @return {Promise} 81 | */ 82 | changePassword: function (oldPassword, newPassword, callback) { 83 | var cb = callback || angular.noop; 84 | 85 | return User.changePassword({ id: currentUser._id }, { 86 | oldPassword: oldPassword, 87 | newPassword: newPassword 88 | }, function (user) { 89 | return cb(user); 90 | }, function (err) { 91 | return cb(err); 92 | }).$promise; 93 | }, 94 | 95 | /** 96 | * Gets all available info on authenticated user 97 | * 98 | * @return {Object} user 99 | */ 100 | getCurrentUser: function () { 101 | return currentUser; 102 | }, 103 | 104 | /** 105 | * Check if a user is logged in 106 | * 107 | * @return {Boolean} 108 | */ 109 | isLoggedIn: function () { 110 | return currentUser.hasOwnProperty('role'); 111 | }, 112 | 113 | /** 114 | * Waits for currentUser to resolve before checking if user is logged in 115 | */ 116 | isLoggedInAsync: function (cb) { 117 | if (currentUser.hasOwnProperty('$promise')) { 118 | currentUser.$promise.then(function () { 119 | cb(true); 120 | }).catch(function () { 121 | cb(false); 122 | }); 123 | } else if (currentUser.hasOwnProperty('role')) { 124 | cb(true); 125 | } else { 126 | cb(false); 127 | } 128 | }, 129 | 130 | /** 131 | * Check if a user is an admin 132 | * 133 | * @return {Boolean} 134 | */ 135 | isAdmin: function () { 136 | return currentUser.role === 'admin'; 137 | }, 138 | 139 | /** 140 | * Get auth token 141 | */ 142 | getToken: function () { 143 | return $cookieStore.get('token'); 144 | } 145 | }; 146 | }); 147 | -------------------------------------------------------------------------------- /private/src/server/service/dispatcher.js: -------------------------------------------------------------------------------- 1 | module.exports = function dispatcherService(log, ioServer, serializer, _, workers, uiApplicationModels) { 2 | 3 | // TODO: task clients send, received form clients, 4 | // TODO maybe tasks here are not required? 5 | var tasks = []; 6 | 7 | // maybe merge with tasks 8 | var promises = []; 9 | 10 | return { 11 | start: start, 12 | addTask: addTask, 13 | stop: stop 14 | }; 15 | 16 | // TODO check here validity of client message 17 | // TODO task manger should decide if we should reject or resolve 18 | // TODO reject worker tasks after some timeout(he yet may reconnect) 19 | function start() { 20 | var handleMessage = function (socket) { 21 | var onError, 22 | worker; 23 | 24 | log.info('New client of dispatcher ', socket.id); 25 | log.info('[%s] CONNECTED', socket.address); 26 | 27 | // this is optional 28 | authenticateClient(socket); 29 | 30 | registerApplicationHandlers(socket); 31 | 32 | // Try to give him a task. 33 | worker = workers.create(socket); 34 | emitFreeTask(worker); 35 | 36 | // process client response 37 | socket.on('response', function (data) { 38 | log.info('Client response ', socket.id); 39 | log.info('task id', data.id); 40 | log.info('data', data.resp); 41 | promises[socket.id] && promises[socket.id].resolve(data.resp); 42 | workers.free(worker, 1); 43 | emitFreeTask(worker); 44 | }); 45 | 46 | // drop dead clients 47 | socket.on('disconnect', function () { 48 | workers.free(worker, -1); // penalty 49 | workers.remove(worker); 50 | promises[socket.id] && promises[socket.id].reject('Client disconnected'); 51 | }); 52 | 53 | // When the client emits 'info', this listens and executes 54 | socket.on('info', function (data) { 55 | console.info('[%s] %s', socket.address, JSON.stringify(data, null, 2)); 56 | }); 57 | 58 | onError = _.partial(onClientError, worker, socket); 59 | socket.on('syntaxError', onError); 60 | socket.on('clientError', onError); 61 | }; 62 | ioServer.on('connection', handleMessage); 63 | } 64 | 65 | // JS-Spark UI handlers, its getting java-ish 66 | function registerApplicationHandlers(socket) { 67 | uiApplicationModels.forEach(function (model) { 68 | model.register(socket); 69 | }); 70 | } 71 | 72 | function onClientError(worker, socket, data) { 73 | log.error('client ', socket.id, ', task ', data.id, ', reports error:', data.resp); 74 | promises[socket.id] && promises[socket.id].reject(data.resp); 75 | workers.free(worker, -1); // penalty 76 | emitFreeTask(worker); 77 | } 78 | 79 | function authenticateClient(socket) { 80 | // We can authenticate socket.io users and access their token through socket.handshake.decoded_token 81 | // 82 | // 1. You will need to send the token in `client/components/socket/socket.service.js` 83 | // 84 | // 2. Require authentication here: 85 | // socketio.use(require('socketio-jwt').authorize({ 86 | // secret: config.secrets.session, 87 | // handshake: true 88 | // })); 89 | 90 | // set socket address 91 | socket.address = socket.handshake.address !== null ? 92 | socket.handshake.address.address + ':' + socket.handshake.address.port : 93 | process.env.DOMAIN; 94 | 95 | socket.connectedAt = new Date(); 96 | } 97 | 98 | // this will eventually time out all clients 99 | // may also iterate thu clients list and shutdown them immediately 100 | function stop() { 101 | var stopDate = new Date(); 102 | log.info('dispatching stopped @' + stopDate); 103 | ioServer.close(); 104 | 105 | return stopDate; 106 | } 107 | 108 | function emitFreeTask(worker) { 109 | if (_.isEmpty(tasks)) { 110 | return; 111 | } 112 | var task = tasks.pop(); 113 | promises[worker._id] = task.deferred; 114 | workers.busy(worker); 115 | worker.socket.emit( 116 | 'task', { 117 | id: newUniqueTaskId(worker._id), 118 | task: task.task 119 | } 120 | ); 121 | } 122 | 123 | // maybe better hashing algorithm than 124 | // consequent unique numbers + prefix 125 | function newUniqueTaskId(prefix) { 126 | return _.uniqueId(prefix); 127 | } 128 | 129 | // TODO refactor out to task manager 130 | // maybe memorize stringify() with _.memoize(serializer.stringify) 131 | // task schema 132 | function newTask(task, clientId, deferred) { 133 | clientId = clientId || ''; 134 | 135 | return { 136 | id: newUniqueTaskId(clientId), 137 | task: serializer.stringify(task), 138 | deferred: deferred 139 | } 140 | } 141 | 142 | // TODO add different strategies 143 | function addTask(task, deferred) { 144 | tasks.push(newTask(task, '', deferred)); 145 | 146 | var w = workers.getFirstFree(); 147 | if (w) { 148 | emitFreeTask(w); 149 | } 150 | } 151 | }; 152 | -------------------------------------------------------------------------------- /private/src/server/config/di.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by syzer on 5/15/2014. 3 | */ 4 | 'use strict'; 5 | 6 | // for npm main path 7 | var MAIN_PATH = __dirname + '/../../../../'; 8 | // for UI/app 9 | var ROOT_PATH = __dirname + '/../'; 10 | var DATA_PATH = ROOT_PATH + '../../data/'; 11 | var PROD_SETTINGS = 'production'; 12 | 13 | var services = { 14 | _: function addService(di) { 15 | return require('lodash'); 16 | }, 17 | app: function addService(di) { 18 | var express = di.get('express'); 19 | var app = express(); 20 | app.use(di.get('bodyParser')); 21 | app.use(di.get('methodOverride')); 22 | 23 | var allowCrossDomain = function(req, res, next) { 24 | res.setHeader('Access-Control-Allow-Origin', '*'); //http://example.com ////config.allowedDomains 25 | res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, DELETE, OPTIONS'); 26 | res.header('Access-Control-Allow-Headers', 'x-xsrf-token, X-Requested-With, X-HTTP-Method-Override, Authorization, Origin, Content-Type, Accept'); 27 | res.header('Access-Control-Allow-Credentials', 'true'); //for basic auth 28 | 29 | next(); 30 | }; 31 | app.use(allowCrossDomain); 32 | 33 | return app; 34 | }, 35 | bodyParser: function addService() { 36 | return require('body-parser').urlencoded({ 37 | extended: true 38 | }) 39 | }, 40 | cookieParser: require('cookie-parser')(), 41 | compression: require('compression')(), 42 | connect: require('connect'), 43 | config: function addService(di) { 44 | var config = require(ROOT_PATH + 'config/config')(ROOT_PATH, DATA_PATH, MAIN_PATH); 45 | // use adapter 46 | if (process.env.NODE_ENV === PROD_SETTINGS) { 47 | console.log('Running in production\n'); 48 | return require(ROOT_PATH + 'config/prodConfig')(config); 49 | } 50 | return config; 51 | }, 52 | 'controller.index': function addService(di) { 53 | return require(ROOT_PATH + 'service/index')(ROOT_PATH); 54 | }, 55 | defer: function addService(di) { 56 | return require(ROOT_PATH + 'service/defer')( 57 | di.get('promise') 58 | ); 59 | }, 60 | exec: require('child_process').exec, 61 | express: require('express'), 62 | events: require('events'), 63 | http: require('http'), 64 | io: require('socket.io'), 65 | 'io.server': function addService(di) { 66 | var server = di.get('server'); 67 | 68 | return di.get('io').listen(server); 69 | }, 70 | // Lets you use HTTP verbs such as PUT or DELETE in places you normally can't. 71 | methodOverride: require('method-override')(), 72 | port: (process.env.PORT || 9000), 73 | promise: require('bluebird'), 74 | log: function addService(di) { 75 | return require(ROOT_PATH + 'service/logging')( 76 | di.get('config'), 77 | di.get('winston') 78 | ).createLog(); 79 | }, 80 | 'server': function addService(di) { 81 | var app = di.get('app'); 82 | // can easily switch here for https 83 | return di.get('http').createServer(app); 84 | }, 85 | 'service.dispatcher': function addService(di) { 86 | return require(ROOT_PATH + 'service/dispatcher')( 87 | di.get('log'), 88 | di.get('io.server'), 89 | di.get('service.serializer'), 90 | di.get('_'), 91 | di.get('service.workers'), 92 | di.get('service.uiApplicationModels') 93 | ); 94 | }, 95 | 'service.fork': function addService(di) { 96 | var fork = di 97 | .get('promise') 98 | .promisify(require('child_process').fork); 99 | 100 | return require(ROOT_PATH + 'service/fork')( 101 | di.get('log'), 102 | fork, 103 | di.get('_'), 104 | di.get('promise'), 105 | // TODO client.js->bower 106 | MAIN_PATH + 'client.js' 107 | ); 108 | }, 109 | 'service.jsSpark': function addService(di) { 110 | return require(ROOT_PATH + 'service/jsSpark')( 111 | di.get('service.taskManager') 112 | ); 113 | }, 114 | 'service.serializer': function addService(di) { 115 | return require(ROOT_PATH + 'service/serializer')( 116 | JSON 117 | ); 118 | }, 119 | 'service.taskManager': function addService(di) { 120 | var taskManager = require(ROOT_PATH + 'service/taskManager')( 121 | di.get('config'), 122 | di.get('log'), 123 | di.get('service.dispatcher'), 124 | di.get('service.workers'), 125 | di.get('defer'), 126 | di.get('promise'), 127 | di.get('server'), 128 | di.get('port'), 129 | di.get('_') 130 | ); 131 | taskManager.init(); 132 | return taskManager; 133 | }, 134 | 'service.uiApplicationModels': function addService(di) { 135 | return [ 136 | require(ROOT_PATH + 'api/thing/thing.socket'), 137 | require(ROOT_PATH + 'api/client/client.socket')( 138 | di.get('service.workers') 139 | ) 140 | // Insert OTHER sockets below 141 | ] 142 | }, 143 | 'service.workers': function addService(di) { 144 | return require(ROOT_PATH + 'service/workers')( 145 | di.get('log'), 146 | di.get('_') 147 | ); 148 | }, 149 | session: function addService(di) { 150 | return require('express-session')({ 151 | secret: 'shhhhhhhhh!' 152 | }); 153 | }, 154 | util: require('util'), 155 | winston: require('winston') 156 | }; 157 | module.exports.services = services; 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | What is JS-Spark 2 | ==== 3 | Distributed real time computation/job/work queue using JavaScript. 4 | A JavaScript reimagining of the fabulous Apache Spark and Storm projects. 5 | 6 | If you know `underscore.js` or [`lodash.js`](https://lodash.com/) you may use JS-Spark 7 | as a distributed version of them. 8 | 9 | If you know Distributed-RPC systems like [storm](https://storm.incubator.apache.org/documentation/Distributed-RPC.html) 10 | you will feel at home. 11 | 12 | If you've ever worked with distributed work queues such as Celery, 13 | you will find JS-Spark easy to use. 14 | 15 | ![main page](https://raw.github.com/syzer/JS-Spark/master/public/docs/JS-Spark-main-page.png) 16 | ![computing que](https://raw.github.com/syzer/JS-Spark/master/public/docs/JS-Spark-computing-que-view.png) 17 | 18 | 19 | 20 | Why 21 | === 22 | There are no JS tools that can offload your processing to 1000+ CPUs. 23 | Furthermore, existing tools in other languages, such as Seti@Home, 24 | Gearman, require time, expensive setup of server, and later setting up/supervising clients machines. 25 | 26 | **We want to do better.** 27 | On JS-Spark your clients need just to click on a **URL**, and the server side has one line installation (less than 5 min). 28 | 29 | Hadoop is quite slow and requires maintaining a cluster - **we can to do better**. 30 | Imagine that there's no need to set up expensive cluster/cloud solutions. 31 | Use web browsers! Easily scale to multiple clients. Clients do not need to install anything like Java or other plugins. 32 | 33 | Setup in a matter of minutes and you are good to go. 34 | 35 | The possibilities are endless: 36 | -------------------------- 37 | No need to setup expensive clusters. 38 | The setup takes 5 min and you are good to go. 39 | You can do it on one machine. Even on a Raspberry Pi. 40 | 41 | * Use as ML tool to process in real time huge streams of data... while all clients still browse their favorite websites 42 | 43 | * Use for big data analytics. Connect to Hadoop HDFS and process even terabytes of data. 44 | 45 | * Use to safely transfer huge amount of data to remote computers. 46 | 47 | * Use as CDN... Today most websites runs slower when more clients use them. 48 | But using JS-Spark you can totally reverse this trend. Build websites that run FASTER the more people use them 49 | 50 | * Synchronize data between multiple smartphones.. even in Africa 51 | 52 | * No expensive cluster setup required! 53 | 54 | * Free to use. 55 | 56 | How (Getting started with npm) 57 | ============================= 58 | To add a distributed job queue to any node app run: 59 | 60 | npm i --save js-spark 61 | 62 | Look for **Usage with npm**. 63 | 64 | Example: running multicore jobs in JS: 65 | ==================================== 66 | ### Simple example with node multicore jobs 67 | [example-js-spark-usage](https://github.com/syzer/example-js-spark-usage) 68 | 69 | ```bash 70 | git clone git@github.com:syzer/example-js-spark-usage.git && cd $_ 71 | npm install 72 | ``` 73 | 74 | ### Game of life example 75 | [distributed-game-of-life](https://github.com/syzer/distributed-game-of-life.git) 76 | 77 | ```bash 78 | git clone https://github.com/syzer/distributed-game-of-life.git && cd $_ 79 | npm install 80 | ``` 81 | 82 | 83 | ### Example: NLP 84 | This example shows how to use one of the Natural Language Processing tools called N-Gram 85 | in a distributed manner using JS-Spark: 86 | 87 | [Distributed-N-Gram](https://github.com/syzer/distributedNgram) 88 | 89 | 90 | If you'd like to know more about N-grams please read: 91 | 92 | [http://en.wikipedia.org/wiki/N-gram](http://en.wikipedia.org/wiki/N-gram) 93 | 94 | 95 | How (Getting started) 96 | ==================== 97 | Prerequisites: install `Node.js`, then: 98 | install grunt and bower, 99 | 100 | ```bash 101 | sudo npm install -g bower 102 | sudo npm install -g grunt 103 | ``` 104 | 105 | Install `js-spark` 106 | ---------------- 107 | ```bash 108 | npm i --save js-spark 109 | #or use: 110 | git clone git@github.com:syzer/JS-Spark.git && cd $_ 111 | npm install 112 | ``` 113 | 114 | Then run: 115 | 116 | node index & 117 | node client 118 | 119 | Or: 120 | 121 | npm start 122 | 123 | After that you may see how the clients do the heavy lifting. 124 | 125 | 126 | Usage with npm 127 | ============== 128 | 129 | ```JavaScript 130 | var core = require('jsSpark')({workers:8}); 131 | var jsSpark = core.jsSpark; 132 | 133 | jsSpark([20, 30, 40, 50]) 134 | // this is executed on the client 135 | .map(function addOne(num) { 136 | return num + 1; 137 | }) 138 | .reduce(function sumUp(sum, num) { 139 | return sum + num; 140 | }) 141 | .thru(function addString(num){ 142 | return "It was a number but I will convert it to " + num; 143 | }) 144 | .run() 145 | .then(function(data) { 146 | // this is executed on back on the server 147 | console.log(data); 148 | }) 149 | ``` 150 | 151 | Usage (Examples) 152 | =============== 153 | Client side heavy CPU computation (MapReduce) 154 | -------------------------------------------- 155 | 156 | ```JavaScript 157 | task = jsSpark([20, 30, 40, 50]) 158 | // this is executed on client side 159 | .map(function addOne(num) { 160 | return num + 1; 161 | }) 162 | .reduce(function sumUp(sum, num) { 163 | return sum + num; 164 | }) 165 | .run(); 166 | ``` 167 | 168 | 169 | Distributed version of lodash/underscore 170 | ---------------------------------------- 171 | 172 | ```JavaScript 173 | jsSpark(_.range(10)) 174 | // https://lodash.com/docs#sortBy 175 | .add('sortBy', function _sortBy(el) { 176 | return Math.sin(el); 177 | }) 178 | .map(function multiplyBy2(el) { 179 | return el * 2; 180 | }) 181 | .filter(function remove5and10(el) { 182 | return el % 5 !== 0; 183 | }) 184 | // sum of [ 2, 4, 6, 8, 12, 14, 16, 18 ] => 80 185 | .reduce(function sumUp(arr, el) { 186 | return arr + el; 187 | }) 188 | .run(); 189 | ``` 190 | 191 | 192 | Multiple retry and clients elections 193 | ------------------------------------ 194 | If you run calculations via unknown clients is better to recalculate 195 | same tasks on different clients: 196 | 197 | 198 | ```JavaScript 199 | jsSpark(_.range(10)) 200 | .reduce(function sumUp(sum, num) { 201 | return sum + num; 202 | }) 203 | // how many times to repeat calculations 204 | .run({times: 6}) 205 | .then(function whenClientsFinished(data) { 206 | // may also get 2 most relevant answers 207 | console.log('Most clients believe that:'); 208 | console.log('Total sum of numbers from 1 to 10 is:', data); 209 | }) 210 | .catch(function whenClientsArgue(reason) { 211 | console.log('Most clients could not agree, ', + reason.toString()); 212 | }); 213 | ``` 214 | 215 | 216 | Combined usage with server side processing 217 | ------------------------------------------ 218 | 219 | ```JavaScript 220 | task3 = task 221 | .then(function serverSideComputingOfData(data) { 222 | var basesNumber = data + 21; 223 | // All your 101 base are belong to us 224 | console.log('All your ' + basesNumber + ' base are belong to us'); 225 | return basesNumber; 226 | }) 227 | .catch(function (reason) { 228 | console.log('Task could not compute ' + reason.toString()); 229 | }); 230 | ``` 231 | 232 | 233 | More references 234 | =============== 235 | This project involves reimplementing some nice things from the world of big data, so there are of course some nice 236 | resources you can use to dive into the topic: 237 | 238 | * [Map-Reduce revisited](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.104.5859&rep=rep1&type=pdf) 239 | * [Awesome BigData - A curated list of awesome frameworks, resources and other things.](https://github.com/onurakpolat/awesome-bigdata) 240 | 241 | 242 | Running with UI 243 | =============== 244 | 245 | Normally you do not need to start UI server. But if you want to build an application on top on the js-spark UI server. Feel free to do so. 246 | 247 | git clone git@github.com:syzer/JS-Spark.git && cd $_ 248 | npm install 249 | grunt build 250 | grunt serve 251 | 252 | To spam more light-weight (headless) clients: 253 | 254 | node client 255 | 256 | 257 | 258 | Required to run UI 259 | ================== 260 | * mongoDB 261 | default connection parameters: 262 | 263 | * mongodb://localhost/jssparkui-dev user: 'js-spark', pass: 'js-spark1' 264 | install mongo, make sure mongod(mongo service) is running 265 | run mongo shell with command: 266 | 267 | ```js 268 | mongo 269 | use jssparkui-dev 270 | db.createUser({ 271 | user: "js-spark", 272 | pwd: "js-spark1", 273 | roles: [ 274 | { role: "readWrite", db: "jssparkui-dev" } 275 | ] 276 | }) 277 | ``` 278 | * old mongodb engines can use `db.addUser()` with same API 279 | * to run without UI db code is not required! 280 | 281 | * on first run you need to seed the db: change option `seedDB: false` => `seedDB: true` 282 | on `./private/srv/server/config/environment/development.js` 283 | 284 | Tests 285 | ===== 286 | `npm test` 287 | 288 | 289 | TODO 290 | ==== 291 | - [X] service/file -> removed for other module 292 | - [ ] di -> separate module 293 | - [!] bower for js-spark client 294 | - [ ] config-> merge different config files 295 | - [!] server/auth -> split to js-spark-ui module 296 | - [!] server/api/jobs -> split to js-spark-ui module 297 | - [ ] split ui 298 | - [X] more examples 299 | - [X] example with cli usage (not daemon) 300 | - [X] example with using thu 301 | - [?] .add() is might be broken... maybe fix or remove 302 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (grunt) { 4 | var localConfig; 5 | try { 6 | localConfig = require('./private/src/server/config/local.env'); 7 | } catch (e) { 8 | localConfig = {}; 9 | } 10 | 11 | // Load grunt tasks automatically, when needed 12 | require('jit-grunt')(grunt, { 13 | express: 'grunt-express-server', 14 | useminPrepare: 'grunt-usemin', 15 | ngtemplates: 'grunt-angular-templates', 16 | cdnify: 'grunt-google-cdn', 17 | protractor: 'grunt-protractor-runner', 18 | injector: 'grunt-asset-injector' 19 | }); 20 | 21 | // Time how long tasks take. Can help when optimizing build times 22 | require('time-grunt')(grunt); 23 | 24 | // Define the configuration for all the tasks 25 | grunt.initConfig({ 26 | 27 | // Project settings 28 | yeoman: { 29 | // configurable paths 30 | client: require('./bower.json').appPath || 'private/src/client', 31 | dist: 'dist' 32 | }, 33 | express: { 34 | options: { 35 | port: process.env.PORT || 9000 36 | }, 37 | dev: { 38 | options: { 39 | script: 'private/src/server/app.js', 40 | debug: true 41 | } 42 | }, 43 | prod: { 44 | options: { 45 | script: 'dist/server/app.js' 46 | } 47 | } 48 | }, 49 | open: { 50 | server: { 51 | url: 'http://localhost:<%= express.options.port %>' 52 | } 53 | }, 54 | watch: { 55 | injectJS: { 56 | files: [ 57 | '<%= yeoman.client %>/{app,components}/**/*.js', 58 | '!<%= yeoman.client %>/{app,components}/**/*.spec.js', 59 | '!<%= yeoman.client %>/{app,components}/**/*.mock.js', 60 | '!<%= yeoman.client %>/app/app.js'], 61 | tasks: ['injector:scripts'] 62 | }, 63 | injectCss: { 64 | files: [ 65 | '<%= yeoman.client %>/{app,components}/**/*.css' 66 | ], 67 | tasks: ['injector:css'] 68 | }, 69 | mochaTest: { 70 | files: ['private/srv/server/test/**/*.spec.js'], 71 | tasks: ['env:test', 'mochaTest'] 72 | }, 73 | jsTest: { 74 | files: [ 75 | '<%= yeoman.client %>/{app,components}/**/*.spec.js', 76 | '<%= yeoman.client %>/{app,components}/**/*.mock.js' 77 | ], 78 | tasks: ['newer:jshint:all', 'karma'] 79 | }, 80 | gruntfile: { 81 | files: ['Gruntfile.js'] 82 | }, 83 | livereload: { 84 | files: [ 85 | '{.tmp,<%= yeoman.client %>}/{app,components}/**/*.css', 86 | '{.tmp,<%= yeoman.client %>}/{app,components}/**/*.html', 87 | '{.tmp,<%= yeoman.client %>}/{app,components}/**/*.js', 88 | '!{.tmp,<%= yeoman.client %>}{app,components}/**/*.spec.js', 89 | '!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.mock.js', 90 | '<%= yeoman.client %>/assets/images/{,*//*}*.{png,jpg,jpeg,gif,webp,svg}' 91 | ], 92 | options: { 93 | livereload: true 94 | } 95 | }, 96 | express: { 97 | files: [ 98 | 'private/src/server/**/*.{js,json}' 99 | ], 100 | tasks: ['express:dev', 'wait'], 101 | options: { 102 | livereload: true, 103 | nospawn: true //Without this option specified express won't be reloaded 104 | } 105 | } 106 | }, 107 | 108 | // Make sure code styles are up to par and there are no obvious mistakes 109 | jshint: { 110 | options: { 111 | jshintrc: '<%= yeoman.client %>/.jshintrc', 112 | reporter: require('jshint-stylish') 113 | }, 114 | server: { 115 | options: { 116 | jshintrc: 'private/src/server/.jshintrc' 117 | }, 118 | src: [ 'private/src/server/{,*/}*.js'] 119 | }, 120 | all: [ 121 | '<%= yeoman.client %>/{app,components}/**/*.js', 122 | '!<%= yeoman.client %>/{app,components}/**/*.spec.js', 123 | '!<%= yeoman.client %>/{app,components}/**/*.mock.js' 124 | ], 125 | test: { 126 | src: [ 127 | '<%= yeoman.client %>/{app,components}/**/*.spec.js', 128 | '<%= yeoman.client %>/{app,components}/**/*.mock.js' 129 | ] 130 | } 131 | }, 132 | 133 | // Empties folders to start fresh 134 | clean: { 135 | dist: { 136 | files: [ 137 | { 138 | dot: true, 139 | src: [ 140 | '.tmp', 141 | '<%= yeoman.dist %>/*', 142 | '!<%= yeoman.dist %>/.git*', 143 | '!<%= yeoman.dist %>/.openshift', 144 | '!<%= yeoman.dist %>/Procfile' 145 | ] 146 | } 147 | ] 148 | }, 149 | server: '.tmp' 150 | }, 151 | 152 | // Add vendor prefixed styles 153 | autoprefixer: { 154 | options: { 155 | browsers: ['last 1 version'] 156 | }, 157 | dist: { 158 | files: [ 159 | { 160 | expand: true, 161 | cwd: '.tmp/', 162 | src: '{,*/}*.css', 163 | dest: '.tmp/' 164 | } 165 | ] 166 | } 167 | }, 168 | 169 | // Debugging with node inspector 170 | 'node-inspector': { 171 | custom: { 172 | options: { 173 | 'web-host': 'localhost' 174 | } 175 | } 176 | }, 177 | 178 | // Use nodemon to run server in debug mode with an initial breakpoint 179 | nodemon: { 180 | debug: { 181 | script: 'private/src/server/app.js', 182 | options: { 183 | nodeArgs: ['--debug-brk'], 184 | env: { 185 | PORT: process.env.PORT || 9000 186 | }, 187 | callback: function (nodemon) { 188 | nodemon.on('log', function (event) { 189 | console.log(event.colour); 190 | }); 191 | 192 | // opens browser on initial server start 193 | nodemon.on('config:update', function () { 194 | setTimeout(function () { 195 | require('open')('http://localhost:8080/debug?port=5858'); 196 | }, 500); 197 | }); 198 | } 199 | } 200 | } 201 | }, 202 | 203 | // Automatically inject Bower components into the app 204 | bowerInstall: { 205 | target: { 206 | src: '<%= yeoman.client %>/index.html', 207 | ignorePath: '<%= yeoman.client %>/', 208 | exclude: [/bootstrap-sass-official/, /bootstrap.js/, '/json3/', '/es5-shim/'] 209 | } 210 | }, 211 | 212 | // Renames files for browser caching purposes 213 | rev: { 214 | dist: { 215 | files: { 216 | src: [ 217 | '<%= yeoman.dist %>/public/{,*/}*.js', 218 | '<%= yeoman.dist %>/public/{,*/}*.css', 219 | '<%= yeoman.dist %>/public/assets/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', 220 | '<%= yeoman.dist %>/public/assets/fonts/*' 221 | ] 222 | } 223 | } 224 | }, 225 | 226 | // Reads HTML for usemin blocks to enable smart builds that automatically 227 | // concat, minify and revision files. Creates configurations in memory so 228 | // additional tasks can operate on them 229 | useminPrepare: { 230 | html: ['<%= yeoman.client %>/index.html'], 231 | options: { 232 | dest: '<%= yeoman.dist %>/public' 233 | } 234 | }, 235 | 236 | // Performs rewrites based on rev and the useminPrepare configuration 237 | usemin: { 238 | html: ['<%= yeoman.dist %>/public/{,*/}*.html'], 239 | css: ['<%= yeoman.dist %>/public/{,*/}*.css'], 240 | js: ['<%= yeoman.dist %>/public/{,*/}*.js'], 241 | options: { 242 | assetsDirs: [ 243 | '<%= yeoman.dist %>/public', 244 | '<%= yeoman.dist %>/public/assets/images' 245 | ], 246 | // This is so we update image references in our ng-templates 247 | patterns: { 248 | js: [ 249 | [/(assets\/images\/.*?\.(?:gif|jpeg|jpg|png|webp|svg))/gm, 'Update the JS to reference our revved images'] 250 | ] 251 | } 252 | } 253 | }, 254 | 255 | // The following *-min tasks produce minified files in the dist folder 256 | imagemin: { 257 | dist: { 258 | files: [ 259 | { 260 | expand: true, 261 | cwd: '<%= yeoman.client %>/assets/images', 262 | src: '{,*/}*.{png,jpg,jpeg,gif}', 263 | dest: '<%= yeoman.dist %>/public/assets/images' 264 | } 265 | ] 266 | } 267 | }, 268 | 269 | svgmin: { 270 | dist: { 271 | files: [ 272 | { 273 | expand: true, 274 | cwd: '<%= yeoman.client %>/assets/images', 275 | src: '{,*/}*.svg', 276 | dest: '<%= yeoman.dist %>/public/assets/images' 277 | } 278 | ] 279 | } 280 | }, 281 | 282 | // Allow the use of non-minsafe AngularJS files. Automatically makes it 283 | // minsafe compatible so Uglify does not destroy the ng references 284 | ngAnnotate: { 285 | dist: { 286 | files: [ 287 | { 288 | expand: true, 289 | cwd: '.tmp/concat', 290 | src: '*/**.js', 291 | dest: '.tmp/concat' 292 | } 293 | ] 294 | } 295 | }, 296 | 297 | // Package all the html partials into a single javascript payload 298 | ngtemplates: { 299 | options: { 300 | // This should be the name of your apps angular module 301 | module: 'jsSparkUiApp', 302 | htmlmin: { 303 | collapseBooleanAttributes: true, 304 | collapseWhitespace: true, 305 | removeAttributeQuotes: true, 306 | removeEmptyAttributes: true, 307 | removeRedundantAttributes: true, 308 | removeScriptTypeAttributes: true, 309 | removeStyleLinkTypeAttributes: true 310 | }, 311 | usemin: 'app/app.js' 312 | }, 313 | main: { 314 | cwd: '<%= yeoman.client %>', 315 | src: ['{app,components}/**/*.html'], 316 | dest: '.tmp/templates.js' 317 | }, 318 | tmp: { 319 | cwd: '.tmp', 320 | src: ['{app,components}/**/*.html'], 321 | dest: '.tmp/tmp-templates.js' 322 | } 323 | }, 324 | 325 | // Replace Google CDN references 326 | cdnify: { 327 | dist: { 328 | html: ['<%= yeoman.dist %>/*.html'] 329 | } 330 | }, 331 | 332 | // Copies remaining files to places other tasks can use 333 | copy: { 334 | dist: { 335 | files: [ 336 | { 337 | expand: true, 338 | dot: true, 339 | cwd: '<%= yeoman.client %>', 340 | dest: '<%= yeoman.dist %>/public', 341 | src: [ 342 | '*.{ico,png,txt}', 343 | '.htaccess', 344 | 'component/**/*', 345 | 'assets/images/{,*/}*.{webp}', 346 | 'assets/fonts/**/*', 347 | 'index.html' 348 | ] 349 | }, 350 | { 351 | expand: true, 352 | cwd: '.tmp/images', 353 | dest: '<%= yeoman.dist %>/public/assets/images', 354 | src: ['generated/*'] 355 | }, 356 | { 357 | expand: true, 358 | dest: '<%= yeoman.dist %>', 359 | src: [ 360 | 'package.json', 361 | 'server/**/*' 362 | ] 363 | } 364 | ] 365 | }, 366 | styles: { 367 | expand: true, 368 | cwd: '<%= yeoman.client %>', 369 | dest: '.tmp/', 370 | src: ['{app,components}/**/*.css'] 371 | } 372 | }, 373 | 374 | // Run some tasks in parallel to speed up the build process 375 | concurrent: { 376 | server: [ 377 | ], 378 | test: [ 379 | ], 380 | debug: { 381 | tasks: [ 382 | 'nodemon', 383 | 'node-inspector' 384 | ], 385 | options: { 386 | logConcurrentOutput: true 387 | } 388 | }, 389 | dist: [ 390 | 'imagemin', 391 | 'svgmin' 392 | ] 393 | }, 394 | 395 | // Test settings 396 | karma: { 397 | unit: { 398 | configFile: 'karma.conf.js', 399 | singleRun: true 400 | } 401 | }, 402 | 403 | mochaTest: { 404 | options: { 405 | reporter: 'spec' 406 | }, 407 | src: ['private/src/server/**/*.spec.js'] 408 | }, 409 | 410 | protractor: { 411 | options: { 412 | configFile: 'protractor.conf.js' 413 | }, 414 | chrome: { 415 | options: { 416 | args: { 417 | browser: 'chrome' 418 | } 419 | } 420 | } 421 | }, 422 | 423 | env: { 424 | test: { 425 | NODE_ENV: 'test' 426 | }, 427 | prod: { 428 | NODE_ENV: 'production' 429 | }, 430 | all: localConfig 431 | }, 432 | 433 | injector: { 434 | options: { 435 | 436 | }, 437 | // Inject application script files into index.html (doesn't include bower) 438 | scripts: { 439 | options: { 440 | transform: function (filePath) { 441 | filePath = filePath.replace('/private/src/client/', ''); 442 | filePath = filePath.replace('/.tmp/', ''); 443 | return ''; 444 | }, 445 | starttag: '', 446 | endtag: '' 447 | }, 448 | files: { 449 | '<%= yeoman.client %>/index.html': [ 450 | ['{.tmp,<%= yeoman.client %>}/{app,components}/**/*.js', 451 | '!{.tmp,<%= yeoman.client %>}/app/app.js', 452 | '!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.spec.js', 453 | '!{.tmp,<%= yeoman.client %>}/{app,components}/**/*.mock.js'] 454 | ] 455 | } 456 | }, 457 | 458 | // Inject component css into index.html 459 | css: { 460 | options: { 461 | transform: function (filePath) { 462 | filePath = filePath.replace('/private/src/client/', ''); 463 | filePath = filePath.replace('/.tmp/', ''); 464 | return ''; 465 | }, 466 | starttag: '', 467 | endtag: '' 468 | }, 469 | files: { 470 | '<%= yeoman.client %>/index.html': [ 471 | '<%= yeoman.client %>/{app,components}/**/*.css' 472 | ] 473 | } 474 | } 475 | }, 476 | }); 477 | 478 | // Used for delaying livereload until after server has restarted 479 | grunt.registerTask('wait', function () { 480 | grunt.log.ok('Waiting for server reload...'); 481 | 482 | var done = this.async(); 483 | 484 | setTimeout(function () { 485 | grunt.log.writeln('Done waiting!'); 486 | done(); 487 | }, 1500); 488 | }); 489 | 490 | grunt.registerTask('express-keepalive', 'Keep grunt running', function () { 491 | this.async(); 492 | }); 493 | 494 | grunt.registerTask('serve', function (target) { 495 | if (target === 'dist') { 496 | return grunt.task.run(['build', 'env:all', 'env:prod', 'express:prod', 'wait', 'open', 'express-keepalive']); 497 | } 498 | 499 | if (target === 'debug') { 500 | return grunt.task.run([ 501 | 'clean:server', 502 | 'env:all', 503 | 'concurrent:server', 504 | 'injector', 505 | 'bowerInstall', 506 | 'autoprefixer', 507 | 'concurrent:debug' 508 | ]); 509 | } 510 | 511 | grunt.task.run([ 512 | 'clean:server', 513 | 'env:all', 514 | 'concurrent:server', 515 | 'injector', 516 | 'bowerInstall', 517 | 'autoprefixer', 518 | 'express:dev', 519 | 'wait', 520 | 'open', 521 | 'watch' 522 | ]); 523 | }); 524 | 525 | grunt.registerTask('server', function () { 526 | grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.'); 527 | grunt.task.run(['serve']); 528 | }); 529 | 530 | grunt.registerTask('test', function (target) { 531 | if (target === 'server') { 532 | return grunt.task.run([ 533 | 'env:all', 534 | 'env:test', 535 | 'mochaTest' 536 | ]); 537 | } 538 | 539 | else if (target === 'client') { 540 | return grunt.task.run([ 541 | 'clean:server', 542 | 'env:all', 543 | 'concurrent:test', 544 | 'injector', 545 | 'autoprefixer', 546 | 'karma' 547 | ]); 548 | } 549 | 550 | else if (target === 'e2e') { 551 | return grunt.task.run([ 552 | 'clean:server', 553 | 'env:all', 554 | 'env:test', 555 | 'concurrent:test', 556 | 'injector', 557 | 'bowerInstall', 558 | 'autoprefixer', 559 | 'express:dev', 560 | 'protractor' 561 | ]); 562 | } 563 | 564 | else grunt.task.run([ 565 | 'test:server', 566 | 'test:client' 567 | ]); 568 | }); 569 | 570 | grunt.registerTask('build', [ 571 | 'clean:dist', 572 | 'concurrent:dist', 573 | 'injector', 574 | 'bowerInstall', 575 | 'useminPrepare', 576 | 'autoprefixer', 577 | 'ngtemplates', 578 | 'concat', 579 | 'ngAnnotate', 580 | 'copy:dist', 581 | 'cdnify', 582 | 'cssmin', 583 | 'uglify', 584 | 'rev', 585 | 'usemin' 586 | ]); 587 | 588 | grunt.registerTask('default', [ 589 | 'newer:jshint', 590 | 'test', 591 | 'build' 592 | ]); 593 | }; 594 | -------------------------------------------------------------------------------- /private/src/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 | --------------------------------------------------------------------------------