├── Procfile ├── .bowerrc ├── fast-typing.gif ├── public ├── images │ ├── user.jpg │ ├── spacer.gif │ └── preloader.gif ├── app.js └── index.html ├── system ├── public │ ├── settings │ │ ├── settings.js │ │ ├── settings.routes.js │ │ ├── settings.controllers.js │ │ ├── settings.services.js │ │ └── views │ │ │ └── settings.html │ ├── views │ │ └── search.html │ ├── utils.js │ ├── index.js │ └── templates │ │ └── notification.html ├── models │ └── settings.js ├── config │ ├── production.js │ └── development.js ├── routes │ ├── search.js │ └── settings.js ├── helpers │ ├── emailing.js │ ├── events.js │ ├── json.js │ ├── notifications.js │ └── auth.js ├── tests │ └── system.spec.js └── index.js ├── modules ├── chats │ ├── public │ │ ├── chats.js │ │ ├── chats.routes.js │ │ ├── chats.services.js │ │ ├── views │ │ │ ├── chatsList.html │ │ │ └── chat-dialog.html │ │ └── chats.controllers.js │ └── server │ │ ├── main.js │ │ ├── routes │ │ └── chats.js │ │ ├── models │ │ └── chats.js │ │ └── controllers │ │ └── chats.js ├── posts │ ├── public │ │ ├── posts.js │ │ ├── posts.routes.js │ │ ├── views │ │ │ ├── feed.html │ │ │ └── post-single.html │ │ ├── posts.controllers.js │ │ └── posts.services.js │ └── server │ │ ├── main.js │ │ ├── routes │ │ └── posts.js │ │ └── models │ │ └── posts.js ├── streams │ ├── public │ │ ├── streams.js │ │ ├── streams.services.js │ │ ├── streams.routes.js │ │ ├── views │ │ │ └── streamsList.html │ │ └── streams.controllers.js │ └── server │ │ ├── main.js │ │ ├── routes │ │ └── streams.js │ │ ├── models │ │ └── streams.js │ │ └── controllers │ │ └── streams.js ├── activities │ ├── public │ │ ├── activities.js │ │ ├── activities.routes.js │ │ ├── activities.services.js │ │ ├── activities.controllers.js │ │ └── views │ │ │ └── activities.html │ └── server │ │ ├── routes │ │ └── activities.js │ │ ├── main.js │ │ ├── models │ │ └── activities.js │ │ └── controllers │ │ └── activities.js ├── users │ ├── public │ │ ├── views │ │ │ ├── activating.html │ │ │ ├── user-list.html │ │ │ ├── users-dialog.html │ │ │ ├── users-pwd-dialog.html │ │ │ ├── change-password.html │ │ │ ├── users-invite-dialog.html │ │ │ ├── login.html │ │ │ └── profile.html │ │ ├── users.js │ │ ├── users.services.js │ │ └── users.routes.js │ └── server │ │ ├── main.js │ │ ├── tests │ │ └── users.test.js │ │ ├── routes │ │ └── users.js │ │ └── models │ │ └── users.js └── notifications │ └── public │ ├── views │ └── activities.html │ ├── notifications.js │ ├── notifications.services.js │ └── notifications.controllers.js ├── .travis.yml ├── tools └── test │ ├── mocha-req.js │ └── assets.json ├── index.js ├── .gitignore ├── bower.json ├── LICENSE ├── package.json ├── README.md ├── gulpfile.js └── karma.conf.js /Procfile: -------------------------------------------------------------------------------- 1 | web: npm start -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "public/bower_components" 3 | } -------------------------------------------------------------------------------- /fast-typing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ritenv/atwork/HEAD/fast-typing.gif -------------------------------------------------------------------------------- /public/images/user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ritenv/atwork/HEAD/public/images/user.jpg -------------------------------------------------------------------------------- /public/images/spacer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ritenv/atwork/HEAD/public/images/spacer.gif -------------------------------------------------------------------------------- /system/public/settings/settings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.settings', []); -------------------------------------------------------------------------------- /modules/chats/public/chats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.chats', ['atwork.system']); -------------------------------------------------------------------------------- /modules/posts/public/posts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.posts', ['atwork.system']); -------------------------------------------------------------------------------- /public/images/preloader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ritenv/atwork/HEAD/public/images/preloader.gif -------------------------------------------------------------------------------- /modules/streams/public/streams.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.streams', ['atwork.system']); -------------------------------------------------------------------------------- /modules/activities/public/activities.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.activities', ['atwork.system']); -------------------------------------------------------------------------------- /modules/users/public/views/activating.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Activating...

4 |
5 |
6 | -------------------------------------------------------------------------------- /modules/activities/public/activities.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.activities') 4 | .config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) { 5 | 6 | $locationProvider.html5Mode(true); 7 | }]); -------------------------------------------------------------------------------- /modules/chats/public/chats.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.chats') 4 | .config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) { 5 | $routeProvider 6 | ; 7 | $locationProvider.html5Mode(true); 8 | }]); -------------------------------------------------------------------------------- /modules/activities/public/activities.services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.activities') 4 | .factory('appActivities', ['$resource', 5 | function($resource) { 6 | return $resource('activities/feed/:userId', { 7 | userId: '@_id' 8 | }); 9 | } 10 | ]) 11 | ; 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "0.10" 5 | 6 | before_install: 7 | - npm install -g gulp 8 | - npm install -g bower 9 | 10 | install: 11 | - npm install 12 | - bower cache clean 13 | - bower install 14 | 15 | before_script: 16 | - export DISPLAY=:99.0 17 | - sh -e /etc/init.d/xvfb start 18 | 19 | services: 20 | - mongodb 21 | -------------------------------------------------------------------------------- /tools/test/mocha-req.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | process.env.NODE_ENV = 'development'; 4 | var appRoot = __dirname + '/../../'; 5 | global.System = require(appRoot + 'index'); 6 | 7 | /** 8 | * Mock event plugin 9 | * @type {Object} 10 | */ 11 | global.System.plugins.event = { 12 | on: function() { 13 | return true; 14 | }, 15 | trigger: function() { 16 | return true; 17 | } 18 | }; -------------------------------------------------------------------------------- /system/public/settings/settings.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.settings') 4 | .config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) { 5 | $routeProvider 6 | .when('/settings', { 7 | templateUrl: '/system/settings/views/settings.html', 8 | controller: 'SettingsCtrl' 9 | }) 10 | ; 11 | $locationProvider.html5Mode(true); 12 | }]); -------------------------------------------------------------------------------- /modules/chats/server/main.js: -------------------------------------------------------------------------------- 1 | var routes = require('./routes/chats'); 2 | 3 | module.exports = function(System) { 4 | /** 5 | * Name of this module 6 | * @type {String} 7 | */ 8 | var moduleName = 'chats'; 9 | 10 | /** 11 | * Build the routes 12 | * @type {Array} 13 | */ 14 | var builtRoutes = routes(System); 15 | 16 | /** 17 | * Add routes to System 18 | */ 19 | System.route(builtRoutes, moduleName); 20 | }; -------------------------------------------------------------------------------- /modules/posts/server/main.js: -------------------------------------------------------------------------------- 1 | var routes = require('./routes/posts'); 2 | 3 | module.exports = function(System) { 4 | /** 5 | * Name of this module 6 | * @type {String} 7 | */ 8 | var moduleName = 'posts'; 9 | 10 | /** 11 | * Build the routes 12 | * @type {Array} 13 | */ 14 | var builtRoutes = routes(System); 15 | 16 | /** 17 | * Add routes to System 18 | */ 19 | System.route(builtRoutes, moduleName); 20 | }; -------------------------------------------------------------------------------- /modules/users/server/main.js: -------------------------------------------------------------------------------- 1 | var routes = require('./routes/users'); 2 | 3 | module.exports = function(System) { 4 | /** 5 | * Name of this module 6 | * @type {String} 7 | */ 8 | var moduleName = 'users'; 9 | 10 | /** 11 | * Build the routes 12 | * @type {Array} 13 | */ 14 | var builtRoutes = routes(System); 15 | 16 | /** 17 | * Add routes to System 18 | */ 19 | System.route(builtRoutes, moduleName); 20 | }; -------------------------------------------------------------------------------- /modules/activities/server/routes/activities.js: -------------------------------------------------------------------------------- 1 | var myController = require('../controllers/activities'); 2 | /** 3 | * Init the controller 4 | */ 5 | module.exports = function(System) { 6 | var activities = myController(System); 7 | 8 | var routes = []; 9 | 10 | routes.push({ 11 | method: 'get', 12 | path: '/feed/:userId', 13 | handler: activities.feed, 14 | authorized: true 15 | }); 16 | 17 | return routes; 18 | }; -------------------------------------------------------------------------------- /system/models/settings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var mongoose = require('mongoose'), 7 | Schema = mongoose.Schema, 8 | crypto = require('crypto'), 9 | _ = require('lodash'); 10 | 11 | /** 12 | * Activity Schema 13 | */ 14 | 15 | var SettingsSchema = new Schema({ 16 | name: String, 17 | value: String 18 | }); 19 | 20 | 21 | mongoose.model('settings', SettingsSchema); 22 | -------------------------------------------------------------------------------- /modules/streams/server/main.js: -------------------------------------------------------------------------------- 1 | var routes = require('./routes/streams'); 2 | 3 | module.exports = function(System) { 4 | /** 5 | * Name of this module 6 | * @type {String} 7 | */ 8 | var moduleName = 'streams'; 9 | 10 | /** 11 | * Build the routes 12 | * @type {Array} 13 | */ 14 | var builtRoutes = routes(System); 15 | 16 | /** 17 | * Add routes to System 18 | */ 19 | System.route(builtRoutes, moduleName); 20 | }; -------------------------------------------------------------------------------- /system/public/views/search.html: -------------------------------------------------------------------------------- 1 |
2 | 9 | {{item.name}} / @{{item.username}} 10 | 11 |
12 | -------------------------------------------------------------------------------- /modules/activities/server/main.js: -------------------------------------------------------------------------------- 1 | var routes = require('./routes/activities'); 2 | 3 | module.exports = function(System) { 4 | /** 5 | * Name of this module 6 | * @type {String} 7 | */ 8 | var moduleName = 'activities'; 9 | 10 | /** 11 | * Build the routes 12 | * @type {Array} 13 | */ 14 | var builtRoutes = routes(System); 15 | 16 | /** 17 | * Add routes to System 18 | */ 19 | System.route(builtRoutes, moduleName); 20 | }; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /********************************** 2 | | | 3 | ,---.|--- . . .,---.,---.|__/ 4 | ,---|| | | || || | \ 5 | `---^`---' `-'-'`---'` ` ` 6 | **********************************/ 7 | 8 | /** 9 | * Load the system 10 | */ 11 | var System = require('./system'); 12 | 13 | /** 14 | * Boot up 15 | */ 16 | System.boot(); 17 | 18 | /** 19 | * Export the module 20 | * @type {Object} 21 | */ 22 | module.exports = System; -------------------------------------------------------------------------------- /modules/users/public/views/user-list.html: -------------------------------------------------------------------------------- 1 | 2 | Actions 3 | 4 | 5 | 6 | 7 | 8 | {{ item.name }} 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /modules/chats/public/chats.services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.chats') 4 | .factory('appChats', ['$resource', 5 | function($resource) { 6 | return { 7 | single: $resource('chats/:chatId/:action', { 8 | chatId: '@_id' 9 | }, { 10 | message: { 11 | method: 'POST', 12 | params: {action: 'message'} 13 | }, 14 | end: { 15 | method: 'POST', 16 | params: {action: 'end'} 17 | } 18 | }) 19 | } 20 | } 21 | ]) 22 | ; 23 | -------------------------------------------------------------------------------- /system/config/production.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | REQUESTS_DELAY: 0, 5 | REQUESTS_DELAY_SYSTEM: 0, 6 | baseURL: (process.env.BASEURL || 'http://atwork.riten.io'), 7 | db: 'mongodb://localhost:27017/' + (process.env.DB || 'atwork'), 8 | server: { 9 | host: 'localhost', 10 | port: process.env.PORT || 8111 11 | }, 12 | secret: 'atworksecret', 13 | settings: { 14 | perPage: 10, 15 | email: { 16 | service: 'Gmail' 17 | } 18 | }, 19 | aws: { 20 | accessKeyId: process.env.AWS_ACCESS_KEY_ID, 21 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY 22 | } 23 | }; -------------------------------------------------------------------------------- /modules/streams/public/streams.services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.streams') 4 | .factory('appStreams', ['$resource', 5 | function($resource) { 6 | return { 7 | single: $resource('streams/:streamId/:action', { 8 | streamId: '@_id' 9 | }, { 10 | subscribe: { 11 | method: 'POST', 12 | params: {action: 'subscribe'} 13 | }, 14 | unsubscribe: { 15 | method: 'POST', 16 | params: {action: 'unsubscribe'} 17 | } 18 | }) 19 | } 20 | } 21 | ]) 22 | ; 23 | -------------------------------------------------------------------------------- /system/config/development.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | console.log('Config in development'); 3 | module.exports = { 4 | REQUESTS_DELAY: 0, 5 | REQUESTS_DELAY_SYSTEM: 0, 6 | baseURL: 'http://localhost:8111', 7 | db: process.env.MONGOHQ_URL || 'mongodb://' + (process.env.DB_PORT_27017_TCP_ADDR || 'localhost') + '/atwork', 8 | server: { 9 | host: 'localhost', 10 | port: 8111 11 | }, 12 | secret: 'atworksecret', 13 | settings: { 14 | perPage: 10, 15 | email: { 16 | service: 'Gmail' 17 | } 18 | }, 19 | aws: { 20 | accessKeyId: process.env.AWS_ACCESS_KEY_ID, 21 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY 22 | } 23 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | public/bower_components 28 | public/uploads 29 | 30 | # Users Environment Variables 31 | .lock-wscript 32 | 33 | /**/.DS_Store -------------------------------------------------------------------------------- /system/routes/search.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var User = mongoose.model('User'); 3 | 4 | /** 5 | * Init the controller 6 | */ 7 | module.exports = function(System) { 8 | 9 | var routes = []; 10 | var json = System.plugins.JSON; 11 | 12 | routes.push({ 13 | method: 'get', 14 | path: '/search/:keyword', 15 | authorized: true, 16 | handler: function(req, res) { 17 | var keyword = req.param('keyword'); 18 | User 19 | .find({name: new RegExp(keyword, 'ig')}, null, {sort: {name: 1}}) 20 | .lean() 21 | .exec(function(err, items) { 22 | if (err) { 23 | return json.unhappy(err, res); 24 | } 25 | return json.happy({ items: items }, res); 26 | }); 27 | } 28 | }); 29 | return routes; 30 | }; -------------------------------------------------------------------------------- /modules/chats/server/routes/chats.js: -------------------------------------------------------------------------------- 1 | var myController = require('../controllers/chats'); 2 | /** 3 | * Init the controller 4 | */ 5 | module.exports = function(System) { 6 | var chats = myController(System); 7 | 8 | var routes = []; 9 | 10 | routes.push({ 11 | method: 'post', 12 | path: '/', 13 | handler: chats.create, 14 | authorized: true 15 | }); 16 | 17 | routes.push({ 18 | method: 'get', 19 | path: '/', 20 | handler: chats.list, 21 | authorized: true 22 | }); 23 | 24 | routes.push({ 25 | method: 'get', 26 | path: '/:chatId', 27 | handler: chats.single, 28 | authorized: true 29 | }); 30 | 31 | routes.push({ 32 | method: 'post', 33 | path: '/:chatId/message', 34 | handler: chats.message, 35 | authorized: true 36 | }); 37 | 38 | return routes; 39 | }; -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atwork", 3 | "main": "index.js", 4 | "version": "0.0.0", 5 | "homepage": "https://github.com/matterial/atwork", 6 | "authors": [ 7 | "Riten Vagadiya " 8 | ], 9 | "moduleType": [ 10 | "node" 11 | ], 12 | "license": "MIT", 13 | "private": true, 14 | "ignore": [ 15 | "**/.*", 16 | "node_modules", 17 | "public/bower_components", 18 | "test", 19 | "tests" 20 | ], 21 | "dependencies": { 22 | "q": "1.0.1", 23 | "fontawesome": "~4.3.0", 24 | "ng-file-upload": "~3.0.2", 25 | "socket.io-client": "~1.3.5", 26 | "jquery": "~2.1.3", 27 | "lodash": "~3.8.0", 28 | "angular": "~1.3.15", 29 | "angular-material": "~0.9.0", 30 | "angular-route": "~1.3.15", 31 | "angular-messages": "~1.3.15", 32 | "angular-resource": "~1.3.15", 33 | "angular-loading-bar": "~0.7.1", 34 | "angular-mocks": "~1.3.15" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tools/test/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "core": { 3 | "css": { 4 | "public/css/main.css": [] 5 | }, 6 | "js": { 7 | "public/bower_components/build/js/dist.min.js": [ 8 | "public/bower_components/angular/angular.js", 9 | "public/bower_components/angular-aria/angular-aria.js", 10 | "public/bower_components/angular-animate/angular-animate.js", 11 | "public/bower_components/angular-material/angular-material.js", 12 | "public/bower_components/angular-route/angular-route.js", 13 | "public/bower_components/angular-messages/angular-messages.js", 14 | "public/bower_components/angular-resource/angular-resource.js", 15 | "public/bower_components/q/q.js", 16 | "public/bower_components/ng-file-upload/angular-file-upload.js", 17 | "public/bower_components/angular-loading-bar/build/loading-bar.min.js", 18 | "public/bower_components/angular-mocks/angular-mocks.js" 19 | ] 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /modules/activities/public/activities.controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.activities') 4 | .controller('ActivitiesCtrl', [ 5 | '$scope', 6 | '$rootScope', 7 | '$routeParams', 8 | '$timeout', 9 | 'appPosts', 10 | 'appActivities', 11 | 'appAuth', 12 | 'appToast', 13 | 'appStorage', 14 | 'appLocation', 15 | 'appWebSocket', 16 | function($scope, $rootScope, $routeParams, $timeout, appPosts, appActivities, appAuth, appToast, appStorage, appLocation, appWebSocket) { 17 | $scope.lastUpdated = 0; 18 | $scope.newActivitiesCount = 0; 19 | $scope.actions = []; 20 | var userId = $routeParams.userId; 21 | 22 | var activitiesData = appActivities.get({userId: userId, timestamp: $scope.lastUpdated}, function() { 23 | $scope.actions = activitiesData.res.records ? activitiesData.res.records.concat($scope.actions) : $scope.actions; 24 | $scope.lastUpdated = Date.now(); 25 | }); 26 | } 27 | ]) 28 | ; 29 | -------------------------------------------------------------------------------- /modules/streams/public/streams.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.streams') 4 | .config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) { 5 | $routeProvider 6 | .when('/stream/:streamId', { 7 | templateUrl: '/modules/posts/views/feed.html', 8 | controller: 'PostsCtrl', 9 | resolve: { 10 | resolvedFeeds: [ 11 | '$route', 12 | 'appPostsFeed', 13 | function($route, appPostsFeed) { 14 | var deferred = Q.defer(); 15 | var options = angular.extend({ 16 | limitComments: true 17 | }, $route.current.params); 18 | 19 | appPostsFeed.getFeeds(options, function(response) { 20 | deferred.resolve(response); 21 | }); 22 | 23 | return deferred.promise; 24 | } 25 | ] 26 | } 27 | }) 28 | ; 29 | $locationProvider.html5Mode(true); 30 | }]); -------------------------------------------------------------------------------- /modules/users/public/views/users-dialog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Likers 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{item.name}} 11 |
12 |

{{item.name}}

13 |

@{{item.username}}

14 |

{{item.designation}}

15 |
16 |
17 |
18 | 19 |
20 |
21 | 22 |
23 |
24 | 25 | 26 | Close 27 | 28 |
29 |
-------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Riten Vagadiya 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /modules/activities/public/views/activities.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | Load New Items ({{newActivitiesCount}}) 6 | 7 | 8 | 9 |

10 | {{::item.created | date:'short'}} 11 | 12 | 13 | Liked this 14 | Removed my like from this 15 | Commented on this 16 | Written a new 17 | Post. 18 | 19 |

20 | 21 |
22 |
23 |
24 |
-------------------------------------------------------------------------------- /modules/users/public/users.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.users', ['atwork.system']) 4 | .factory('appAuth', [ 5 | '$http', 6 | 'appStorage', 7 | function($http, appStorage) { 8 | return { 9 | isLoggedIn: function() { 10 | return appStorage.get('userToken'); 11 | }, 12 | getToken: function() { 13 | return appStorage.get('userToken'); 14 | }, 15 | refreshUser: function(cb) { 16 | /** 17 | * FIXME: convert this to an ngResource call 18 | */ 19 | $http.get('/users/me').success(function(response) { 20 | var serializedUser = angular.toJson(response.res.record); 21 | appStorage.set('user', serializedUser); 22 | cb(response.res.record); 23 | }); 24 | }, 25 | getUser: function() { 26 | var serialized = appStorage.get('user'); 27 | if (serialized) { 28 | return angular.fromJson(serialized); 29 | } else { 30 | return { 31 | name: 'unknown' 32 | }; 33 | } 34 | } 35 | } 36 | } 37 | ]); 38 | -------------------------------------------------------------------------------- /modules/chats/public/views/chatsList.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | Chats 4 |

5 | 6 | 18 |

Loading...

19 |

No chats yet

20 |
-------------------------------------------------------------------------------- /modules/notifications/public/views/activities.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | Load New Items ({{newActivitiesCount}}) 6 | 7 | 8 | 9 |

10 | {{::item.created | date:'short'}} 11 | 12 | I have 13 | liked this 14 | removed my like from this 15 | commented on this 16 | written a new 17 | Post. 18 | 19 |

20 | 21 |
22 |
23 |
24 |
-------------------------------------------------------------------------------- /system/helpers/emailing.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var _ = require('lodash'); 3 | 4 | /** 5 | * Unhappy JSON response 6 | * @param {Error} err The error object 7 | * @return {Object} The json response to be output 8 | */ 9 | var generate = function(options, cb) { 10 | var emailTemplate = fs.readFile(options.template ? options.template : (__dirname + '/../public/templates/notification.html'), function(err, fileContent) { 11 | var ejs = require('ejs'); 12 | var html = ejs.render(fileContent.toString(), options); 13 | cb(html); 14 | }); 15 | }; 16 | 17 | module.exports = function(System) { 18 | var plugin = { 19 | /** 20 | * The helper register method 21 | * @return {Void} 22 | */ 23 | register: function () { 24 | return { 25 | generate: function(options, cb) { 26 | options = _.extend(options, System.settings); 27 | return generate(options, cb); 28 | } 29 | }; 30 | } 31 | }; 32 | 33 | /** 34 | * Attributes to identify the plugin 35 | * @type {Object} 36 | */ 37 | plugin.register.attributes = { 38 | name: 'Email Helper', 39 | key: 'emailing', 40 | version: '1.0.0' 41 | }; 42 | return plugin; 43 | }; -------------------------------------------------------------------------------- /system/public/settings/settings.controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.settings') 4 | .controller('SettingsCtrl', [ 5 | '$scope', 6 | '$rootScope', 7 | '$routeParams', 8 | '$timeout', 9 | 'appPosts', 10 | 'appAuth', 11 | 'appToast', 12 | 'appStorage', 13 | 'appLocation', 14 | 'appWebSocket', 15 | 'appUsersSearch', 16 | 'appSettings', 17 | function($scope, $rootScope, $routeParams, $timeout, appPosts, appAuth, appToast, appStorage, appLocation, appWebSocket, appUsersSearch, appSettings) { 18 | /** 19 | * Refresh settings from API 20 | */ 21 | appSettings.fetch(function(settings) { 22 | $scope.systemSettings = settings; 23 | }); 24 | 25 | if (appAuth.getUser().roles.indexOf('admin') === -1) { 26 | appToast('Only an Administrator can change system\'s settings.'); 27 | appLocation.url('/'); 28 | } 29 | 30 | $scope.save = function(isValid) { 31 | var req = new appSettings.single($scope.systemSettings); 32 | req.$save(function(res) { 33 | if (res.success) { 34 | appToast('Your settings are saved.'); 35 | appLocation.url('/'); 36 | } 37 | }); 38 | }; 39 | } 40 | ]) 41 | ; 42 | -------------------------------------------------------------------------------- /modules/streams/server/routes/streams.js: -------------------------------------------------------------------------------- 1 | var myController = require('../controllers/streams'); 2 | /** 3 | * Init the controller 4 | */ 5 | module.exports = function(System) { 6 | var streams = myController(System); 7 | 8 | var routes = []; 9 | 10 | routes.push({ 11 | method: 'post', 12 | path: '/', 13 | handler: streams.create, 14 | authorized: true 15 | }); 16 | 17 | routes.push({ 18 | method: 'get', 19 | path: '/', 20 | handler: streams.list, 21 | authorized: true 22 | }); 23 | 24 | routes.push({ 25 | method: 'get', 26 | path: '/:streamId', 27 | handler: streams.single, 28 | authorized: true 29 | }); 30 | 31 | routes.push({ 32 | method: 'post', 33 | path: '/:streamId', 34 | handler: streams.modify, 35 | authorized: true 36 | }); 37 | 38 | routes.push({ 39 | method: 'post', 40 | path: '/:streamId/subscribe', 41 | handler: streams.subscribe, 42 | authorized: true 43 | }); 44 | 45 | routes.push({ 46 | method: 'post', 47 | path: '/:streamId/unsubscribe', 48 | handler: streams.unsubscribe, 49 | authorized: true 50 | }); 51 | 52 | routes.push({ 53 | method: 'delete', 54 | path: '/:streamId', 55 | handler: streams.remove, 56 | authorized: true 57 | }); 58 | 59 | return routes; 60 | }; -------------------------------------------------------------------------------- /system/helpers/events.js: -------------------------------------------------------------------------------- 1 | var registeredEvents = {}; 2 | var registerType = function(e) { 3 | if (typeof registeredEvents[e] !== 'object' || registeredEvents[e].constructor.name !== 'Array') { 4 | registeredEvents[e] = []; 5 | } 6 | }; 7 | var isRegisteredEvent = function(e) { 8 | return (typeof registeredEvents[e] === 'object' && registeredEvents[e].constructor.name === 'Array'); 9 | }; 10 | var registerEvent = function(e, cb) { 11 | registeredEvents[e].push(cb); 12 | }; 13 | var triggerEvent = function(e, args) { 14 | registeredEvents[e].map(function(cb) { 15 | cb(args); 16 | }); 17 | }; 18 | module.exports = function(System) { 19 | var plugin = { 20 | /** 21 | * The helper register method 22 | * @return {Void} 23 | */ 24 | register: function () { 25 | return { 26 | on: function(e, cb, args) { 27 | registerType(e); 28 | registerEvent(e, cb, args); 29 | }, 30 | trigger: function(e, args) { 31 | if (isRegisteredEvent(e)) { 32 | triggerEvent(e, args); 33 | } 34 | } 35 | }; 36 | } 37 | }; 38 | 39 | /** 40 | * Attributes to identify the plugin 41 | * @type {Object} 42 | */ 43 | plugin.register.attributes = { 44 | name: 'Events Helper', 45 | key: 'event', 46 | version: '1.0.0' 47 | }; 48 | return plugin; 49 | }; -------------------------------------------------------------------------------- /modules/activities/server/models/activities.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var mongoose = require('mongoose'), 7 | Schema = mongoose.Schema, 8 | crypto = require('crypto'), 9 | _ = require('lodash'); 10 | 11 | /** 12 | * Getter 13 | */ 14 | var escapeProperty = function(value) { 15 | return _.escape(value); 16 | }; 17 | 18 | /** 19 | * Activity Schema 20 | */ 21 | 22 | var ActivitySchema = new Schema({ 23 | created: { 24 | type: Date, 25 | default: Date.now 26 | }, 27 | actor: { 28 | type: Schema.ObjectId, 29 | required: true, 30 | ref: 'User' 31 | }, 32 | post: { 33 | type: Schema.ObjectId, 34 | required: false, 35 | ref: 'Post' 36 | }, 37 | action: { 38 | type: String, 39 | required: true, 40 | get: escapeProperty 41 | } 42 | }); 43 | 44 | /** 45 | * Methods 46 | */ 47 | ActivitySchema.methods = { 48 | /** 49 | * Hide security sensitive fields 50 | * 51 | * @returns {*|Array|Binary|Object} 52 | */ 53 | toJSON: function() { 54 | var obj = this.toObject(); 55 | if (obj.actor) { 56 | delete obj.actor.token; 57 | delete obj.actor.hashed_password; 58 | delete obj.actor.salt; 59 | delete obj.actor.following; 60 | } 61 | return obj; 62 | } 63 | }; 64 | 65 | mongoose.model('Activity', ActivitySchema); 66 | -------------------------------------------------------------------------------- /system/public/settings/settings.services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.settings') 4 | .factory('appSettings', [ 5 | '$resource', 6 | '$rootScope', 7 | function($resource, $rootScope) { 8 | return { 9 | cache: {}, 10 | single: $resource('system-settings/'), 11 | fetch: function(cb) { 12 | var $this = this; 13 | var settings = $this.single.get({}, function() { 14 | for (var i in settings.res.items) { 15 | var setting = settings.res.items[i]; 16 | $this.cache[setting.name] = setting.value; 17 | } 18 | $rootScope.systemSettings = $this.cache; 19 | return cb ? cb($this.cache) : true; 20 | }); 21 | } 22 | } 23 | } 24 | ]) 25 | .factory('appSettingsValid', [ 26 | 'appSettings', 27 | 'appLocation', 28 | '$rootScope', 29 | function(appSettings, appLocation, $rootScope) { 30 | return function() { 31 | if (appLocation.url() !== '/settings' && appLocation.url() !== '/logout') { 32 | if (!$rootScope.systemSettings || !$rootScope.systemSettings.domains || !$rootScope.systemSettings.workplace) { 33 | appLocation.url('/settings'); 34 | return false; 35 | } 36 | } 37 | return true; 38 | } 39 | } 40 | ]) 41 | 42 | ; 43 | -------------------------------------------------------------------------------- /system/helpers/json.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Happy JSON response 3 | * @param {Object} obj The info object 4 | * @return {Object} The json response to be output 5 | */ 6 | var happy = function(obj, res) { 7 | res.send({ 8 | success: 1, 9 | res: obj 10 | }); 11 | }; 12 | 13 | var list = function(obj, res) { 14 | res.send(obj); 15 | }; 16 | 17 | /** 18 | * Unhappy JSON response 19 | * @param {Error} err The error object 20 | * @return {Object} The json response to be output 21 | */ 22 | var unhappy = function(err, res) { 23 | var obj = { 24 | success: 0, 25 | res: err 26 | }; 27 | if (obj.res.errors) { 28 | obj.res.messages = []; 29 | for (var i in obj.res.errors) { 30 | obj.res.messages.push(obj.res.errors[i].message); 31 | } 32 | obj.res.message = obj.res.messages[0]; 33 | } 34 | res.send(obj); 35 | }; 36 | 37 | 38 | 39 | module.exports = function(System) { 40 | var plugin = { 41 | /** 42 | * The helper register method 43 | * @return {Void} 44 | */ 45 | register: function () { 46 | return { 47 | happy: happy, 48 | list: list, 49 | unhappy: unhappy 50 | }; 51 | } 52 | }; 53 | 54 | /** 55 | * Attributes to identify the plugin 56 | * @type {Object} 57 | */ 58 | plugin.register.attributes = { 59 | name: 'JSON Helper', 60 | key: 'JSON', 61 | version: '1.0.0' 62 | }; 63 | return plugin; 64 | }; -------------------------------------------------------------------------------- /modules/users/public/views/users-pwd-dialog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Reset Password 4 | 5 |
6 |
7 | 8 | 9 | 10 |
11 |
This is required!
12 |
That's too long!
13 |
That's too short!
14 |
15 |
16 | 17 | 18 | Next 19 | 20 |
21 | 22 |
23 |

24 | 25 | Awesome! 26 |

27 |

Check your email for password reset instructions.

28 |
29 | 30 |
31 | 32 |
33 |
34 | 35 | 36 | Close 37 | 38 |
39 |
-------------------------------------------------------------------------------- /system/helpers/notifications.js: -------------------------------------------------------------------------------- 1 | module.exports = function(System) { 2 | var sck = System.webSocket; 3 | var plugin = { 4 | /** 5 | * The helper register method 6 | * @return {Void} 7 | */ 8 | register: function () { 9 | return { 10 | send: function(socketId, data) { 11 | console.log('NOTIFICATION FOR:', socketId); 12 | var type = data.config.systemLevel ? 'system' : 'notification'; 13 | sck.to(socketId).emit(type, data); 14 | }, 15 | sendByEmail: function(user, data) { 16 | var mailOptions = { 17 | from: data.actor.name + ' (' + data.notificationType + ')' + ' via ' + System.settings.workplace + ' <'+ System.settings.email +'>', // sender address 18 | to: data.to ? data.to : user.email, // list of receivers 19 | subject: data.subject ? data.subject : 'Notification', // Subject line 20 | html: data.html 21 | }; 22 | 23 | System.mailer.sendMail(mailOptions, function(error, info){ 24 | if (error) { 25 | console.log(error); 26 | } else { 27 | console.log('Message sent: ' + info.response); 28 | } 29 | }); 30 | }, 31 | }; 32 | } 33 | }; 34 | 35 | /** 36 | * Attributes to identify the plugin 37 | * @type {Object} 38 | */ 39 | plugin.register.attributes = { 40 | name: 'Notifications Helper', 41 | key: 'notifications', 42 | version: '1.0.0' 43 | }; 44 | return plugin; 45 | }; -------------------------------------------------------------------------------- /modules/notifications/public/notifications.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | angular.module('atwork.notifications', ['atwork.system']) 3 | .run([ 4 | '$rootScope', 5 | 'appLocation', 6 | 'appNotification', 7 | 'appWebSocket', 8 | 'appNotificationText', 9 | function($rootScope, appLocation, appNotification, appWebSocket, appNotificationText) { 10 | appWebSocket.conn.on('notification', function (data) { 11 | /** 12 | * Broadcast the notification to the application 13 | */ 14 | $rootScope.$broadcast('notification', data); 15 | $rootScope.$broadcast(data.notificationType, data); 16 | 17 | /** 18 | * No data will be received if it is just a notification update signal 19 | */ 20 | if (!data) return; 21 | 22 | /** 23 | * Prepare to show the notification 24 | */ 25 | data.message = appNotificationText(data).text; 26 | 27 | data.then = function () { 28 | if (data.postId) { 29 | appLocation.url('/post/' + data.postId); 30 | } else if (data.userId) { 31 | appLocation.url('/profile/' + data.actor.username); 32 | } 33 | }; 34 | 35 | appNotification.show(data); 36 | }); 37 | 38 | /** 39 | * A system level notification is only 40 | * for broadcasting to the application 41 | */ 42 | appWebSocket.conn.on('system', function (data) { 43 | /** 44 | * Broadcast the notification to the application 45 | */ 46 | $rootScope.$broadcast(data.notificationType, data); 47 | }); 48 | } 49 | ]); -------------------------------------------------------------------------------- /modules/posts/server/routes/posts.js: -------------------------------------------------------------------------------- 1 | var myController = require('../controllers/posts'); 2 | /** 3 | * Init the controller 4 | */ 5 | module.exports = function(System) { 6 | var posts = myController(System); 7 | 8 | var routes = []; 9 | 10 | routes.push({ 11 | method: 'post', 12 | path: '/', 13 | handler: posts.create, 14 | authorized: true 15 | }); 16 | 17 | routes.push({ 18 | method: 'get', 19 | path: '/', 20 | handler: posts.feed, 21 | authorized: true 22 | }); 23 | 24 | routes.push({ 25 | method: 'get', 26 | path: '/timeline/:userId', 27 | handler: posts.timeline, 28 | authorized: true 29 | }); 30 | 31 | routes.push({ 32 | method: 'get', 33 | path: '/stream/:streamId', 34 | handler: posts.streamPosts, 35 | authorized: true 36 | }); 37 | 38 | routes.push({ 39 | method: 'get', 40 | path: '/:postId', 41 | handler: posts.single, 42 | authorized: true 43 | }); 44 | 45 | routes.push({ 46 | method: 'post', 47 | path: '/:postId/like', 48 | handler: posts.like, 49 | authorized: true 50 | }); 51 | 52 | routes.push({ 53 | method: 'post', 54 | path: '/:postId/comment', 55 | handler: posts.comment, 56 | authorized: true 57 | }); 58 | 59 | routes.push({ 60 | method: 'get', 61 | path: '/:postId/likes', 62 | handler: posts.likes, 63 | authorized: true 64 | }); 65 | 66 | routes.push({ 67 | method: 'post', 68 | path: '/:postId/unlike', 69 | handler: posts.unlike, 70 | authorized: true 71 | }); 72 | 73 | return routes; 74 | }; -------------------------------------------------------------------------------- /modules/users/public/views/change-password.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Please wait...

4 |
5 | 6 |

New Password

7 |
8 | 9 | 10 | 11 |
12 |
This is required!
13 |
That's too long!
14 |
That's too short!
15 |
16 |
17 | 18 | 19 | 20 | 21 |
22 |
This is required!
23 |
That's too long!
24 |
That's too short!
25 |
26 |
27 | 28 | Change 29 | 30 |
31 |
32 |
33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AtWork", 3 | "version": "0.0.1", 4 | "description": "An open-source social network for your workplace", 5 | "repository": "https://github.com/matterial/atwork", 6 | "main": "index.js", 7 | "scripts": { 8 | "start": "node index", 9 | "dev": "node index", 10 | "karma": "node node_modules/karma/bin/karma start karma.conf.js", 11 | "mocha": "./node_modules/.bin/istanbul cover --report html node_modules/.bin/_mocha -- ./modules/**/server/tests/**/*.js -R spec -r tools/test/mocha-req.js", 12 | "test": "npm run mocha && npm run karma", 13 | "postinstall": "./node_modules/bower/bin/bower install" 14 | }, 15 | "author": "Riten Vagadiya", 16 | "license": "MIT", 17 | "dependencies": { 18 | "async": "^0.9.2", 19 | "aws-sdk": "^2.1.28", 20 | "body-parser": "^1.11.0", 21 | "bower": "^1.3.12", 22 | "ejs": "^2.3.1", 23 | "express": "^4.11.2", 24 | "jsonwebtoken": "^3.2.2", 25 | "lodash": "^3.9.3", 26 | "mongoose": "3.8", 27 | "morgan": "^1.5.1", 28 | "multer": "^0.1.7", 29 | "nodemailer": "^1.3.2", 30 | "socket.io": "^1.3.5" 31 | }, 32 | "devDependencies": { 33 | "chai": "^1.10.0", 34 | "expect": "^1.6.0", 35 | "gulp": "^3.8.10", 36 | "gulp-concat": "^2.5.2", 37 | "gulp-minify-css": "^1.1.3", 38 | "gulp-sass": "^1.3.2", 39 | "gulp-uglify": "^1.2.0", 40 | "istanbul": "^0.3.7", 41 | "karma": "^0.12.31", 42 | "karma-coverage": "^0.2.7", 43 | "karma-mocha": "^0.1.10", 44 | "karma-phantomjs-launcher": "^0.1.4", 45 | "mocha": "^2.1.0", 46 | "phantomjs": "^1.9.15" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /modules/chats/public/views/chat-dialog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Chat 4 | 5 |
6 | 7 | 8 | 9 |
10 |
This is required!
11 |
That's too long!
12 |
That's too short!
13 |
14 |
15 |
16 | 17 | 18 | 19 | 20 | {{item.creator.name}} 21 |
22 |

{{item.creator.name}} {{item.created | date:'short'}}

23 |

{{item.message}}

24 |
25 |
26 |
27 |
28 | 29 |
30 |
31 | 32 | 33 | Close 34 | 35 |
36 |
-------------------------------------------------------------------------------- /modules/users/server/tests/users.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Load Prerequisites 5 | */ 6 | var expect = require('chai').expect, 7 | mongoose = require('mongoose'), 8 | User = mongoose.model('User') 9 | ; 10 | 11 | /** 12 | * This will hold all temporarily added records, which will be deleted at the end of the tests 13 | * @type {Object} 14 | */ 15 | var temps = {}; 16 | 17 | describe('', function() { 18 | describe('Model User:', function() { 19 | beforeEach(function(done) { 20 | temps = {}; 21 | /** 22 | * Create a new test user 23 | * @type {User} 24 | */ 25 | temps.user = new User({ 26 | name: 'John Doe', 27 | email: 'test@asdf.com', 28 | username: 'test123', 29 | password: 'password', 30 | provider: 'local', 31 | roles: ['authenticated'] 32 | }); 33 | 34 | done(); 35 | }); 36 | 37 | /** 38 | * Save comment 39 | */ 40 | describe('Method Save', function() { 41 | 42 | it('should be able to save a new user', function(done) { 43 | temps.user.save(function(err, user) { 44 | expect(user.name).to.be.equal('John Doe'); 45 | done(); 46 | }); 47 | }); 48 | 49 | }); 50 | 51 | 52 | /** 53 | * Clean up 54 | */ 55 | afterEach(function(done) { 56 | var remove = function(item, ok) { 57 | if (item && typeof item.remove === "function") { 58 | item.remove(ok); 59 | } else { 60 | ok(); 61 | } 62 | }; 63 | remove(temps.user, function() { 64 | done(); 65 | }); 66 | }); 67 | 68 | }); 69 | }); 70 | 71 | -------------------------------------------------------------------------------- /modules/users/public/users.services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.users') 4 | .factory('appUsers', ['$resource', 5 | function($resource) { 6 | return { 7 | single: $resource('users/:userId/:action', { 8 | userId: '@_id' 9 | }, { 10 | update: { 11 | method: 'PUT' 12 | }, 13 | follow: { 14 | method: 'POST', 15 | params: {action: 'follow'} 16 | }, 17 | unfollow: { 18 | method: 'POST', 19 | params: {action: 'unfollow'} 20 | }, 21 | activate: { 22 | method: 'POST', 23 | params: {action: 'activate'} 24 | }, 25 | invite: { 26 | method: 'POST', 27 | params: {action: 'invite'} 28 | }, 29 | resetPassword: { 30 | method: 'POST', 31 | params: {action: 'resetPassword'} 32 | } 33 | }), 34 | auth: $resource('users/authenticate'), 35 | notifications: $resource('users/notifications/:notificationId') 36 | } 37 | } 38 | ]) 39 | .factory('appUsersSearch', [ 40 | '$resource', 41 | function($resource) { 42 | var search = $resource('users/search/:keyword', {}, {query: {isArray: false}}); 43 | return function(keyword, onlyUsernames) { 44 | //implement search logic here 45 | var criteria = {keyword: keyword}; 46 | if (onlyUsernames) { 47 | criteria.onlyUsernames = true; 48 | } 49 | var promise = search.query(criteria).$promise; 50 | return promise; 51 | }; 52 | } 53 | ]) 54 | .factory('follow'); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![travis-build](https://api.travis-ci.org/ritenv/atwork.svg) 2 | 3 | | | 4 | ,---.|--- . . .,---.,---.|__/ 5 | ,---|| | | || || | \ 6 | `---^`---' `-'-'`---'` ` ` 7 | 8 | **Project atwork** is an open-source social network for your workplace 9 | (Under development) 10 | 11 | ## Idea 12 | 13 | **Project atwork** provides a simplistic, open-source social network with pluggable modules to customize the experience different workplaces desire. It can be easily hosted, customized and white-labeled. 14 | 15 | ## Stack 16 | 17 | **Project atwork** is built entirely in Javascript, with a goal to bring the best of javascript technologies together in a seamless, customizable and modular approach. Below technology stack is being used: 18 | 19 | 1. [MongoDB](http://mongodb.org/): The leading NoSQL database. 20 | 2. [Express](http://expressjs.com/): Fast, unopinionated, minimalist web framework for Node.js. 21 | 3. [AngularJS](): HTML enhanced for web apps. 22 | 4. [Node.js](http://nodejs.org/): A platform built on Chrome's JavaScript runtime. 23 | 24 | ## Contribution 25 | 26 | Contributions are very much welcome! 27 | 28 | ![contribute](fast-typing.gif) 29 | 30 | I am looking for developers to help me out in carrying this project forward - so if you're interested, check out the issues section and you can pick any that interests you in order to get started. If you have any questions, just get in touch via the twitter handle [@ritenv](http://twitter.com/@ritenv). 31 | 32 | ## Background 33 | 34 | **Project atwork** is being developed by [@ritenv](http://twitter.com/@ritenv) as a hobby project. If you have any ideas, suggestions or things you'd like to see in this app, PM me directly at [@ritenv](http://twitter.com/@ritenv). -------------------------------------------------------------------------------- /system/tests/system.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | var expect = chai.expect; 5 | 6 | // Articles Controller Spec 7 | describe('System', function() { 8 | describe('SystemServices', function() { 9 | beforeEach(function() { 10 | module('atwork.system'); 11 | }); 12 | 13 | // Initialize the controller and a mock scope 14 | var appSearch, 15 | appStorage, 16 | tokenHttpInterceptor, 17 | scope, 18 | $httpBackend, 19 | $stateParams, 20 | $location; 21 | 22 | // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). 23 | // This allows us to inject a service but then attach it to a variable 24 | // with the same name as the service. 25 | beforeEach(inject(function($rootScope, _$location_, _$httpBackend_, _appSearch_, _appStorage_, _tokenHttpInterceptor_) { 26 | 27 | scope = $rootScope.$new(); 28 | 29 | appSearch = _appSearch_; 30 | appStorage = _appStorage_; 31 | tokenHttpInterceptor = _tokenHttpInterceptor_; 32 | 33 | $httpBackend = _$httpBackend_; 34 | 35 | $location = _$location_; 36 | 37 | })); 38 | 39 | it('Service appSearch should be available', function() { 40 | expect(appSearch).to.be.a('function'); 41 | }); 42 | it('Service tokenHttpInterceptor should be available', function() { 43 | expect(tokenHttpInterceptor).to.be.a('object'); 44 | 45 | var config = {headers: {}}; 46 | appStorage.set('userToken', 'sample token'); 47 | config = tokenHttpInterceptor.request(config); 48 | expect(config.headers.Authorization).to.be.a('string'); 49 | expect(config.headers.Authorization).to.be.equal('Bearer sample token'); 50 | }); 51 | 52 | }); 53 | }); 54 | }()); 55 | -------------------------------------------------------------------------------- /modules/posts/public/posts.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.posts') 4 | .config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) { 5 | $routeProvider 6 | .when('/feed', { 7 | templateUrl: '/modules/posts/views/feed.html', 8 | controller: 'PostsCtrl', 9 | resolve: { 10 | resolvedFeeds: resolvedFeeds({limitComments: true}) 11 | } 12 | }) 13 | .when('/', { 14 | templateUrl: '/modules/posts/views/feed.html', 15 | controller: 'PostsCtrl', 16 | resolve: { 17 | resolvedFeeds: resolvedFeeds({limitComments: true}) 18 | } 19 | }) 20 | .when('/post/:postId', { 21 | templateUrl: '/modules/posts/views/feed.html', 22 | controller: 'PostsCtrl', 23 | resolve: { 24 | resolvedFeeds: resolvedFeeds({limitComments: false}) 25 | } 26 | }) 27 | .when('/feed/:hashtag', { 28 | templateUrl: '/modules/posts/views/feed.html', 29 | controller: 'PostsCtrl', 30 | resolve: { 31 | resolvedFeeds: resolvedFeeds({limitComments: true}) 32 | } 33 | }) 34 | ; 35 | $locationProvider.html5Mode(true); 36 | }]); 37 | 38 | /** 39 | * Get configuration for resolved feeds to reuse in routes 40 | * @param {Object} params Contains parameters for the options 41 | * @return {Array} 42 | */ 43 | function resolvedFeeds(params) { 44 | return [ 45 | '$route', 46 | 'appPostsFeed', 47 | function($route, appPostsFeed) { 48 | var deferred = Q.defer(); 49 | var options = angular.extend({ 50 | limitComments: params.limitComments 51 | }, $route.current.params); 52 | 53 | appPostsFeed.getFeeds(options, function(response) { 54 | deferred.resolve(response); 55 | }); 56 | 57 | return deferred.promise; 58 | } 59 | ]; 60 | } -------------------------------------------------------------------------------- /system/helpers/auth.js: -------------------------------------------------------------------------------- 1 | var ensureAuthorized = function(req, res, next) { 2 | var mongoose = require('mongoose'); 3 | var User = mongoose.model('User'); 4 | 5 | var bearerToken; 6 | var bearerHeader = req.headers["authorization"]; 7 | if (typeof bearerHeader !== 'undefined') { 8 | var bearer = bearerHeader.split(" "); 9 | bearerToken = bearer[1]; 10 | req.token = bearerToken; 11 | //populate({path: 'following', select: 'name email'}). 12 | 13 | User.findOne({token: req.token}) 14 | .populate('following') 15 | .exec(function(err, user) { 16 | if (err || !user) { 17 | return res.sendStatus(403); 18 | } 19 | req.user = user; 20 | next(); 21 | }); 22 | } else { 23 | res.sendStatus(403); 24 | } 25 | }; 26 | 27 | var justGetUser = function(req, res, next) { 28 | var mongoose = require('mongoose'); 29 | var User = mongoose.model('User'); 30 | var bearerToken; 31 | var bearerHeader = req.headers["authorization"]; 32 | if (typeof bearerHeader !== 'undefined') { 33 | var bearer = bearerHeader.split(" "); 34 | bearerToken = bearer[1]; 35 | req.token = bearerToken; 36 | //populate({path: 'following', select: 'name email'}). 37 | 38 | User.findOne({token: req.token}).exec(function(err, user) { 39 | if (user) { 40 | req.user = user; 41 | } 42 | next(); 43 | }); 44 | } 45 | }; 46 | 47 | module.exports = function(System) { 48 | var plugin = { 49 | /** 50 | * The helper register method 51 | * @return {Void} 52 | */ 53 | register: function () { 54 | return { 55 | ensureAuthorized: ensureAuthorized, 56 | justGetUser: justGetUser 57 | }; 58 | } 59 | }; 60 | 61 | /** 62 | * Attributes to identify the plugin 63 | * @type {Object} 64 | */ 65 | plugin.register.attributes = { 66 | name: 'Auth Helper', 67 | key: 'auth', 68 | version: '1.1.0' 69 | }; 70 | return plugin; 71 | }; -------------------------------------------------------------------------------- /modules/notifications/public/notifications.services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.notifications') 4 | .factory('appNotification', [ 5 | '$resource', 6 | '$mdToast', 7 | function($resource, $mdToast) { 8 | return { 9 | show: function(data) { 10 | if (!data.message) { 11 | return; 12 | } 13 | var toast = $mdToast.simple() 14 | .content(data.message) 15 | .action('VIEW') 16 | .highlightAction(false) 17 | .position('bottom right'); 18 | 19 | $mdToast.show(toast).then(function() { 20 | if (data.then) { 21 | data.then(); 22 | } 23 | }); 24 | if (window.fluid) { 25 | window.fluid.showGrowlNotification({ 26 | title: "Atwork", 27 | description: data.message, 28 | priority: 1, 29 | sticky: false, 30 | identifier: "foo", 31 | onclick: function() { 32 | // window.fluid.activate(); 33 | }, 34 | icon: imgEl // or URL string 35 | }); 36 | } 37 | } 38 | } 39 | } 40 | ]) 41 | .factory('appNotificationText', [ 42 | function() { 43 | return function(obj) { 44 | if (!obj) return {text: ''}; 45 | var msg = ''; 46 | var actor = obj.actor; 47 | 48 | switch (obj.notificationType) { 49 | case 'like': 50 | msg = actor.name + ' has liked a post'; 51 | break; 52 | 53 | case 'comment': 54 | msg = actor.name + ' has commented on a post'; 55 | break; 56 | 57 | case 'follow': 58 | msg = actor.name + ' is now following you'; 59 | break; 60 | 61 | case 'mention': 62 | msg = actor.name + ' mentioned you in a post'; 63 | break; 64 | } 65 | return {text: msg}; 66 | } 67 | } 68 | ]) 69 | ; 70 | -------------------------------------------------------------------------------- /system/public/settings/views/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Setup

4 |

Provide the details related to your workplace to customize your experience.

5 |
6 | 7 | 8 | 9 | 10 |
11 |
The name of your company, team, office or branch (e.g. Back Seat Boys)
12 |
13 |
14 | 15 | 16 | 17 | 18 |
19 |
Anything that inspires your team (e.g. Onward and upward!)
20 |
21 |
22 | 23 | 24 | 25 | 26 |
27 |
The email domains that you want to allow for registrations (e.g. @best-company.com, @best-branch.com)
28 |
29 |
30 | 31 | 32 | 33 | 34 |
35 |
The email the system connects to, for sending notifications and stuff
36 |
37 |
38 | 39 | 40 | 41 | 42 |
43 |
The email the system connects to, for sending notifications and stuff
44 |
45 |
46 | 47 | 48 | Save Settings 49 | 50 |
51 |
52 |
-------------------------------------------------------------------------------- /modules/users/public/views/users-invite-dialog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Invite 4 | 5 |
6 |
7 | 8 | 9 | 10 |
11 |
This is required!
12 |
That's too long!
13 |
That's too short!
14 |
15 |
16 | 17 | 18 | 19 | 20 |
21 |
This is required!
22 |
That's too long!
23 |
That's too short!
24 |
25 |
26 | 27 | 28 | 29 | 30 |
31 |
That's too long!
32 |
33 |
34 | 35 | 36 | Send Invite 37 | 38 |
39 | 40 |
41 |

42 | 43 | Awesome! 44 |

45 |

Your invitation is on its way!

46 |
47 | 48 |
49 | 50 |
51 |
52 | 53 | 54 | Close 55 | 56 |
57 |
-------------------------------------------------------------------------------- /modules/streams/public/views/streamsList.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | Streams 4 |

5 | 6 |
    7 |
  • 8 | 9 | $ {{ stream.title }} {{ stream.unread }} 10 | 11 |
    12 | 13 | 14 | 15 |
    16 |
  • 17 |
  • 18 |
    19 | 20 | 21 | 22 |
    23 |
    That's too long!
    24 |
    That's too short!
    25 |
    26 |
    27 |
    28 | 29 | + Create New 30 | 31 |
    32 |
    33 | 34 | {{::stream.title}} 35 | 36 | 37 |
    38 |
  • 39 |
40 |

Loading...

41 |
-------------------------------------------------------------------------------- /modules/activities/server/controllers/activities.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Activity = mongoose.model('Activity'); 3 | 4 | module.exports = function(System) { 5 | var User = mongoose.model('User'); 6 | var obj = {}; 7 | var json = System.plugins.JSON; 8 | var event = System.plugins.event; 9 | 10 | /** 11 | * Event listeners for post activities 12 | */ 13 | ['like', 'unlike', 'comment', 'newpost'].map(function(action) { 14 | event.on(action, function(data) { 15 | var post = data.post; 16 | var actor = data.actor; 17 | console.log(post.content, 'has been', action, 'by', actor.name); 18 | obj.create(action, actor, post); 19 | }); 20 | }); 21 | 22 | /** 23 | * Create a new activity 24 | * @param {Object} req Request 25 | * @param {Object} res Response 26 | * @return {Void} 27 | */ 28 | obj.create = function(action, actor, post) { 29 | var activity = new Activity({ 30 | actor: actor, 31 | post: post, 32 | action: action 33 | }); 34 | activity.save(function(err) { 35 | if (err) { 36 | return err; 37 | } 38 | return activity; 39 | }); 40 | }; 41 | 42 | /** 43 | * Get activities for a user 44 | * @param {Object} req The request object 45 | * @param {Object} res The response object 46 | * @return {Void} 47 | */ 48 | obj.feed = function(req, res) { 49 | 50 | /** 51 | * The search criteria 52 | * @type {Object} 53 | */ 54 | var criteria = {}; 55 | 56 | /** 57 | * Can accept username 58 | */ 59 | criteria.username = req.params.userId; 60 | 61 | User 62 | .findOne(criteria) 63 | .lean() 64 | .exec(function(err, user) { 65 | if (err) { 66 | return json.unhappy(err, res); 67 | } 68 | var activityCriteria = { actor: user._id }; 69 | Activity.find(activityCriteria, null, {sort: {created: -1}}) 70 | .populate('actor') 71 | .populate('post') 72 | .lean() 73 | .exec(function(err, activities) { 74 | /** 75 | * Filter activities to exclude the ones 76 | * whose posts are non existent 77 | */ 78 | activities = activities.filter(function(activity) { 79 | return activity.post; 80 | }); 81 | if (err) { 82 | return json.unhappy(err, res); 83 | } else { 84 | return json.happy({ 85 | records: activities 86 | }, res); 87 | } 88 | }); 89 | }); 90 | }; 91 | 92 | return obj; 93 | }; -------------------------------------------------------------------------------- /modules/users/server/routes/users.js: -------------------------------------------------------------------------------- 1 | var myController = require('../controllers/users'); 2 | /** 3 | * Init the controller 4 | */ 5 | module.exports = function(System) { 6 | var users = myController(System); 7 | 8 | var routes = []; 9 | 10 | routes.push({ 11 | method: 'post', 12 | path: '/', 13 | handler: users.create 14 | }); 15 | 16 | routes.push({ 17 | method: 'post', 18 | path: '/authenticate', 19 | handler: users.authenticate 20 | }); 21 | 22 | routes.push({ 23 | method: 'get', 24 | path: '/me', 25 | handler: users.me, 26 | authorized: true 27 | }); 28 | 29 | routes.push({ 30 | method: 'get', 31 | path: '/', 32 | handler: users.list, 33 | authorized: true 34 | }); 35 | 36 | routes.push({ 37 | method: 'post', 38 | path: '/:userId/avatar', 39 | handler: users.avatar, 40 | authorized: true 41 | }); 42 | 43 | routes.push({ 44 | method: 'post', 45 | path: '/:userId/activate', 46 | handler: users.activate, 47 | authorized: false //allow anonymous 48 | }); 49 | 50 | routes.push({ 51 | method: 'post', 52 | path: '/resetPassword', 53 | handler: users.resetPassword, 54 | authorized: false //allow anonymous 55 | }); 56 | 57 | routes.push({ 58 | method: 'post', 59 | path: '/:userId/invite', 60 | handler: users.invite, 61 | authorized: true 62 | }); 63 | 64 | routes.push({ 65 | method: 'post', 66 | path: '/:userId/follow', 67 | handler: users.follow, 68 | authorized: true 69 | }); 70 | 71 | routes.push({ 72 | method: 'post', 73 | path: '/:userId/unfollow', 74 | handler: users.unfollow, 75 | authorized: true 76 | }); 77 | 78 | routes.push({ 79 | method: 'get', 80 | path: '/notifications', 81 | handler: users.notifications, 82 | authorized: true 83 | }); 84 | 85 | routes.push({ 86 | method: 'get', 87 | path: '/notifications/:notificationId', 88 | handler: users.markRead, 89 | authorized: true 90 | }); 91 | 92 | routes.push({ 93 | method: 'get', 94 | path: '/:userId', 95 | handler: users.single, 96 | authorized: true 97 | }); 98 | 99 | routes.push({ 100 | method: 'put', 101 | path: '/', 102 | handler: users.modify, 103 | authorized: true 104 | }); 105 | 106 | routes.push({ 107 | method: 'get', 108 | path: '/search/:keyword', 109 | handler: users.search, 110 | authorized: true 111 | }); 112 | 113 | return routes; 114 | }; -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var sass = require('gulp-sass'); 3 | var uglify = require('gulp-uglify'); 4 | var concat = require('gulp-concat'); 5 | 6 | gulp.task('uglify', function() { 7 | return gulp.src([ 8 | /** 9 | * Load system files first 10 | */ 11 | 'system/public/utils.js', 12 | 'system/public/index.js', 13 | 'system/public/settings/settings.js', 14 | 'system/public/settings/settings.controllers.js', 15 | 'system/public/settings/settings.routes.js', 16 | 'system/public/settings/settings.services.js', 17 | 18 | /** 19 | * Load the main module 20 | */ 21 | 'modules/**/public/!(*.test|*.controllers|*.services|*.routes).js', 22 | 23 | /** 24 | * Load the controllers, services and routes 25 | */ 26 | 'modules/**/public/**/*.controllers.js', 27 | 'modules/**/public/**/*.services.js', 28 | 'modules/**/public/**/*.routes.js', 29 | 30 | './public/app.js' 31 | ]) 32 | .pipe(uglify()) 33 | .pipe(concat('scripts.js')) 34 | .pipe(gulp.dest('./dist')); 35 | }); 36 | 37 | gulp.task('uglifylibs', function() { 38 | return gulp.src([ 39 | './public/bower_components/jquery/dist/jquery.min.js', 40 | './public/bower_components/angular/angular.js', 41 | './public/bower_components/angular-aria/angular-aria.js', 42 | './public/bower_components/angular-animate/angular-animate.js', 43 | './public/bower_components/angular-material/angular-material.js', 44 | './public/bower_components/angular-route/angular-route.js', 45 | './public/bower_components/angular-messages/angular-messages.js', 46 | './public/bower_components/angular-resource/angular-resource.js', 47 | './public/bower_components/angular-loading-bar/build/loading-bar.min.js', 48 | './public/bower_components/lodash/lodash.min.js', 49 | 50 | './public/bower_components/q/q.js', 51 | './public/bower_components/ng-file-upload/angular-file-upload.js' 52 | ]) 53 | .pipe(uglify()) 54 | .pipe(concat('libs.js')) 55 | .pipe(gulp.dest('dist')); 56 | }); 57 | 58 | gulp.task('pack', ['uglifylibs', 'uglify'], function() { 59 | return true; 60 | }); 61 | 62 | gulp.task('sass', function () { 63 | gulp.src('./public/src/*.scss') 64 | .pipe(sass()) 65 | .pipe(gulp.dest('./public/css')); 66 | }); 67 | 68 | gulp.task('default', ['sass', 'pack']); 69 | 70 | var watcher = gulp.watch(['public/**/*.scss', 'modules/**/public/**/*.js'], ['default', 'pack']); 71 | watcher.on('change', function(event) { 72 | console.log('File ' + event.path + ' was ' + event.type + ', running tasks...'); 73 | }); -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Karma configuration 4 | module.exports = function(config) { 5 | var _ = require('lodash'), 6 | basePath = '.', 7 | assets = require(basePath + '/tools/test/assets.json'); 8 | 9 | config.set({ 10 | 11 | // base path, that will be used to resolve files and exclude 12 | basePath: basePath, 13 | 14 | // frameworks to use 15 | frameworks: ['mocha'], 16 | 17 | // list of files / patterns to load in the browser 18 | files: _.flatten(_.values(assets.core.js)).concat([ 19 | /** 20 | * Load system files first 21 | */ 22 | 'system/public/utils.js', 23 | 'system/public/index.js', 24 | 25 | /** 26 | * Load the main module 27 | */ 28 | 'modules/**/public/!(*.test|*.controllers|*.services|*.routes).js', 29 | 30 | /** 31 | * Load the controllers, services and routes 32 | */ 33 | 'modules/**/public/**/*.controllers.js', 34 | 'modules/**/public/**/*.services.js', 35 | 'modules/**/public/**/*.routes.js', 36 | 37 | "node_modules/mocha/mocha.js", 38 | "node_modules/chai/chai.js", 39 | 40 | 'system/**/*.spec.js' 41 | ]), 42 | 43 | // list of files to exclude 44 | exclude: [], 45 | 46 | // test results reporter to use 47 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 48 | reporters: ['progress', 'coverage'], 49 | 50 | // coverage 51 | preprocessors: { 52 | // source files that you want to generate coverage for 53 | // do not include tests or libraries 54 | // (these files will be instrumented by Istanbul) 55 | 'modules/**/public/**/!(*.test).js': ['coverage'] 56 | }, 57 | 58 | coverageReporter: { 59 | type: 'html', 60 | dir: 'test/coverage/' 61 | }, 62 | 63 | // web server port 64 | port: 9876, 65 | 66 | // enable / disable colors in the output (reporters and logs) 67 | colors: true, 68 | 69 | // level of logging 70 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 71 | logLevel: config.LOG_INFO, 72 | 73 | // enable / disable watching file and executing tests whenever any file changes 74 | autoWatch: true, 75 | 76 | // Start these browsers, currently available: 77 | // - Chrome 78 | // - ChromeCanary 79 | // - Firefox 80 | // - Opera 81 | // - Safari (only Mac) 82 | // - PhantomJS 83 | // - IE (only Windows) 84 | browsers: ['PhantomJS'], 85 | 86 | // If browser does not capture in given timeout [ms], kill it 87 | captureTimeout: 60000, 88 | 89 | // Continuous Integration mode 90 | // if true, it capture browsers, run tests and exit 91 | singleRun: true 92 | }); 93 | }; 94 | -------------------------------------------------------------------------------- /system/routes/settings.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var async = require('async'); 3 | var SystemSettings = mongoose.model('settings'); 4 | 5 | /** 6 | * Init the controller 7 | */ 8 | module.exports = function(System) { 9 | 10 | var routes = []; 11 | var json = System.plugins.JSON; 12 | 13 | routes.push({ 14 | method: 'get', 15 | path: '/system-settings', 16 | authorized: false, 17 | handler: function(req, res) { 18 | var criteria = {}; 19 | if (!req.user) { 20 | criteria = { 21 | name: { $in: ['workplace', 'tagline'] } 22 | }; 23 | } 24 | SystemSettings 25 | .find(criteria, null, {sort: {name: 1}}) 26 | .lean() 27 | .exec(function(err, items) { 28 | if (err) { 29 | return json.unhappy(err, res); 30 | } 31 | return json.happy({ items: items }, res); 32 | }); 33 | } 34 | }); 35 | 36 | routes.push({ 37 | method: 'post', 38 | path: '/system-settings', 39 | authorized: true, 40 | handler: function(req, res) { 41 | /** 42 | * Received settings 43 | * @type {Object} 44 | */ 45 | var settingItems = req.body; 46 | 47 | /** 48 | * Check if user is an admin 49 | */ 50 | if (!req.user.isAdmin()) { 51 | console.log('FORBIDDEN ADMIN'); 52 | /** 53 | * Return as is 54 | */ 55 | return json.happy({ items: settingItems }, res); 56 | } 57 | 58 | /** 59 | * Function to save an individual setting 60 | * @param {Object} 61 | * @param {Function} 62 | * @return {Void} 63 | */ 64 | var saveSetting = function(setting, cb) { 65 | SystemSettings 66 | .findOne({name: setting.key}) 67 | .exec(function(err, item) { 68 | if (err) { 69 | return json.unhappy(err, res); 70 | } 71 | if (item) { 72 | item.value = setting.val; 73 | } else { 74 | item = new SystemSettings(); 75 | item.name = setting.key; 76 | item.value = setting.val; 77 | } 78 | item.save(function(err) { 79 | cb(err); 80 | }); 81 | }); 82 | }; 83 | 84 | /** 85 | * Create an array of items 86 | * @type {Array} 87 | */ 88 | var items = []; 89 | for (var key in settingItems) { 90 | var val = settingItems[key]; 91 | items.push({key: key, val: val}); 92 | }; 93 | 94 | /** 95 | * Save all gathered settings 96 | */ 97 | async.map(items, saveSetting, function(err, results) { 98 | console.log(results); 99 | return json.happy({ items: items, results: results }, res); 100 | }); 101 | 102 | } 103 | }); 104 | return routes; 105 | }; -------------------------------------------------------------------------------- /modules/notifications/public/notifications.controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | angular.module('atwork.notifications') 3 | .controller('notificationsCtrl', [ 4 | '$scope', 5 | '$rootScope', 6 | 'appLocation', 7 | 'appUsers', 8 | 'appNotification', 9 | 'appWebSocket', 10 | 'appNotificationText', 11 | 'appDesktop', 12 | function($scope, $rootScope, appLocation, appUsers, appNotification, appWebSocket, appNotificationText, appDesktop) { 13 | /** 14 | * Initialize the defaults 15 | */ 16 | $scope.notificationShown = false; 17 | $scope.notificationCount = 0; 18 | $scope.items = []; 19 | 20 | /** 21 | * The event will be broadcasted when new notifications are received 22 | */ 23 | $rootScope.$on('notification', function(e, data) { 24 | $scope.updateNotifications(); 25 | }); 26 | 27 | /** 28 | * Hide or show notifications box 29 | * @param {Object} $event 30 | * @return {Void} 31 | */ 32 | $scope.showUserNotifications = function($event) { 33 | $scope.notificationShown = !$scope.notificationShown; 34 | }; 35 | 36 | /** 37 | * Mark notification as read 38 | * @param {Object} item The item object 39 | * @return {Void} 40 | */ 41 | $scope.markRead = function (item) { 42 | var record = appUsers.notifications.get({notificationId: item._id}, function () { 43 | if (record.res.notifications) { 44 | record.res.notifications.map(function (item) { 45 | item.display = appNotificationText(item); 46 | }); 47 | } 48 | $scope.items = record.res.notifications; 49 | $scope.notificationCount = record.res.notifications.length; 50 | }); 51 | $scope.showUserNotifications(); 52 | }; 53 | 54 | /** 55 | * Get notifications 56 | * @return {Void} 57 | */ 58 | $scope.updateNotifications = function () { 59 | var record = appUsers.notifications.get({}, function () { 60 | if (record.res.notifications) { 61 | record.res.notifications.map(function (item) { 62 | item.display = appNotificationText(item); 63 | if (item.post) { 64 | item.href = '/post/' + item.post._id 65 | } else if (item.user) { 66 | item.href = '/profile/' + item.actor.username 67 | } 68 | }); 69 | } 70 | $scope.items = record.res.notifications; 71 | $scope.notificationCount = record.res.notifications ? record.res.notifications.length : 0; 72 | appDesktop.notify({notificationsCount: $scope.notificationCount}); 73 | // if (window.fluid) { 74 | // window.fluid.dockBadge = $scope.notificationCount ? $scope.notificationCount : undefined; 75 | // if (window.fluid.dockBadge) { 76 | // window.fluid.playSound('Sosumi'); 77 | // window.fluid.playSound('Purr'); 78 | // } 79 | // } 80 | }); 81 | }; 82 | 83 | /** 84 | * Get initial notifications on load 85 | */ 86 | $scope.updateNotifications(); 87 | 88 | } 89 | ]); -------------------------------------------------------------------------------- /modules/streams/server/models/streams.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var mongoose = require('mongoose'), 7 | Schema = mongoose.Schema, 8 | crypto = require('crypto'), 9 | _ = require('lodash'); 10 | 11 | /** 12 | * Getter 13 | */ 14 | var escapeProperty = function(value) { 15 | return _.escape(value); 16 | }; 17 | 18 | /** 19 | * Post Schema 20 | */ 21 | 22 | var StreamsSchema = new Schema({ 23 | created: { 24 | type: Date, 25 | default: Date.now 26 | }, 27 | creator: { 28 | type: Schema.ObjectId, 29 | required: true, 30 | ref: 'User' 31 | }, 32 | title: { 33 | type: String, 34 | required: true, 35 | get: escapeProperty, 36 | match: [/^[a-zA-Z0-9_]*$/, 'Oh no! Only numbers and letters for stream names.'] 37 | }, 38 | purpose: { 39 | type: String, 40 | get: escapeProperty 41 | }, 42 | subscribers: [{ 43 | type: Schema.ObjectId, 44 | required: false, 45 | ref: 'User' 46 | }] 47 | }); 48 | 49 | /** 50 | * Methods 51 | */ 52 | StreamsSchema.methods = { 53 | /** 54 | * Hide security sensitive fields 55 | * 56 | * @returns {*|Array|Binary|Object} 57 | */ 58 | toJSON: function() { 59 | var obj = this.toObject(); 60 | if (obj.creator) { 61 | delete obj.creator.token; 62 | delete obj.creator.hashed_password; 63 | delete obj.creator.salt; 64 | delete obj.creator.following; 65 | } 66 | if (obj.likes) { 67 | obj.likeCount = obj.likes.length; 68 | } else { 69 | obj.likeCount = 0; 70 | } 71 | return obj; 72 | }, 73 | subscribe: function(userId) { 74 | if (this.subscribers.indexOf(userId) === -1 && this._id !== userId) { //cannot subscribe to own post 75 | this.subscribers.push(userId); 76 | } 77 | }, 78 | notifyUsers: function(data, System) { 79 | 80 | var notification = { 81 | postId: data.postId, 82 | actorId: data.actorId, 83 | notificationType: data.type 84 | }; 85 | this.populate('creator subscribers', function(err, stream) { 86 | stream.subscribers.map(function(user) { 87 | /** 88 | * Ignore creator, because we have a special call for that later 89 | */ 90 | if (user._id.toString() === stream.creator._id.toString()) { 91 | return; 92 | } 93 | /** 94 | * Ignore the person taking this action 95 | */ 96 | if (user._id.toString() === data.actorId.toString()) { 97 | return; 98 | } 99 | /** 100 | * Notify 101 | */ 102 | user.notify(notification, System); 103 | }); 104 | 105 | /** 106 | * Notify creator, if its not the creator taking this action 107 | */ 108 | if (stream.creator._id.toString() !== data.actorId.toString()) { 109 | stream.creator.notify(notification, System); 110 | } 111 | }); 112 | } 113 | }; 114 | 115 | mongoose.model('Stream', StreamsSchema); 116 | -------------------------------------------------------------------------------- /modules/users/public/users.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.users') 4 | .config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) { 5 | $routeProvider 6 | .when('/login', { 7 | templateUrl: '/modules/users/views/login.html?v', 8 | controller: 'LoginCtrl' 9 | }) 10 | .when('/logout', { 11 | templateUrl: '/modules/users/views/login.html?v', 12 | controller: 'LogoutCtrl' 13 | }) 14 | .when('/activate/:userId/:activationCode', { 15 | templateUrl: '/modules/users/views/activating.html', 16 | controller: 'ActivationCtrl' 17 | }) 18 | .when('/changePassword/:userId/:activationCode', { 19 | templateUrl: '/modules/users/views/change-password.html', 20 | controller: 'PasswordCtrl' 21 | }) 22 | .when('/profile/:userId/change-password', { 23 | templateUrl: '/modules/users/views/change-password.html', 24 | controller: 'ProfileCtrl', 25 | resolve: { 26 | profileData: [ 27 | '$route', 28 | 'appAuth', 29 | 'appUsers', 30 | function($route, appAuth, appUsers) { 31 | var routeParams = $route.current.params; 32 | var userId = routeParams.userId || appAuth.getUser()._id; 33 | return appUsers.single.get({userId: userId}).$promise; 34 | } 35 | ], 36 | resolvedFeeds: [ 37 | '$route', 38 | 'appPostsFeed', 39 | function($route, appPostsFeed) { 40 | var deferred = Q.defer(); 41 | var options = angular.extend({ 42 | feedPage: 0 43 | }, $route.current.params); 44 | 45 | appPostsFeed.getFeeds(options, function(response) { 46 | deferred.resolve(response); 47 | }); 48 | 49 | return deferred.promise; 50 | } 51 | ] 52 | } 53 | }) 54 | .when('/profile/:userId', { 55 | templateUrl: '/modules/users/views/profile.html?v', 56 | controller: 'ProfileCtrl', 57 | resolve: { 58 | profileData: [ 59 | '$route', 60 | 'appAuth', 61 | 'appUsers', 62 | function($route, appAuth, appUsers) { 63 | var routeParams = $route.current.params; 64 | var userId = routeParams.userId || appAuth.getUser()._id; 65 | return appUsers.single.get({userId: userId}).$promise; 66 | } 67 | ], 68 | resolvedFeeds: [ 69 | '$route', 70 | 'appPostsFeed', 71 | function($route, appPostsFeed) { 72 | var deferred = Q.defer(); 73 | var options = angular.extend({ 74 | feedPage: 0 75 | }, $route.current.params); 76 | 77 | appPostsFeed.getFeeds(options, function(response) { 78 | deferred.resolve(response); 79 | }); 80 | 81 | return deferred.promise; 82 | } 83 | ] 84 | } 85 | }) 86 | .when('/me', { 87 | templateUrl: '/modules/users/views/profile.html?v', 88 | controller: 'ProfileCtrl' 89 | }) 90 | ; 91 | $locationProvider.html5Mode(true); 92 | }]); -------------------------------------------------------------------------------- /system/public/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.utils', ['ngRoute', 'ngMaterial']) 4 | .factory('appStorage', function() { 5 | return { 6 | get: function(item) { 7 | return localStorage.getItem(item); 8 | }, 9 | set: function(item, val) { 10 | return localStorage.setItem(item, val); 11 | }, 12 | remove: function(item) { 13 | return localStorage.removeItem(item); 14 | } 15 | } 16 | }) 17 | .factory('appPromise', [ 18 | function() { 19 | return function(fn) { 20 | var deferred = Q.defer(); 21 | fn(deferred); 22 | return deferred.promise; 23 | } 24 | } 25 | ]) 26 | .factory('appLocation', [ 27 | '$location', 28 | function($location) { 29 | return $location; 30 | } 31 | ]) 32 | .factory('appWebSocket', [ 33 | function($location) { 34 | var obj = { 35 | conn: {}, 36 | connect: function() { 37 | var $this = this; 38 | var socket = window.io(); 39 | socket.on('connect', function() { 40 | console.log('Connected'); 41 | }); 42 | socket.on('disconnect', function() { 43 | $this.connect(); 44 | }); 45 | this.conn = socket; 46 | }, 47 | reconnect: function() { 48 | this.conn.close(); 49 | this.connect(); 50 | }, 51 | close: function() { 52 | this.conn.close(); 53 | } 54 | }; 55 | obj.connect(); 56 | return obj; 57 | } 58 | ]) 59 | .factory('appToast', [ 60 | '$mdToast', 61 | function($mdToast) { 62 | return function(message) { 63 | var toast = $mdToast.simple() 64 | .content(message) 65 | .action('OK') 66 | .highlightAction(false) 67 | .position('top right'); 68 | $mdToast.show(toast); 69 | } 70 | } 71 | ]) 72 | .factory('appDialog', [ 73 | '$mdDialog', 74 | function($mdDialog) { 75 | return $mdDialog; 76 | } 77 | ]) 78 | .factory('appDesktop', [ 79 | '$rootScope', 80 | function($rootScope) { 81 | var notifBadge = 0; 82 | var messageBadge = 0; 83 | return { 84 | notify: function(options) { 85 | notifBadge = (options.notificationsCount !== undefined) ? options.notificationsCount : notifBadge; 86 | messageBadge = (options.messagesCount !== undefined) ? options.messagesCount : messageBadge; 87 | $rootScope.badges = {messageBadge: messageBadge}; 88 | if (window.fluid) { 89 | window.fluid.dockBadge = notifBadge + messageBadge; 90 | if (parseInt(window.fluid.dockBadge) <= 0) { 91 | window.fluid.dockBadge = undefined; 92 | } else { 93 | window.fluid.playSound('Sosumi'); 94 | window.fluid.playSound('Purr'); 95 | } 96 | } 97 | } 98 | } 99 | } 100 | ]) 101 | .directive('setFocus', [ 102 | '$timeout', '$parse', 103 | function($timeout, $parse) { 104 | return { 105 | //scope: true, // optionally create a child scope 106 | link: function(scope, element, attrs) { 107 | /** 108 | * Set focus only if not on mobile 109 | */ 110 | if ($(window).width() <= 600) { 111 | return true; 112 | } 113 | var model = $parse(attrs.setFocus); 114 | scope.$watch(model, function(value) { 115 | if(value === true) { 116 | $timeout(function() { 117 | element[0].focus(); 118 | }, 800); 119 | } 120 | }); 121 | } 122 | }; 123 | } 124 | ]) 125 | 126 | ; 127 | -------------------------------------------------------------------------------- /system/public/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.system', [ 4 | 'ngRoute', 5 | 'ngMessages', 6 | 'ngResource', 7 | 'angularFileUpload', 8 | 'atwork.utils', 9 | 'angular-loading-bar', 10 | 'ngAnimate' 11 | ]); 12 | 13 | angular.module('atwork.system') 14 | .factory('tokenHttpInterceptor', [ 15 | 'appStorage', 16 | function (appStorage) { 17 | return { 18 | request: function (config) { 19 | config.headers.Authorization = 'Bearer ' + appStorage.get('userToken'); 20 | return config; 21 | } 22 | }; 23 | } 24 | ]) 25 | .factory('appSearch', [ 26 | '$resource', 27 | function($resource) { 28 | var search = $resource('search/:keyword', {}, {query: {isArray: false}}); 29 | return function(keyword) { 30 | //implement search logic here 31 | var promise = search.query({keyword: keyword}).$promise; 32 | return promise; 33 | }; 34 | } 35 | ]) 36 | .config([ 37 | '$httpProvider', 38 | '$mdThemingProvider', 39 | 'cfpLoadingBarProvider', 40 | function ($httpProvider, $mdThemingProvider, cfpLoadingBarProvider) { 41 | $httpProvider.interceptors.push('tokenHttpInterceptor'); 42 | // $mdThemingProvider.theme('default') 43 | // .primaryPalette('blue') 44 | // .accentPalette('blue-grey'); 45 | 46 | // $mdThemingProvider.definePalette('primaryPalette', { 47 | // '50': 'E4EFF7', 48 | // '100': 'D6E0E7', 49 | // '200': '77C0F4', 50 | // '300': '63B4ED', 51 | // '400': '40A8F2', 52 | // '500': '36A5F4', 53 | // '600': '249DF4', 54 | // '700': '1196F4', 55 | // '800': '0691F4', 56 | // '900': '0A98FD', 57 | // 'A100': '89BEC8', 58 | // 'A200': '89BEC8', 59 | // 'A400': '89BEC8', 60 | // 'A700': '89BEC8', 61 | // 'contrastDefaultColor': 'light', // whether, by default, text (contrast) 62 | // // on this palette should be dark or light 63 | // 'contrastDarkColors': ['50', '100', //hues which contrast should be 'dark' by default 64 | // '200', '300', '400', 'A100'], 65 | // 'contrastLightColors': undefined // could also specify this if default was 'dark' 66 | // }); 67 | 68 | // $mdThemingProvider.theme('default') 69 | // .primaryPalette('primaryPalette') 70 | // .accentPalette('primaryPalette') 71 | 72 | $mdThemingProvider.definePalette('amazingPaletteName', { 73 | '50': 'ffebee', 74 | '100': 'ffcdd2', 75 | '200': 'ef9a9a', 76 | '300': 'e57373', 77 | '400': 'ef5350', 78 | '500': 'f44336', 79 | '600': 'e53935', 80 | '700': 'd32f2f', 81 | '800': 'c62828', 82 | '900': 'b71c1c', 83 | 'A100': 'ff8a80', 84 | 'A200': 'ff5252', 85 | 'A400': 'ff1744', 86 | 'A700': 'd50000', 87 | 'contrastDefaultColor': 'light', // whether, by default, text (contrast) 88 | // on this palette should be dark or light 89 | 'contrastDarkColors': ['50', '100', //hues which contrast should be 'dark' by default 90 | '200', '300', '400', 'A100'], 91 | 'contrastLightColors': undefined // could also specify this if default was 'dark' 92 | }); 93 | $mdThemingProvider.theme('default') 94 | .primaryPalette('amazingPaletteName') 95 | .accentPalette('amazingPaletteName') 96 | 97 | cfpLoadingBarProvider.includeSpinner = true; 98 | cfpLoadingBarProvider.includeBar = false; 99 | } 100 | ]); 101 | -------------------------------------------------------------------------------- /public/app.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('AtWork', [ 2 | 'atwork.system', 3 | 'atwork.users', 4 | 'atwork.posts', 5 | 'atwork.streams', 6 | 'atwork.chats', 7 | 'atwork.activities', 8 | 'atwork.notifications', 9 | 'atwork.settings', 10 | 'ngMaterial']); 11 | 12 | app.controller('AppCtrl', [ 13 | '$scope', 14 | '$route', 15 | '$rootScope', 16 | '$mdSidenav', 17 | '$mdBottomSheet', 18 | '$location', 19 | '$timeout', 20 | 'appLocation', 21 | 'appAuth', 22 | 'appWebSocket', 23 | 'appSettings', 24 | 'appSettingsValid', 25 | 'appToast', 26 | function($scope, $route, $rootScope, $mdSidenav, $mdBottomSheet, $location, $timeout, appLocation, appAuth, appWebSocket, appSettings, appSettingsValid, appToast) { 27 | $scope.barTitle = ''; 28 | $scope.search = ''; 29 | 30 | $scope.toggleSidenav = function(menuId) { 31 | $mdSidenav(menuId).toggle(); 32 | }; 33 | 34 | $scope.updateLoginStatus = function() { 35 | $scope.isLoggedIn = appAuth.isLoggedIn(); 36 | $scope.user = appAuth.getUser(); 37 | }; 38 | 39 | $scope.goHome = function() { 40 | appLocation.url('/'); 41 | }; 42 | 43 | $scope.showUserActions = function($event) { 44 | $mdBottomSheet.show({ 45 | templateUrl: '/modules/users/views/user-list.html', 46 | controller: 'UserSheet', 47 | targetEvent: $event 48 | }).then(function(clickedItem) { 49 | $scope.alert = clickedItem.name + ' clicked!'; 50 | }); 51 | }; 52 | 53 | var initiateSettings = function(cb) { 54 | appSettings.fetch(function(settings) { 55 | $rootScope.systemSettings = settings; 56 | if (cb) { 57 | cb(); 58 | } 59 | }); 60 | }; 61 | 62 | /** 63 | * Scroll the view to top on route change 64 | */ 65 | $scope.$on('$routeChangeSuccess', function() { 66 | angular.element('*[md-scroll-y]').animate({scrollTop: 0}, 300); 67 | $mdSidenav('left').close(); 68 | }); 69 | 70 | $scope.$on('loggedIn', function() { 71 | $scope.updateLoginStatus(); 72 | $scope.barTitle = ''; 73 | $scope.$broadcast('updateNotifications'); 74 | appWebSocket.conn.emit('online', {token: appAuth.getToken()}); 75 | appAuth.refreshUser(function(user) { 76 | $scope.user = user; 77 | }); 78 | /** 79 | * Fetch settings and get the app ready 80 | */ 81 | initiateSettings(function() { 82 | $scope.$on('$routeChangeStart', function (event, toState) { 83 | var valid = appSettingsValid(); 84 | if (!valid) { 85 | appToast('Please complete the setup first.'); 86 | } 87 | }); 88 | $scope.appReady = true; 89 | $scope.barTitle = $rootScope.systemSettings.tagline; 90 | $timeout(appSettingsValid); 91 | }); 92 | 93 | }); 94 | 95 | $scope.$on('loggedOut', function() { 96 | $scope.updateLoginStatus(); 97 | appWebSocket.conn.emit('logout', {token: appAuth.getToken()}); 98 | }); 99 | 100 | appWebSocket.conn.on('connect', function() { 101 | if (appAuth.isLoggedIn()) { 102 | appWebSocket.conn.emit('online', {token: appAuth.getToken()}); 103 | } 104 | }); 105 | 106 | $scope.updateLoginStatus(); 107 | $timeout(function() { 108 | if (!appAuth.isLoggedIn()) { 109 | if (window.location.href.indexOf('/activate/') == -1 && window.location.href.indexOf('/changePassword/') == -1) { 110 | appLocation.url('/login'); 111 | } 112 | initiateSettings(); 113 | $scope.appReady = true; 114 | } else { 115 | $scope.barTitle = ''; 116 | $scope.$broadcast('loggedIn'); 117 | } 118 | 119 | }); 120 | } 121 | ]); -------------------------------------------------------------------------------- /modules/chats/server/models/chats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var mongoose = require('mongoose'), 7 | Schema = mongoose.Schema, 8 | crypto = require('crypto'), 9 | _ = require('lodash'); 10 | 11 | /** 12 | * Getter 13 | */ 14 | var escapeProperty = function(value) { 15 | return _.escape(value); 16 | }; 17 | 18 | /** 19 | * Post Schema 20 | */ 21 | 22 | var ChatsSchema = new Schema({ 23 | created: { 24 | type: Date, 25 | default: Date.now 26 | }, 27 | modified: { 28 | type: Date, 29 | default: Date.now 30 | }, 31 | lastAccessed: [{ 32 | accessed: { 33 | type: Date, 34 | default: Date.now 35 | }, 36 | user: { 37 | type: Schema.ObjectId, 38 | required: true, 39 | ref: 'User' 40 | }, 41 | unread: Number 42 | }], 43 | creator: { 44 | type: Schema.ObjectId, 45 | required: true, 46 | ref: 'User' 47 | }, 48 | unread: Number, 49 | messages: [ 50 | { 51 | created: { 52 | type: Date, 53 | default: Date.now 54 | }, 55 | creator: { 56 | type: Schema.ObjectId, 57 | required: true, 58 | ref: 'User' 59 | }, 60 | message: String 61 | } 62 | ], 63 | participants: [{ 64 | type: Schema.ObjectId, 65 | required: false, 66 | ref: 'User' 67 | }] 68 | }); 69 | 70 | /** 71 | * Methods 72 | */ 73 | ChatsSchema.methods = { 74 | /** 75 | * Hide security sensitive fields 76 | * 77 | * @returns {*|Array|Binary|Object} 78 | */ 79 | toJSON: function() { 80 | var obj = this.toObject(); 81 | if (obj.creator) { 82 | delete obj.creator.token; 83 | delete obj.creator.hashed_password; 84 | delete obj.creator.salt; 85 | delete obj.creator.following; 86 | } 87 | return obj; 88 | }, 89 | calculateUnread: function() { 90 | var obj = this; 91 | obj.lastAccessed.map(function(access) { 92 | access.unread = obj.messages.filter(function(msg) { 93 | return msg.created > access.accessed; 94 | }).length; 95 | }); 96 | }, 97 | calculateUnreadFor: function(user) { 98 | var obj = this; 99 | obj.lastAccessed.map(function(access) { 100 | if (access.user.toString() === user._id.toString()) { 101 | obj.unread = access.unread; 102 | } 103 | }); 104 | }, 105 | doAccess: function(user) { 106 | var chat = this; 107 | //change last accessed 108 | var lastAccessedUpdated = false; 109 | chat.lastAccessed.map(function(access) { 110 | if (access.user.toString() === user._id.toString()) { 111 | access.accessed = Date.now(); 112 | lastAccessedUpdated = true; 113 | } 114 | }); 115 | if (!lastAccessedUpdated) { 116 | chat.lastAccessed.push({ 117 | user: user._id, 118 | accessed: Date.now() 119 | }); 120 | } 121 | }, 122 | notifyUsers: function(data, System) { 123 | var chatMessage = data.chatMessage; 124 | 125 | var notification = { 126 | chatId: data.chatId, 127 | chatMessage: data.chatMessage, 128 | actorId: data.actorId, 129 | notificationType: data.type, 130 | config: data.config 131 | }; 132 | this.populate('creator participants', function(err, chat) { 133 | chat.participants.map(function(user) { 134 | /** 135 | * Ignore creator of message 136 | */ 137 | if (user._id.toString() === chatMessage.creator._id.toString()) { 138 | return; 139 | } 140 | /** 141 | * Ignore the person taking this action 142 | */ 143 | if (user._id.toString() === data.actorId.toString()) { 144 | return; 145 | } 146 | /** 147 | * Notify 148 | */ 149 | user.notify(notification, System); 150 | }); 151 | }); 152 | } 153 | }; 154 | 155 | mongoose.model('Chat', ChatsSchema); 156 | -------------------------------------------------------------------------------- /modules/posts/public/views/feed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 |
5 | 6 | Back 7 | 8 |
9 |

...

10 |
11 | 12 | 13 | 14 |
15 |
16 | 17 |
18 |
19 | 20 | 21 | 22 |
23 |
Write your post first.
24 |
That's too long!
25 |
That's too short. Write more?
26 |
27 |
28 | 29 | 30 | Share 31 | 32 | 33 | Cancel 34 | 35 |
36 |
37 | 38 | 39 |
40 | 41 | 42 | 43 | 44 | {{item.name}} 45 |
46 |

{{item.name}}

47 |

@{{item.username}}

48 |

{{item.designation}}

49 |

{{item.email}}

50 |
51 |
52 |
53 |
54 |
55 |
56 | 57 |
58 |
59 | Filter 60 | 61 | 62 | 63 | 64 |
65 |
66 |
67 | 68 | Load New Items ({{newFeedCount}}) 69 | 70 | 71 |

Loading...

72 | 73 |
74 | Loaded more below 75 |
76 | 77 |
78 | 79 |
80 |
81 | Load More 82 |
83 |

-- end --

84 | 85 |
86 |
87 |
-------------------------------------------------------------------------------- /modules/posts/public/views/post-single.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | {{item.creator.name}} 4 |
5 | {{::item.creator.name}} 6 |

7 |
8 | 13 | - 14 | 15 | Comment 16 | 17 | - 18 | 19 | View 20 | 21 | - 22 | 23 | $ {{item.stream.title}} 24 | 25 | 26 | {{::item.created | date:'medium'}} 27 | 28 |
29 | {{::item.created | date:'medium'}} 30 | 31 | 32 |
33 |
34 | 35 | 36 | 37 | 38 |
39 |
Write your post first.
40 |
That's too long!
41 |
That's too short. Write more?
42 |
43 |
44 | 45 | 46 | Share 47 | 48 | 49 | Cancel 50 | 51 |
52 |
53 | 54 |
55 | 56 | 57 | {{comment.creator.name}} 58 |
59 | {{::comment.creator.name}} 60 |

61 | {{::comment.created | date:'medium'}} 62 |
63 |
64 | 65 | More Comments ({{::item.hasMoreComments}}) 66 | 67 |
68 | 69 | 70 | 71 |
72 |
73 | 74 | 75 |
-------------------------------------------------------------------------------- /modules/posts/server/models/posts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var mongoose = require('mongoose'), 7 | Schema = mongoose.Schema, 8 | crypto = require('crypto'), 9 | _ = require('lodash'); 10 | 11 | /** 12 | * Getter 13 | */ 14 | var escapeProperty = function(value) { 15 | return _.escape(value); 16 | }; 17 | 18 | /** 19 | * Post Schema 20 | */ 21 | 22 | var PostSchema = new Schema({ 23 | created: { 24 | type: Date, 25 | default: Date.now 26 | }, 27 | creator: { 28 | type: Schema.ObjectId, 29 | required: true, 30 | ref: 'User' 31 | }, 32 | content: { 33 | type: String, 34 | required: true, 35 | get: escapeProperty 36 | }, 37 | comments: [{ 38 | created: { 39 | type: Date, 40 | default: Date.now 41 | }, 42 | content: { 43 | type: String, 44 | required: true, 45 | get: escapeProperty 46 | }, 47 | creator: { 48 | type: Schema.ObjectId, 49 | required: true, 50 | ref: 'User' 51 | } 52 | }], 53 | stream: { 54 | type: Schema.ObjectId, 55 | required: false, 56 | ref: 'Stream' 57 | }, 58 | subscribers: [{ 59 | type: Schema.ObjectId, 60 | required: false, 61 | ref: 'User' 62 | }], 63 | likes: [{ 64 | type: Schema.ObjectId, 65 | required: false, 66 | ref: 'User' 67 | }], 68 | liked: { 69 | type: Boolean, 70 | default: false 71 | }, 72 | hasMoreComments: { 73 | type: Number, 74 | default: 0 75 | } 76 | }); 77 | 78 | /** 79 | * Methods 80 | */ 81 | PostSchema.methods = { 82 | /** 83 | * Hide security sensitive fields 84 | * 85 | * @returns {*|Array|Binary|Object} 86 | */ 87 | toJSON: function() { 88 | var obj = this.toObject(); 89 | if (obj.creator) { 90 | delete obj.creator.token; 91 | delete obj.creator.hashed_password; 92 | delete obj.creator.salt; 93 | delete obj.creator.following; 94 | } 95 | if (obj.likes) { 96 | obj.likeCount = obj.likes.length; 97 | } else { 98 | obj.likeCount = 0; 99 | } 100 | return obj; 101 | }, 102 | afterSave: function(user, limitComments) { 103 | var obj = this; 104 | obj.liked = obj.likes.indexOf(user._id) != -1; 105 | if (limitComments && obj.comments && obj.comments.length > 3) { 106 | obj.hasMoreComments = obj.comments.length - 3; 107 | obj.comments = obj.comments.slice(0, 3); 108 | } 109 | return obj; 110 | }, 111 | getMentionedUsers: function(cb) { 112 | /** 113 | * Mention format will be @xyz 114 | */ 115 | var re = /@([A-Za-z0-9_]+)/g; 116 | 117 | /** 118 | * Try to find the usernames 119 | * @type {Array} 120 | */ 121 | var usernames = this.content.match(re); 122 | 123 | if (!usernames || !usernames.length) { 124 | return []; 125 | } 126 | 127 | /** 128 | * Remove the '@' symbol 129 | */ 130 | usernames.map(function(username, i) { 131 | usernames[i] = username.substring(1); 132 | }); 133 | 134 | /** 135 | * Find in the db 136 | */ 137 | var User = mongoose.model('User'); 138 | 139 | User.find({username: {$in: usernames} }) 140 | .exec(function(err, users) { 141 | if (cb) { 142 | cb(err, users); 143 | } 144 | }); 145 | }, 146 | subscribe: function(userId) { 147 | if (this.subscribers.indexOf(userId) === -1 && this._id !== userId) { //cannot subscribe to own post 148 | this.subscribers.push(userId); 149 | } 150 | }, 151 | notifyUsers: function(data, System) { 152 | 153 | var notification = { 154 | postId: this._id, 155 | actorId: data.actorId, 156 | notificationType: data.type, 157 | config: data.config 158 | }; 159 | this.populate('creator subscribers', function(err, post) { 160 | post.subscribers.map(function(user) { 161 | /** 162 | * Ignore creator, because we have a special call for that later 163 | */ 164 | if (user._id.toString() === post.creator._id.toString()) { 165 | return; 166 | } 167 | /** 168 | * Ignore the person taking this action 169 | */ 170 | if (user._id.toString() === data.actorId.toString()) { 171 | return; 172 | } 173 | /** 174 | * Notify 175 | */ 176 | user.notify(notification, System); 177 | }); 178 | 179 | /** 180 | * Notify creator, if its not the creator taking this action 181 | */ 182 | if (post.creator._id.toString() !== data.actorId.toString()) { 183 | post.creator.notify(notification, System); 184 | } 185 | }); 186 | } 187 | }; 188 | 189 | mongoose.model('Post', PostSchema); 190 | -------------------------------------------------------------------------------- /modules/chats/public/chats.controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.chats') 4 | .controller('ChatsCtrl', [ 5 | '$scope', 6 | '$rootScope', 7 | '$routeParams', 8 | '$timeout', 9 | 'appAuth', 10 | 'appToast', 11 | 'appStorage', 12 | 'appLocation', 13 | 'appWebSocket', 14 | 'appChats', 15 | 'appDialog', 16 | 'appDesktop', 17 | function($scope, $rootScope, $routeParams, $timeout, appAuth, appToast, appStorage, appLocation, appWebSocket, appChats, appDialog, appDesktop) { 18 | $scope.chats = []; 19 | $scope.actions = {}; 20 | var openChats = {}; 21 | 22 | var updateBadges = function() { 23 | /** 24 | * Update badge 25 | */ 26 | var messagesCount = 0; 27 | _.each($scope.chats, function(chat) { 28 | messagesCount += chat.unread; 29 | }) 30 | appDesktop.notify({messagesCount: messagesCount}); 31 | }; 32 | 33 | /** 34 | * Open a new conversation 35 | * @return {Void} 36 | */ 37 | $scope.message = function(ev, profile, chatItem) { 38 | var criteria; 39 | 40 | if (chatItem) { 41 | chatItem.unread = 0; 42 | updateBadges(); 43 | criteria = { 44 | chatId: chatItem._id 45 | }; 46 | } else { 47 | criteria = { 48 | participants: [ 49 | profile._id, 50 | appAuth.getUser()._id 51 | ] 52 | } 53 | } 54 | 55 | var chat = new appChats.single(criteria); 56 | 57 | chat.$save(function(response) { 58 | var chatId = response.res.record._id; 59 | 60 | /** 61 | * Add chat to openChats 62 | */ 63 | openChats[response.res.record._id] = response.res.record; 64 | 65 | /** 66 | * Show dialog 67 | */ 68 | appDialog.show({ 69 | controller: [ 70 | '$scope', 71 | 'appDialog', 72 | function($scope, appDialog) { 73 | updateBadges(); 74 | /** 75 | * Assign likers to the users variable 76 | * @type {Array} 77 | */ 78 | $scope.messages = response.res.record.messages; 79 | 80 | $scope.chatId = chatId; 81 | $scope.firstTime = true; 82 | 83 | $scope.$on('chatMessage', function(e, data) { 84 | $scope.$apply(function() { 85 | $scope.messages.unshift(data.chatMessage); 86 | }); 87 | appWebSocket.conn.emit('markAccessed', {chatId: data.chatId, userId: appAuth.getUser()._id}); 88 | }); 89 | 90 | /** 91 | * Hide the dialog 92 | * @return {Void} 93 | */ 94 | $scope.hide = function() { 95 | appDialog.hide(); 96 | }; 97 | 98 | $scope.sendMessage = function(isValid) { 99 | if (isValid) { 100 | var message = $scope.message; 101 | $scope.message = ''; 102 | appChats.single.message({ 103 | message: message, 104 | creator: appAuth.getUser()._id, 105 | _id: $scope.chatId 106 | }, function(response) { 107 | $scope.messages.unshift(response.res.record.messages[0]); 108 | }); 109 | } 110 | }; 111 | } 112 | ], 113 | templateUrl: '/modules/chats/views/chat-dialog.html', 114 | targetEvent: ev, 115 | }) 116 | .finally(function() { 117 | delete openChats[chatId]; 118 | }); 119 | 120 | }); 121 | }; 122 | 123 | $scope.updateChats = function (options) { 124 | options = options || {}; 125 | var chatsData = appChats.single.get({}, function() { 126 | /** 127 | * Check if the feed needs to reload 128 | */ 129 | if (options.reload) { 130 | $scope.chats = []; 131 | } 132 | 133 | /** 134 | * Check whether to append to feed (at bottom) or insert (at top) 135 | */ 136 | if (chatsData.res.records.length) { 137 | if (!options.append) { 138 | $scope.chats = chatsData.res.records.concat($scope.chats); 139 | } else { 140 | $scope.chats = $scope.chats.concat(chatsData.res.records); 141 | } 142 | } 143 | 144 | updateBadges(); 145 | 146 | /** 147 | * Check if there are more pages 148 | * @type {Boolean} 149 | */ 150 | $scope.noMoreChats = !chatsData.res.morePages; 151 | 152 | /** 153 | * Set the updated timestamp 154 | */ 155 | $scope.lastUpdated = Date.now(); 156 | }); 157 | }; 158 | 159 | $scope.$on('chatMessage', function(e, data) { 160 | if (!openChats[data.chatId]) { 161 | $scope.updateChats({reload: true}); 162 | } 163 | }); 164 | 165 | } 166 | ]) 167 | ; 168 | -------------------------------------------------------------------------------- /modules/streams/server/controllers/streams.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Stream = mongoose.model('Stream'); 3 | 4 | module.exports = function(System) { 5 | var obj = {}; 6 | var json = System.plugins.JSON; 7 | var event = System.plugins.event; 8 | var sck = System.webSocket; 9 | 10 | /** 11 | * Stream related sockets 12 | */ 13 | sck.on('connection', function(socket){ 14 | socket.on('stream', function(streamId) { 15 | socket.broadcast.emit('stream', streamId); 16 | }); 17 | }); 18 | 19 | /** 20 | * Create a stream 21 | * @param {Object} req The request object 22 | * @param {Object} res The response object 23 | * @return {Void} 24 | */ 25 | obj.create = function (req, res) { 26 | var stream = new Stream(req.body); 27 | stream.creator = req.user._id; 28 | stream.subscribers.push(req.user._id); 29 | 30 | stream.save(function(err) { 31 | if (err) { 32 | return json.unhappy(err, res); 33 | } 34 | return json.happy(stream, res); 35 | }); 36 | }; 37 | 38 | /** 39 | * Modify a stream 40 | * @param {Object} req The request object 41 | * @param {Object} res The response object 42 | * @return {Void} 43 | */ 44 | obj.modify = function (req, res) { 45 | Stream.findOne({ 46 | _id: req.params.streamId 47 | }) 48 | .populate('creator') 49 | .exec(function(err, stream) { 50 | if (err) { 51 | return json.unhappy(err, res); 52 | } 53 | stream.purpose = req.body.purpose; 54 | stream.title = req.body.title || stream.title; 55 | stream.save(function(err) { 56 | if (err) { 57 | return json.unhappy(err, res); 58 | } 59 | return json.happy(stream, res); 60 | }); 61 | }); 62 | }; 63 | 64 | /** 65 | * List all streams 66 | * @param {Object} req The request object 67 | * @param {Object} res The response object 68 | * @return {Void} 69 | */ 70 | obj.list = function (req, res) { 71 | var user = req.user; 72 | 73 | var criteria = {}; 74 | 75 | /** 76 | * Do we want only subscribed streams? 77 | */ 78 | if (req.query.subscribed) { 79 | criteria.subscribers = req.user._id; 80 | } 81 | 82 | /** 83 | * Do we want only unsubscribed streams? 84 | */ 85 | if (req.query.unsubscribed) { 86 | criteria.subscribers = {$ne: req.user._id}; 87 | } 88 | 89 | Stream.find(criteria, null, {sort: {title: 1}}) 90 | .populate('creator') 91 | .skip(parseInt(req.query.page) * System.config.settings.perPage) 92 | .limit(System.config.settings.perPage+1) 93 | .exec(function(err, streams) { 94 | if (err) { 95 | json.unhappy(err, res); 96 | } else { 97 | var morePages = System.config.settings.perPage < streams.length; 98 | if (morePages) { 99 | streams.pop(); 100 | } 101 | json.happy({ 102 | records: streams, 103 | morePages: morePages 104 | }, res); 105 | } 106 | }); 107 | }; 108 | 109 | /** 110 | * Return info about single stream 111 | * @param {Object} req The req object 112 | * @param {Object} res The res object 113 | * @return {Void} 114 | */ 115 | obj.single = function(req, res) { 116 | Stream.findOne({ 117 | _id: req.params.streamId 118 | }) 119 | .populate('creator') 120 | .exec(function(err, stream) { 121 | if (err) { 122 | return json.unhappy(err, res); 123 | } else if (stream) { 124 | 125 | return json.happy({ 126 | record: stream 127 | }, res); 128 | } else { 129 | return json.unhappy({message: 'Stream not found'}, res); 130 | } 131 | }); 132 | }; 133 | 134 | /** 135 | * Subscribe to a stream 136 | * @param {Object} req The request object 137 | * @param {Object} res The response object 138 | * @return {Void} 139 | */ 140 | obj.subscribe = function (req, res) { 141 | Stream.findOne({_id: req.params.streamId}) 142 | .populate('creator') 143 | .exec(function(err, stream) { 144 | if (err) { 145 | return json.unhappy(err, res); 146 | } else if (stream) { 147 | if (stream.subscribers.indexOf(req.user._id) !== -1) { 148 | return json.unhappy('You have already subscribers to the group', res); 149 | } 150 | stream.subscribe(req.user._id); 151 | stream.save(function(err, item) { 152 | if (err) { 153 | return json.unhappy(err, res); 154 | } 155 | json.happy({ 156 | record: item 157 | }, res); 158 | }); 159 | 160 | } else { 161 | return json.unhappy({message: 'Stream not found'}, res); 162 | } 163 | }); 164 | }; 165 | 166 | /** 167 | * Unsubscribe to a stream 168 | * @param {Object} req The request object 169 | * @param {Object} res The response object 170 | * @return {Void} 171 | */ 172 | obj.unsubscribe = function (req, res) { 173 | Stream.findOne({_id: req.params.streamId}) 174 | .populate('creator') 175 | .exec(function(err, stream) { 176 | if (err) { 177 | return json.unhappy(err, res); 178 | } else if (stream) { 179 | if (stream.subscribers.indexOf(req.user._id) !== -1) { 180 | stream.subscribers.splice(stream.subscribers.indexOf(req.user._id), 1); 181 | } else { 182 | return json.unhappy('You have ubsubscribed', res); 183 | } 184 | 185 | stream.save(function(err, item) { 186 | if (err) { 187 | return json.unhappy(err, res); 188 | } 189 | json.happy({ 190 | record: item 191 | }, res); 192 | }); 193 | 194 | } else { 195 | return json.unhappy({message: 'Stream not found'}, res); 196 | } 197 | }); 198 | }; 199 | 200 | /** 201 | * Remove the stream 202 | * @param {Object} req The request object 203 | * @param {Object} res The response object 204 | * @return {Void} 205 | */ 206 | obj.remove = function (req, res) { 207 | 208 | }; 209 | 210 | return obj; 211 | }; -------------------------------------------------------------------------------- /modules/users/public/views/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

{{systemSettings.tagline}}

4 |

Becoming a member is quick & easy.

5 | 6 | 7 |
8 | 9 | 10 | 11 |
12 |
This is required!
13 |
That's too long!
14 |
That's too short!
15 |
16 |
17 | 18 | 19 | 20 | 21 |
22 |
This is required!
23 |
That's too long!
24 |
That's too short!
25 |
26 |
27 | 28 | 29 | Login 30 | 31 |
32 | Forgot Password? 33 |
34 | 35 |
36 |
37 | 38 | 39 | 40 |
41 |
This is required!
42 |
That's too long!
43 |
That's too short!
44 |
45 |
46 | 47 | 48 | 49 | 50 |
51 |
This is required!
52 |
That's too long!
53 |
That's too short!
54 |
55 |
56 | 57 | 58 | 59 | 60 |
61 |
This is required!
62 |
That's too long!
63 |
That's too short!
64 |
65 |
66 | 67 | 68 | 69 | 70 |
71 |
This is required!
72 |
That's too long!
73 |
That's too short!
74 |
75 |
76 | 77 | 78 | 79 | 80 |
81 |
This is required!
82 |
That's too long!
83 |
That's too short!
84 |
85 |
86 | 87 | 88 | 89 | 90 |
91 |
This is required!
92 |
That's too long!
93 |
That's too short!
94 |
95 |
96 | 97 | Register 98 | 99 |
100 |
101 | 102 | 103 |

104 | 105 | Awesome! 106 |

107 |

As last step of the registration process, please check your email to activate your account and login.

108 |
109 |

Didn't get an email yet?

110 | Resend Email 111 |
112 | 113 |
114 |
115 |
116 |
117 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 | 17 |

Flying

18 |

Hold on, tightly...

19 |
20 |
21 | 22 | 23 | 26 |

{{systemSettings ? systemSettings.workplace : 'AtWork'}}

27 |
28 | 37 | 38 |
39 |
40 | 41 | 42 |
43 | {{ badges.messageBadge }} 44 | 47 | 48 | 49 | 50 | 51 | {{systemSettings ? systemSettings.workplace : 'AtWork'}} 52 |   {{barTitle}} 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 |
61 | 62 | {{notificationCount}} 63 | 64 | 65 |
66 | 67 | 68 | 69 | 70 | 71 | {{item.actor.name}} 72 |
73 |

{{::item.display.text}}

74 | {{::item.created | date:'medium'}} 75 |
76 |
77 |
78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
86 |

No unread notifications.

87 |
88 |
89 |
90 |
91 | 92 |
93 |
94 |
95 | 96 | 99 | 100 |
101 |
102 | 103 | 104 |
105 | 106 | 107 |
108 |
109 |
110 | 111 |
112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /modules/streams/public/streams.controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('atwork.streams') 4 | .controller('StreamsPurposeCtrl', [ 5 | '$scope', 6 | '$rootScope', 7 | '$routeParams', 8 | '$timeout', 9 | 'appAuth', 10 | 'appToast', 11 | 'appStorage', 12 | 'appLocation', 13 | 'appWebSocket', 14 | 'appStreams', 15 | function($scope, $rootScope, $routeParams, $timeout, appAuth, appToast, appStorage, appLocation, appWebSocket, appStreams) { 16 | var streamId = $routeParams.streamId; 17 | 18 | $scope.getStream = function (streamId) { 19 | var stream = appStreams.single.get({streamId: streamId}, function() { 20 | $scope.stream = stream.res.record; 21 | $scope.stream.purpose = $scope.stream.purpose || 'Set the stream\'s purpose here...' 22 | }); 23 | }; 24 | 25 | $scope.updateStreamPurpose = function (isValid) { 26 | var stream = appStreams.single.get({streamId: streamId}, function() { 27 | stream = angular.extend(stream, $scope.stream); 28 | stream.$save(function(response) { 29 | appToast('Stream purpose updated.'); 30 | }); 31 | }); 32 | }; 33 | 34 | if (streamId) { 35 | $scope.getStream(streamId); 36 | } 37 | } 38 | ]) 39 | .controller('StreamsCtrl', [ 40 | '$scope', 41 | '$rootScope', 42 | '$routeParams', 43 | '$timeout', 44 | 'appAuth', 45 | 'appToast', 46 | 'appStorage', 47 | 'appLocation', 48 | 'appWebSocket', 49 | 'appStreams', 50 | function($scope, $rootScope, $routeParams, $timeout, appAuth, appToast, appStorage, appLocation, appWebSocket, appStreams) { 51 | $scope.streams = []; 52 | $scope.actions = {}; 53 | // $scope.toSubscribe = ''; 54 | 55 | $scope.clearThis = function(item) { 56 | console.log($scope); 57 | console.log(item); 58 | $timeout(function() { 59 | // $scope.toSubscribe = undefined; 60 | }, 2000); 61 | }; 62 | 63 | $scope.processMoreStreams = function(selected) { 64 | $timeout(function() { 65 | if (selected === '1') { 66 | $scope.createNew(); 67 | // $scope.toSubscribe = undefined; 68 | } else { 69 | var selectedStreamData = appStreams.single.get({streamId: selected}, function() { 70 | selectedStreamData.$subscribe({streamId: selected}, function() { 71 | $scope.updateStreams({reload: true}); 72 | appToast('You have subscribed to the new stream.'); 73 | appLocation.url('/stream/' + selected); 74 | // $scope.toSubscribe = undefined; 75 | }); 76 | }); 77 | } 78 | }, 500); 79 | }; 80 | 81 | /** 82 | * Unsubscribe from a specific stream 83 | * @return {Void} 84 | */ 85 | $scope.unsubscribe = function(stream) { 86 | var streamId = stream._id; 87 | var selectedStreamData = appStreams.single.get({streamId: streamId}, function() { 88 | selectedStreamData.$unsubscribe({streamId: streamId}, function() { 89 | $scope.updateStreams({reload: true}); 90 | appToast('You have unsubscribed from that stream.'); 91 | }); 92 | }); 93 | }; 94 | 95 | $scope.updateStreams = function (options) { 96 | options = options || {}; 97 | 98 | var streamsData = appStreams.single.get({subscribed: true}, function() { 99 | /** 100 | * Check if the feed needs to reload 101 | */ 102 | if (options.reload) { 103 | $scope.streams = []; 104 | } 105 | 106 | /** 107 | * Check whether to append to feed (at bottom) or insert (at top) 108 | */ 109 | if (!options.append) { 110 | $scope.streams = streamsData.res.records.concat($scope.streams); 111 | } else { 112 | $scope.streams = $scope.streams.concat(streamsData.res.records); 113 | } 114 | /** 115 | * Check if there are more pages 116 | * @type {Boolean} 117 | */ 118 | $scope.noMoreStreams = !streamsData.res.morePages; 119 | /** 120 | * Set the updated timestamp 121 | */ 122 | $scope.lastUpdated = Date.now(); 123 | }); 124 | 125 | var moreStreamsData = appStreams.single.get({unsubscribed: true}, function() { 126 | $scope.moreStreams = moreStreamsData.res.records; 127 | }); 128 | }; 129 | 130 | $scope.createNew = function () { 131 | $scope.actions.createNew = true; 132 | }; 133 | 134 | /** 135 | * Create a new post 136 | * @param {Boolean} isValid Will be true if form validation passes 137 | * @return {Void} 138 | */ 139 | $scope.create = function (isValid) { 140 | if (isValid) { 141 | var stream = new appStreams.single({ 142 | title: this.newStreamName 143 | }); 144 | stream.$save(function(response) { 145 | if (response.success) { 146 | appWebSocket.conn.emit('stream', response.res._id); 147 | $scope.actions.createNew = false; 148 | $scope.updateStreams({reload: true}); 149 | appLocation.url('/stream/' + response.res._id); 150 | } else { 151 | $scope.failure = true; 152 | appToast(response.res.message); 153 | } 154 | }); 155 | } else { 156 | appToast('Bummer! Is the stream name good?'); 157 | } 158 | }; 159 | 160 | /** 161 | * If a stream has a new msg, show a badge 162 | */ 163 | $rootScope.$on('stream-message', function(e, data) { 164 | angular.forEach($scope.streams, function(stream) { 165 | if (data.streamId === stream._id) { 166 | stream.unread = stream.unread ? stream.unread++ : 1; 167 | } 168 | }); 169 | $scope.$digest(); 170 | }); 171 | 172 | /** 173 | * Clear the stream's badge 174 | * @param {Object} stream The stream object 175 | * @return {Void} 176 | */ 177 | $scope.clearBadge = function(stream) { 178 | stream.unread = 0; 179 | }; 180 | 181 | /** 182 | * Listen to socket 183 | */ 184 | appWebSocket.conn.on('stream', function() { 185 | appToast('Woot! There is a new stream available!'); 186 | $scope.updateStreams({reload: true}); 187 | }); 188 | 189 | /** 190 | * Get the initial list 191 | */ 192 | $scope.updateStreams(); 193 | } 194 | ]) 195 | ; 196 | -------------------------------------------------------------------------------- /modules/chats/server/controllers/chats.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Chat = mongoose.model('Chat'); 3 | 4 | module.exports = function(System) { 5 | var obj = {}; 6 | var json = System.plugins.JSON; 7 | var event = System.plugins.event; 8 | var sck = System.webSocket; 9 | 10 | /** 11 | * Chat related sockets 12 | */ 13 | sck.on('connection', function(socket){ 14 | socket.on('markAccessed', function(data) { 15 | Chat 16 | .findOne({_id: data.chatId}) 17 | .exec(function(err, chat) { 18 | chat.doAccess({_id: data.userId}); 19 | chat.calculateUnread(); 20 | chat.save(); 21 | }); 22 | }); 23 | }); 24 | 25 | 26 | /** 27 | * Event based notifications 28 | */ 29 | ['chatMessage'].map(function(action) { 30 | event.on(action, function(data) { 31 | var chat = data.chat; 32 | var actor = data.actor; 33 | var chatMessage = data.message; 34 | chat.notifyUsers({ 35 | chatId: chat._id, 36 | actorId: actor._id, 37 | type: action, 38 | chatMessage: chatMessage, 39 | config: { 40 | systemLevel: true 41 | } 42 | }, System); 43 | }); 44 | }); 45 | 46 | /** 47 | * Create a stream 48 | * @param {Object} req The request object 49 | * @param {Object} res The response object 50 | * @return {Void} 51 | */ 52 | obj.create = function (req, res) { 53 | 54 | /** 55 | * Always sort, so whoever creates, it has participants in the same order 56 | */ 57 | if (req.body.participants) { 58 | req.body.participants.sort(function(a, b) { 59 | return a < b; 60 | }); 61 | } 62 | 63 | var criteria = {}; 64 | if (req.body.chatId) { 65 | criteria = { 66 | _id: req.body.chatId 67 | }; 68 | } else { 69 | criteria = { 70 | participants: req.body.participants 71 | }; 72 | } 73 | Chat.findOne(criteria) 74 | .populate('creator') 75 | .populate('participants') 76 | .populate('messages') 77 | .populate('messages.creator') 78 | .exec(function(err, chat) { 79 | if (err) { 80 | return json.unhappy(err, res); 81 | } else if (chat) { 82 | chat.doAccess(req.user); 83 | chat.calculateUnread(); 84 | chat.save(function(err, chat) { 85 | return json.happy({ 86 | record: chat 87 | }, res); 88 | }); 89 | } else { 90 | var chat = new Chat(req.body); 91 | chat.creator = req.user._id; 92 | 93 | /** 94 | * Mark as first accessed for each participant 95 | */ 96 | req.body.participants.map(function(userId) { 97 | chat.doAccess({_id: userId}); 98 | }); 99 | chat.save(function(err) { 100 | if (err) { 101 | return json.unhappy(err, res); 102 | } 103 | return json.happy({ 104 | record: chat 105 | }, res); 106 | }); 107 | } 108 | }); 109 | }; 110 | 111 | /** 112 | * Create a stream 113 | * @param {Object} req The request object 114 | * @param {Object} res The response object 115 | * @return {Void} 116 | */ 117 | obj.message = function (req, res) { 118 | Chat.findOne({ 119 | _id: req.params.chatId 120 | }) 121 | .populate('creator') 122 | .populate('participants') 123 | .populate('messages') 124 | .populate('messages.creator') 125 | .exec(function(err, chat) { 126 | if (err) { 127 | return json.unhappy(err, res); 128 | } else if (chat) { 129 | chat.messages.unshift({ 130 | message: req.body.message, 131 | creator: req.user._id 132 | }); 133 | chat.doAccess(req.user); 134 | chat.calculateUnread(); 135 | chat.save(function(err, chat) { 136 | chat 137 | .populate('messages messages.creator', function(err, chat) { 138 | event.trigger('chatMessage', {chat: chat, message: chat.messages[0], actor: req.user}); 139 | return json.happy({ 140 | record: chat 141 | }, res); 142 | }) 143 | }) 144 | } else { 145 | return json.unhappy({message: 'Chat not found'}, res); 146 | } 147 | }); 148 | }; 149 | 150 | /** 151 | * Modify a stream 152 | * @param {Object} req The request object 153 | * @param {Object} res The response object 154 | * @return {Void} 155 | */ 156 | obj.addParticipant = function (req, res) { 157 | Chat.findOne({ 158 | _id: req.params.chatId 159 | }) 160 | .populate('creator') 161 | .exec(function(err, chat) { 162 | if (err) { 163 | return json.unhappy(err, res); 164 | } 165 | chat.participants.push = req.body.userId; 166 | chat.save(function(err) { 167 | if (err) { 168 | return json.unhappy(err, res); 169 | } 170 | return json.happy(chat, res); 171 | }); 172 | }); 173 | }; 174 | 175 | /** 176 | * List all streams 177 | * @param {Object} req The request object 178 | * @param {Object} res The response object 179 | * @return {Void} 180 | */ 181 | obj.list = function (req, res) { 182 | var user = req.user; 183 | 184 | var criteria = { 185 | participants: req.user 186 | }; 187 | 188 | Chat.find(criteria, null, {sort: {modified: 1}}) 189 | .populate('creator') 190 | .populate('participants') 191 | .skip(parseInt(req.query.page) * System.config.settings.perPage) 192 | .limit(System.config.settings.perPage+1) 193 | .exec(function(err, chats) { 194 | chats.map(function(chat) { 195 | chat.calculateUnreadFor(req.user); 196 | }); 197 | if (err) { 198 | json.unhappy(err, res); 199 | } else { 200 | var morePages = System.config.settings.perPage < chats.length; 201 | if (morePages) { 202 | chats.pop(); 203 | } 204 | json.happy({ 205 | records: chats, 206 | morePages: morePages 207 | }, res); 208 | } 209 | }); 210 | }; 211 | 212 | /** 213 | * Return info about single stream 214 | * @param {Object} req The req object 215 | * @param {Object} res The res object 216 | * @return {Void} 217 | */ 218 | obj.single = function(req, res) { 219 | Chat.findOne({ 220 | _id: req.params.chatId 221 | }) 222 | .populate('creator') 223 | .populate('participants') 224 | .populate('messages') 225 | .populate('messages.creator') 226 | .exec(function(err, chat) { 227 | if (err) { 228 | return json.unhappy(err, res); 229 | } else if (chat) { 230 | 231 | return json.happy({ 232 | record: chat 233 | }, res); 234 | } else { 235 | return json.unhappy({message: 'Chat not found'}, res); 236 | } 237 | }); 238 | }; 239 | 240 | return obj; 241 | }; -------------------------------------------------------------------------------- /modules/users/public/views/profile.html: -------------------------------------------------------------------------------- 1 |