├── 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 |
6 |
7 | {{user.name}}
8 | {{user.email}}
9 |
10 |
11 |
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 |
16 | Sequence:
17 | Last task:
18 | Last result:
19 |
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 | Id
13 | Email
14 | IP
15 | Free
16 | Connected @
17 | Computing ques
18 | Points
19 |
20 |
21 |
22 |
23 | {{$index+1}}
24 | {{client._id}}
25 | {{client.email}}
26 | {{client.handshake.address}}
27 | {{client.free|boolean}}
28 | {{client.connectedAt|date : 'medium'}}
29 | {{client.rooms|json}}
30 | {{client.points}}
31 |
32 |
33 |
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 |
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 |
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 |
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 |
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 | 
16 | 
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 |
--------------------------------------------------------------------------------