├── 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 |
--------------------------------------------------------------------------------
/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 |
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 |
7 |
8 |
9 | $ {{ chat.participants[0].name === user.name ? chat.participants[1].name : chat.participants[0].name }} {{ chat.unread }}
10 |
11 |
16 |
17 |
18 |
19 |
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 |
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 |
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 |
16 |
17 |
18 |
19 |
20 |
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 | 
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 | 
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 |
51 |
52 |
--------------------------------------------------------------------------------
/modules/users/public/views/users-invite-dialog.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Invite
4 |
5 |
6 |
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 |
40 |
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 | What do you want to share?
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 |
56 |
57 |
58 |
59 |
Filter
60 |
61 | Type keywords to filter...
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 |
4 |
5 |
{{::item.creator.name}}
6 |
7 |
8 |
9 | Like
10 | Liked
11 | ({{item.likeCount}})
12 |
13 |
-
14 |
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 | Write your reply...
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 |
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 | Email Address
10 |
11 |
12 |
This is required!
13 |
That's too long!
14 |
That's too short!
15 |
16 |
17 |
18 |
19 | Password
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 | Full Name
39 |
40 |
41 |
This is required!
42 |
That's too long!
43 |
That's too short!
44 |
45 |
46 |
47 |
48 | Email Address
49 |
50 |
51 |
This is required!
52 |
That's too long!
53 |
That's too short!
54 |
55 |
56 |
57 |
58 | Username (required for @mentions)
59 |
60 |
61 |
This is required!
62 |
That's too long!
63 |
That's too short!
64 |
65 |
66 |
67 |
68 | Designation
69 |
70 |
71 |
This is required!
72 |
That's too long!
73 |
That's too short!
74 |
75 |
76 |
77 |
78 | Password
79 |
80 |
81 |
This is required!
82 |
That's too long!
83 |
That's too short!
84 |
85 |
86 |
87 |
88 | Confirm Password
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 |
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 |
2 |
3 |
4 |
5 | Tip: Click photo to edit
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
{{::profile.name}}
14 |
@{{::profile.username}}
15 |
{{::profile.designation}}
16 |
17 |
18 |
19 |
20 | Full Name
21 |
22 |
23 |
This is required!
24 |
That's too long!
25 |
That's too short!
26 |
27 |
28 |
29 | Designation
30 |
31 |
32 |
This is required!
33 |
That's too long!
34 |
That's too short!
35 |
36 |
37 |
38 | Update
39 |
40 |
41 | Cancel
42 |
43 |
44 |
45 |
@{{::profile.username}}
46 |
47 |
48 |
49 |
50 |
51 |
52 | Follow
53 |
54 |
55 |
56 | Message
57 |
58 |
59 |
60 | Unfollow
61 |
62 |
63 |
64 | Edit Profile
65 |
66 |
67 |
68 | Password
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
{{item.name}}
103 |
@{{item.username}}
104 |
{{item.designation}}
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | No followers yet.
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
{{item.name}}
127 |
@{{item.username}}
128 |
{{item.designation}}
129 |
130 |
131 |
132 |
133 |
134 |
135 | Not following anyone yet.
136 |
137 |
138 |
139 |
141 |
142 |
199 |
Notification
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
Hey <%= name %>,
213 |
<%= message %>
214 | <% if (locals.message2) { %>
215 |
<%= message2 %>
216 | <% } %>
217 |
<%= action %>
218 |
Cheers!
219 | <% if (typeof workplace !== 'undefined') { %>
220 |
<%= workplace %>
221 | <% } %>
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
248 |
249 |
250 |
251 |