From ab81d61bd3cc629d55e0b9eeb8914f2254e191db Mon Sep 17 00:00:00 2001 From: Amos Haviv Date: Mon, 10 Nov 2014 23:12:33 +0200 Subject: [PATCH 001/131] New 0.4 version --- .editorconfig | 2 +- .gitignore | 2 - .jshintrc | 20 +- README.md | 23 +- app/controllers/core.server.controller.js | 11 - .../users/users.profile.server.controller.js | 56 -- app/routes/articles.server.routes.js | 22 - app/routes/core.server.routes.js | 7 - app/routes/users.server.routes.js | 57 -- bower.json | 9 +- config/assets/default.js | 47 ++ config/assets/development.js | 5 + config/assets/production.js | 23 + config/assets/test.js | 9 + config/config.js | 204 ++++-- config/env/all.js | 51 -- config/env/default.js | 14 + config/env/development.js | 57 +- config/env/production.js | 101 ++- config/env/secure.js | 68 -- config/env/test.js | 19 +- config/express.js | 164 ----- config/init.js | 33 - config/lib/express.js | 251 +++++++ config/lib/mongoose.js | 36 + config/lib/socket.io.js | 61 ++ config/logger.js | 36 - gruntfile.js | 206 +++--- gulpfile.js | 187 ++++++ karma.conf.js | 16 +- .../client}/articles.client.module.js | 0 .../client/config/articles.client.config.js | 25 + .../client}/config/articles.client.routes.js | 21 +- .../controllers/articles.client.controller.js | 0 .../services/articles.client.service.js | 4 +- .../views/create-article.client.view.html | 6 +- .../views/edit-article.client.view.html | 16 +- .../views/list-articles.client.view.html | 6 +- .../views/view-article.client.view.html | 4 +- .../controllers/articles.server.controller.js | 22 +- .../server}/models/article.server.model.js | 0 .../server/policies/articles.server.policy.js | 72 ++ .../server/routes/articles.server.routes.js | 23 + .../articles.client.controller.tests.js | 10 +- .../articles/tests/e2e/articles.e2e.tests.js | 10 + .../server/article.server.model.tests.js | 2 +- .../server/article.server.routes.tests.js | 54 +- .../chat/client/chat.client.module.js | 2 +- .../chat/client/config/chat.client.config.js | 12 + .../chat/client/config/chat.client.routes.js | 12 + .../controllers/chat.client.controller.js | 34 + modules/chat/client/css/chat.css | 18 + .../chat/client/views/chat.client.view.html | 28 + .../sockets/chat.server.socket.config.js | 34 + .../client/chat.client.controller.tests.js | 10 + modules/chat/tests/e2e/chat.e2e.tests.js | 8 + .../chat/tests/server/chat.socket.tests.js | 8 + {public => modules/core/client/app}/config.js | 4 +- .../core/client/app/init.js | 4 +- .../core/client}/config/core.client.routes.js | 0 .../controllers/header.client.controller.js | 13 +- .../controllers/home.client.controller.js | 1 - .../core/client}/core.client.module.js | 0 modules/core/client/css/core.css | 33 + .../core/client}/img/brand/favicon.ico | Bin .../core/client}/img/brand/logo.png | Bin .../core/client}/img/loaders/loader.gif | Bin .../client/services/menus.client.service.js | 179 +++++ .../services/socket.io.client.service.js | 38 ++ .../client}/views/header.client.view.html | 40 +- .../core/client}/views/home.client.view.html | 6 +- .../controllers/core.server.controller.js | 28 + .../controllers/errors.server.controller.js | 8 +- .../core/server/routes/core.server.routes.js | 13 + .../core/server}/views/404.server.view.html | 0 .../core/server}/views/500.server.view.html | 0 .../core/server}/views/index.server.view.html | 0 .../server}/views/layout.server.view.html | 22 +- .../client/header.client.controller.tests.js | 0 .../client/home.client.controller.tests.js | 0 .../client}/config/users.client.config.js | 10 +- .../client/config/users.client.routes.js | 69 ++ .../authentication.client.controller.js | 4 +- .../controllers/password.client.controller.js | 4 +- .../controllers/settings.client.controller.js | 10 +- .../change-password.client.controller.js | 20 + ...hange-profile-picture.client.controller.js | 72 ++ .../edit-profile.client.controller.js | 24 + ...anage-social-accounts.client.controller.js | 38 ++ .../settings/settings.client.controller.js | 10 + modules/users/client/css/users.css | 41 ++ .../users/client}/img/buttons/facebook.png | Bin .../users/client}/img/buttons/github.png | Bin .../users/client}/img/buttons/google.png | Bin .../users/client}/img/buttons/linkedin.png | Bin .../users/client}/img/buttons/twitter.png | Bin modules/users/client/img/profile/default.png | Bin 0 -> 65838 bytes .../117bf261c0152c79a428db522715cdd7.png | Bin 0 -> 3835 bytes .../5323300de0498510f42b6bbb57800b77.png | Bin 0 -> 3835 bytes .../97ddbe2719b2f5da90c24b8194cca2c0.png | Bin 0 -> 3835 bytes .../edb5fdc047848ce1089c0e78693f7652.png | Bin 0 -> 23176 bytes .../services/authentication.client.service.js | 1 + .../client}/services/users.client.service.js | 2 +- modules/users/client/users.client.module.js | 4 + .../authentication.client.view.html | 21 + .../authentication/signin.client.view.html | 30 + .../authentication/signup.client.view.html | 42 ++ .../password/forgot-password.client.view.html | 22 + .../reset-password-invalid.client.view.html | 4 + .../reset-password-success.client.view.html | 4 +- .../password/reset-password.client.view.html | 26 + .../settings/change-password.client.view.html | 7 +- .../change-profile-picture.client.view.html | 26 + .../settings/edit-profile.client.view.html | 7 +- .../manage-social-accounts.client.view.html | 44 ++ .../views/settings/settings.client.view.html | 26 + .../server/config}/strategies/facebook.js | 9 +- .../users/server/config}/strategies/github.js | 8 +- .../users/server/config}/strategies/google.js | 8 +- .../server/config}/strategies/linkedin.js | 10 +- .../users/server/config}/strategies/local.js | 4 +- .../server/config}/strategies/twitter.js | 8 +- .../server/config/users.server.config.js | 19 +- .../controllers/users.server.controller.js | 0 .../users.authentication.server.controller.js | 6 +- .../users.authorization.server.controller.js | 0 .../users/users.password.server.controller.js | 20 +- .../users/users.profile.server.controller.js | 97 +++ .../users/server}/models/user.server.model.js | 12 +- .../users/server/routes/auth.server.routes.js | 53 ++ .../server/routes/users.server.routes.js | 21 + ...et-password-confirm-email.server.view.html | 3 + .../reset-password-email.server.view.html | 4 + .../authentication.client.controller.tests.js | 10 +- modules/users/tests/e2e/users.e2e.tests.js | 13 + .../tests/server/user.server.model.tests.js | 2 +- package.json | 37 +- protractor.conf.js | 6 + public/dist/application.js | 622 ------------------ public/dist/application.min.css | 6 +- public/dist/application.min.js | 26 +- .../articles/config/articles.client.config.js | 11 - public/modules/core/css/core.css | 15 - .../core/services/menus.client.service.js | 166 ----- .../users/config/users.client.routes.js | 45 -- public/modules/users/css/users.css | 14 - .../authentication/signin.client.view.html | 45 -- .../authentication/signup.client.view.html | 54 -- .../password/forgot-password.client.view.html | 22 - .../reset-password-invalid.client.view.html | 4 - .../password/reset-password.client.view.html | 26 - .../settings/social-accounts.client.view.html | 29 - server.js | 42 +- 153 files changed, 2606 insertions(+), 2066 deletions(-) delete mode 100644 app/controllers/core.server.controller.js delete mode 100644 app/controllers/users/users.profile.server.controller.js delete mode 100644 app/routes/articles.server.routes.js delete mode 100644 app/routes/core.server.routes.js delete mode 100644 app/routes/users.server.routes.js create mode 100644 config/assets/default.js create mode 100644 config/assets/development.js create mode 100644 config/assets/production.js create mode 100644 config/assets/test.js delete mode 100644 config/env/all.js create mode 100644 config/env/default.js delete mode 100644 config/env/secure.js delete mode 100755 config/express.js delete mode 100644 config/init.js create mode 100755 config/lib/express.js create mode 100644 config/lib/mongoose.js create mode 100644 config/lib/socket.io.js delete mode 100644 config/logger.js create mode 100644 gulpfile.js rename {public/modules/articles => modules/articles/client}/articles.client.module.js (100%) create mode 100644 modules/articles/client/config/articles.client.config.js rename {public/modules/articles => modules/articles/client}/config/articles.client.routes.js (64%) rename {public/modules/articles => modules/articles/client}/controllers/articles.client.controller.js (100%) rename {public/modules/articles => modules/articles/client}/services/articles.client.service.js (82%) rename {public/modules/articles => modules/articles/client}/views/create-article.client.view.html (83%) rename {public/modules/articles => modules/articles/client}/views/edit-article.client.view.html (59%) rename {public/modules/articles => modules/articles/client}/views/list-articles.client.view.html (74%) rename {public/modules/articles => modules/articles/client}/views/view-article.client.view.html (87%) rename {app => modules/articles/server}/controllers/articles.server.controller.js (81%) rename {app => modules/articles/server}/models/article.server.model.js (100%) create mode 100644 modules/articles/server/policies/articles.server.policy.js create mode 100644 modules/articles/server/routes/articles.server.routes.js rename public/modules/articles/tests/articles.client.controller.test.js => modules/articles/tests/client/articles.client.controller.tests.js (92%) create mode 100644 modules/articles/tests/e2e/articles.e2e.tests.js rename app/tests/article.server.model.test.js => modules/articles/tests/server/article.server.model.tests.js (99%) rename app/tests/article.server.routes.test.js => modules/articles/tests/server/article.server.routes.tests.js (86%) rename public/modules/users/users.client.module.js => modules/chat/client/chat.client.module.js (61%) mode change 100755 => 100644 create mode 100644 modules/chat/client/config/chat.client.config.js create mode 100644 modules/chat/client/config/chat.client.routes.js create mode 100644 modules/chat/client/controllers/chat.client.controller.js create mode 100644 modules/chat/client/css/chat.css create mode 100644 modules/chat/client/views/chat.client.view.html create mode 100644 modules/chat/server/sockets/chat.server.socket.config.js create mode 100644 modules/chat/tests/client/chat.client.controller.tests.js create mode 100644 modules/chat/tests/e2e/chat.e2e.tests.js create mode 100644 modules/chat/tests/server/chat.socket.tests.js rename {public => modules/core/client/app}/config.js (90%) rename public/application.js => modules/core/client/app/init.js (92%) rename {public/modules/core => modules/core/client}/config/core.client.routes.js (100%) rename {public/modules/core => modules/core/client}/controllers/header.client.controller.js (67%) rename {public/modules/core => modules/core/client}/controllers/home.client.controller.js (98%) rename {public/modules/core => modules/core/client}/core.client.module.js (100%) create mode 100644 modules/core/client/css/core.css rename {public/modules/core => modules/core/client}/img/brand/favicon.ico (100%) rename {public/modules/core => modules/core/client}/img/brand/logo.png (100%) rename {public/modules/core => modules/core/client}/img/loaders/loader.gif (100%) create mode 100644 modules/core/client/services/menus.client.service.js create mode 100644 modules/core/client/services/socket.io.client.service.js rename {public/modules/core => modules/core/client}/views/header.client.view.html (50%) rename {public/modules/core => modules/core/client}/views/home.client.view.html (90%) create mode 100644 modules/core/server/controllers/core.server.controller.js rename {app => modules/core/server}/controllers/errors.server.controller.js (89%) create mode 100644 modules/core/server/routes/core.server.routes.js rename {app => modules/core/server}/views/404.server.view.html (100%) rename {app => modules/core/server}/views/500.server.view.html (100%) rename {app => modules/core/server}/views/index.server.view.html (100%) rename {app => modules/core/server}/views/layout.server.view.html (75%) rename public/modules/core/tests/header.client.controller.test.js => modules/core/tests/client/header.client.controller.tests.js (100%) rename public/modules/core/tests/home.client.controller.test.js => modules/core/tests/client/home.client.controller.tests.js (100%) rename {public/modules/users => modules/users/client}/config/users.client.config.js (77%) create mode 100755 modules/users/client/config/users.client.routes.js rename {public/modules/users => modules/users/client}/controllers/authentication.client.controller.js (84%) rename {public/modules/users => modules/users/client}/controllers/password.client.controller.js (86%) rename {public/modules/users => modules/users/client}/controllers/settings.client.controller.js (92%) create mode 100644 modules/users/client/controllers/settings/change-password.client.controller.js create mode 100644 modules/users/client/controllers/settings/change-profile-picture.client.controller.js create mode 100644 modules/users/client/controllers/settings/edit-profile.client.controller.js create mode 100644 modules/users/client/controllers/settings/manage-social-accounts.client.controller.js create mode 100644 modules/users/client/controllers/settings/settings.client.controller.js create mode 100644 modules/users/client/css/users.css rename {public/modules/users => modules/users/client}/img/buttons/facebook.png (100%) rename {public/modules/users => modules/users/client}/img/buttons/github.png (100%) rename {public/modules/users => modules/users/client}/img/buttons/google.png (100%) rename {public/modules/users => modules/users/client}/img/buttons/linkedin.png (100%) rename {public/modules/users => modules/users/client}/img/buttons/twitter.png (100%) create mode 100644 modules/users/client/img/profile/default.png create mode 100644 modules/users/client/img/profile/uploads/117bf261c0152c79a428db522715cdd7.png create mode 100644 modules/users/client/img/profile/uploads/5323300de0498510f42b6bbb57800b77.png create mode 100644 modules/users/client/img/profile/uploads/97ddbe2719b2f5da90c24b8194cca2c0.png create mode 100644 modules/users/client/img/profile/uploads/edb5fdc047848ce1089c0e78693f7652.png rename {public/modules/users => modules/users/client}/services/authentication.client.service.js (98%) rename {public/modules/users => modules/users/client}/services/users.client.service.js (83%) create mode 100755 modules/users/client/users.client.module.js create mode 100644 modules/users/client/views/authentication/authentication.client.view.html create mode 100644 modules/users/client/views/authentication/signin.client.view.html create mode 100644 modules/users/client/views/authentication/signup.client.view.html create mode 100644 modules/users/client/views/password/forgot-password.client.view.html create mode 100644 modules/users/client/views/password/reset-password-invalid.client.view.html rename {public/modules/users => modules/users/client}/views/password/reset-password-success.client.view.html (53%) create mode 100644 modules/users/client/views/password/reset-password.client.view.html rename {public/modules/users => modules/users/client}/views/settings/change-password.client.view.html (86%) create mode 100644 modules/users/client/views/settings/change-profile-picture.client.view.html rename {public/modules/users => modules/users/client}/views/settings/edit-profile.client.view.html (87%) create mode 100644 modules/users/client/views/settings/manage-social-accounts.client.view.html create mode 100644 modules/users/client/views/settings/settings.client.view.html rename {config => modules/users/server/config}/strategies/facebook.js (79%) rename {config => modules/users/server/config}/strategies/github.js (84%) rename {config => modules/users/server/config}/strategies/google.js (85%) rename {config => modules/users/server/config}/strategies/linkedin.js (84%) rename {config => modules/users/server/config}/strategies/local.js (86%) rename {config => modules/users/server/config}/strategies/twitter.js (82%) rename config/passport.js => modules/users/server/config/users.server.config.js (57%) mode change 100755 => 100644 rename {app => modules/users/server}/controllers/users.server.controller.js (100%) rename {app => modules/users/server}/controllers/users/users.authentication.server.controller.js (96%) rename {app => modules/users/server}/controllers/users/users.authorization.server.controller.js (100%) rename {app => modules/users/server}/controllers/users/users.password.server.controller.js (91%) create mode 100644 modules/users/server/controllers/users/users.profile.server.controller.js rename {app => modules/users/server}/models/user.server.model.js (94%) create mode 100644 modules/users/server/routes/auth.server.routes.js create mode 100644 modules/users/server/routes/users.server.routes.js rename {app/views => modules/users/server}/templates/reset-password-confirm-email.server.view.html (96%) rename {app/views => modules/users/server}/templates/reset-password-email.server.view.html (97%) rename public/modules/users/tests/authentication.client.controller.test.js => modules/users/tests/client/authentication.client.controller.tests.js (89%) create mode 100644 modules/users/tests/e2e/users.e2e.tests.js rename app/tests/user.server.model.test.js => modules/users/tests/server/user.server.model.tests.js (99%) create mode 100644 protractor.conf.js delete mode 100644 public/dist/application.js delete mode 100644 public/modules/articles/config/articles.client.config.js delete mode 100644 public/modules/core/css/core.css delete mode 100644 public/modules/core/services/menus.client.service.js delete mode 100755 public/modules/users/config/users.client.routes.js delete mode 100644 public/modules/users/css/users.css delete mode 100644 public/modules/users/views/authentication/signin.client.view.html delete mode 100644 public/modules/users/views/authentication/signup.client.view.html delete mode 100644 public/modules/users/views/password/forgot-password.client.view.html delete mode 100644 public/modules/users/views/password/reset-password-invalid.client.view.html delete mode 100644 public/modules/users/views/password/reset-password.client.view.html delete mode 100644 public/modules/users/views/settings/social-accounts.client.view.html diff --git a/.editorconfig b/.editorconfig index 636ceea4c0..06dd60bfd1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -33,4 +33,4 @@ indent_style = tab # Standard at: [Makefile] -indent_style = tab \ No newline at end of file +indent_style = tab diff --git a/.gitignore b/.gitignore index 79f6cf2871..721705ea02 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,5 @@ npm-debug.log node_modules/ public/lib -app/tests/coverage/ .bower-*/ .idea/ -config/sslcert/*.pem diff --git a/.jshintrc b/.jshintrc index 4cd07cdcab..b3a00dc361 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,5 +1,7 @@ { "node": true, // Enable globals available when code is running inside of the NodeJS runtime environment. + "mocha": true, // Enable globals available when code is running inside of the Mocha tests. + "jasmine": true, // Enable globals available when code is running inside of the Jasmine tests. "browser": true, // Standard browser globals e.g. `window`, `document`. "esnext": true, // Allow ES.next specific features such as `const` and `let`. "bitwise": false, // Prohibit bitwise operators (&, |, ^, etc.). @@ -18,25 +20,17 @@ "trailing": true, // Prohibit trailing whitespaces. "smarttabs": false, // Suppresses warnings about mixed tabs and spaces "globals": { // Globals variables. - "jasmine": true, "angular": true, + "io": true, "ApplicationConfiguration": true }, "predef": [ // Extra globals. - "define", - "require", - "exports", - "module", - "describe", - "before", - "beforeEach", - "after", - "afterEach", - "it", "inject", - "expect" + "by", + "browser", + "element" ], "indent": 4, // Specify indentation spacing "devel": true, // Allow development statements e.g. `console.log();`. "noempty": true // Prohibit use of empty blocks. -} \ No newline at end of file +} diff --git a/README.md b/README.md index 6f7d5f4f34..dec285d123 100644 --- a/README.md +++ b/README.md @@ -2,21 +2,20 @@ [![Build Status](https://travis-ci.org/meanjs/mean.svg?branch=master)](https://travis-ci.org/meanjs/mean) [![Dependencies Status](https://david-dm.org/meanjs/mean.svg)](https://david-dm.org/meanjs/mean) -[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/meanjs/mean?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) MEAN.JS is a full-stack JavaScript open-source solution, which provides a solid starting point for [MongoDB](http://www.mongodb.org/), [Node.js](http://www.nodejs.org/), [Express](http://expressjs.com/), and [AngularJS](http://angularjs.org/) based applications. The idea is to solve the common issues with connecting those frameworks, build a robust framework to support daily development needs, and help developers use better practices while working with popular JavaScript components. ## Before You Begin Before you begin we recommend you read about the basic building blocks that assemble a MEAN.JS application: * MongoDB - Go through [MongoDB Official Website](http://mongodb.org/) and proceed to their [Official Manual](http://docs.mongodb.org/manual/), which should help you understand NoSQL and MongoDB better. -* Express - The best way to understand express is through its [Official Website](http://expressjs.com/), which has a [Getting Started](http://expressjs.com/starter/installing.html) guide, as well as an [ExpressJS Guide](http://expressjs.com/guide/error-handling.html) guide for general express topics. You can also go through this [StackOverflow Thread](http://stackoverflow.com/questions/8144214/learning-express-for-node-js) for more resources. +* Express - The best way to understand express is through its [Official Website](http://expressjs.com/), particularly [The Express Guide](http://expressjs.com/guide.html); you can also go through this [StackOverflow Thread](http://stackoverflow.com/questions/8144214/learning-express-for-node-js) for more resources. * AngularJS - Angular's [Official Website](http://angularjs.org/) is a great starting point. You can also use [Thinkster Popular Guide](http://www.thinkster.io/), and the [Egghead Videos](https://egghead.io/). * Node.js - Start by going through [Node.js Official Website](http://nodejs.org/) and this [StackOverflow Thread](http://stackoverflow.com/questions/2353818/how-do-i-get-started-with-node-js), which should get you going with the Node.js platform in no time. ## Prerequisites Make sure you have installed all these prerequisites on your development machine. -* Node.js - [Download & Install Node.js](http://www.nodejs.org/download/) and the npm package manager, if you encounter any problems, you can also use this [GitHub Gist](https://gist.github.com/isaacs/579814) to install Node.js. +* Node.js - [Download & Install Node.js](http://www.nodejs.org/download/) and the npm package manager, if you encounter any problems, you can also use this [Github Gist](https://gist.github.com/isaacs/579814) to install Node.js. * MongoDB - [Download & Install MongoDB](http://www.mongodb.org/downloads), and make sure it's running on the default port (27017). * Bower - You're going to use the [Bower Package Manager](http://bower.io/) to manage your front-end packages, in order to install it make sure you've installed Node.js and npm, then install bower globally using npm: @@ -44,7 +43,7 @@ $ git clone https://github.com/meanjs/mean.git meanjs This will clone the latest version of the MEAN.JS repository to a **meanjs** folder. ### Downloading The Repository Zip File -Another way to use the MEAN.JS boilerplate is to download a zip copy from the [master branch on GitHub](https://github.com/meanjs/mean/archive/master.zip). You can also do this using `wget` command: +Another way to use the MEAN.JS boilerplate is to download a zip copy from the [master branch on github](https://github.com/meanjs/mean/archive/master.zip). You can also do this using `wget` command: ``` $ wget https://github.com/meanjs/mean/archive/master.zip -O meanjs.zip; unzip meanjs.zip; rm meanjs.zip ``` @@ -64,7 +63,7 @@ $ npm install This command does a few things: * First it will install the dependencies needed for the application to run. * If you're running in a development environment, it will then also install development dependencies needed for testing and running your application. -* Finally, when the install process is over, npm will initiate a bower install command to install all the front-end modules needed for the application +* Finally, when the install process is over, npm will initiate a bower installcommand to install all the front-end modules needed for the application ## Running Your Application After the install process is over, you'll be able to run your application using Grunt, just run grunt default task: @@ -98,20 +97,12 @@ $ * To enable live reload forward 35729 port and mount /app and /public as volumes: ```bash -$ docker run -p 3000:3000 -p 35729:35729 -v /Users/mdl/workspace/mean-stack/mean/public:/home/mean/public -v /Users/mdl/workspace/mean-stack/mean/app:/home/mean/app --link db:db_1 mean +$ docker run -p 3000:3000 -p 35729:35729 -v /Users/mdl/workspace/mean-stack/mean/public:/home/mean/public -v /Users/mdl/workspa/mean-stack/mean/app:/home/mean/app --link db:db_1 mean ``` -## Running in a secure environment -To run your application in a secure manner you'll need to use OpenSSL and generate a set of self-signed certificates. Unix-based users can use the following commnad: -``` -$ sh generate-ssl-certs.sh -``` -Windows users can follow instructions found [here](http://www.websense.com/support/article/kbarticle/How-to-use-OpenSSL-and-Microsoft-Certification-Authority) -To generate the key and certificate and place them in the *config/sslcert* folder. - ## Getting Started With MEAN.JS -You have your application running but there are a lot of stuff to understand, we recommend you'll go over the [Official Documentation](http://meanjs.org/docs.html). -In the docs we'll try to explain both general concepts of MEAN components and give you some guidelines to help you improve your development process. We tried covering as many aspects as possible, and will keep update it by your request, you can also help us develop the documentation better by checking out the *gh-pages* branch of this repository. +You have your application running but there are a lot of stuff to understand, we recommend you'll go over the [Offical Documentation](http://meanjs.org/docs.html). +In the docs we'll try to explain both general concepts of MEAN components and give you some guidelines to help you improve your development procees. We tried covering as many aspects as possible, and will keep update it by your request, you can also help us develop the documentation better by checking out the *gh-pages* branch of this repository. ## Community * Use to [Offical Website](http://meanjs.org) to learn about changes and the roadmap. diff --git a/app/controllers/core.server.controller.js b/app/controllers/core.server.controller.js deleted file mode 100644 index f2af8e1f7a..0000000000 --- a/app/controllers/core.server.controller.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ -exports.index = function(req, res) { - res.render('index', { - user: req.user || null, - request: req - }); -}; \ No newline at end of file diff --git a/app/controllers/users/users.profile.server.controller.js b/app/controllers/users/users.profile.server.controller.js deleted file mode 100644 index dd38936fce..0000000000 --- a/app/controllers/users/users.profile.server.controller.js +++ /dev/null @@ -1,56 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ -var _ = require('lodash'), - errorHandler = require('../errors.server.controller.js'), - mongoose = require('mongoose'), - passport = require('passport'), - User = mongoose.model('User'); - -/** - * Update user details - */ -exports.update = function(req, res) { - // Init Variables - var user = req.user; - var message = null; - - // For security measurement we remove the roles from the req.body object - delete req.body.roles; - - if (user) { - // Merge existing user - user = _.extend(user, req.body); - user.updated = Date.now(); - user.displayName = user.firstName + ' ' + user.lastName; - - user.save(function(err) { - if (err) { - return res.status(400).send({ - message: errorHandler.getErrorMessage(err) - }); - } else { - req.login(user, function(err) { - if (err) { - res.status(400).send(err); - } else { - res.json(user); - } - }); - } - }); - } else { - res.status(400).send({ - message: 'User is not signed in' - }); - } -}; - -/** - * Send User - */ -exports.me = function(req, res) { - res.json(req.user || null); -}; \ No newline at end of file diff --git a/app/routes/articles.server.routes.js b/app/routes/articles.server.routes.js deleted file mode 100644 index 7d840e6fc2..0000000000 --- a/app/routes/articles.server.routes.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ -var users = require('../../app/controllers/users.server.controller'), - articles = require('../../app/controllers/articles.server.controller'); - -module.exports = function(app) { - // Article Routes - app.route('/articles') - .get(articles.list) - .post(users.requiresLogin, articles.create); - - app.route('/articles/:articleId') - .get(articles.read) - .put(users.requiresLogin, articles.hasAuthorization, articles.update) - .delete(users.requiresLogin, articles.hasAuthorization, articles.delete); - - // Finish by binding the article middleware - app.param('articleId', articles.articleByID); -}; \ No newline at end of file diff --git a/app/routes/core.server.routes.js b/app/routes/core.server.routes.js deleted file mode 100644 index 1db9d40074..0000000000 --- a/app/routes/core.server.routes.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -module.exports = function(app) { - // Root routing - var core = require('../../app/controllers/core.server.controller'); - app.route('/').get(core.index); -}; \ No newline at end of file diff --git a/app/routes/users.server.routes.js b/app/routes/users.server.routes.js deleted file mode 100644 index 3120e9abb6..0000000000 --- a/app/routes/users.server.routes.js +++ /dev/null @@ -1,57 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ -var passport = require('passport'); - -module.exports = function(app) { - // User Routes - var users = require('../../app/controllers/users.server.controller'); - - // Setting up the users profile api - app.route('/users/me').get(users.me); - app.route('/users').put(users.update); - app.route('/users/accounts').delete(users.removeOAuthProvider); - - // Setting up the users password api - app.route('/users/password').post(users.changePassword); - app.route('/auth/forgot').post(users.forgot); - app.route('/auth/reset/:token').get(users.validateResetToken); - app.route('/auth/reset/:token').post(users.reset); - - // Setting up the users authentication api - app.route('/auth/signup').post(users.signup); - app.route('/auth/signin').post(users.signin); - app.route('/auth/signout').get(users.signout); - - // Setting the facebook oauth routes - app.route('/auth/facebook').get(passport.authenticate('facebook', { - scope: ['email'] - })); - app.route('/auth/facebook/callback').get(users.oauthCallback('facebook')); - - // Setting the twitter oauth routes - app.route('/auth/twitter').get(passport.authenticate('twitter')); - app.route('/auth/twitter/callback').get(users.oauthCallback('twitter')); - - // Setting the google oauth routes - app.route('/auth/google').get(passport.authenticate('google', { - scope: [ - 'https://www.googleapis.com/auth/userinfo.profile', - 'https://www.googleapis.com/auth/userinfo.email' - ] - })); - app.route('/auth/google/callback').get(users.oauthCallback('google')); - - // Setting the linkedin oauth routes - app.route('/auth/linkedin').get(passport.authenticate('linkedin')); - app.route('/auth/linkedin/callback').get(users.oauthCallback('linkedin')); - - // Setting the github oauth routes - app.route('/auth/github').get(passport.authenticate('github')); - app.route('/auth/github/callback').get(users.oauthCallback('github')); - - // Finish by binding the user middleware - app.param('userId', users.userByID); -}; \ No newline at end of file diff --git a/bower.json b/bower.json index 3d7a7451dc..951e48aaac 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "meanjs", - "version": "0.3.2", + "version": "0.4.0", "description": "Fullstack JavaScript with MongoDB, Express, AngularJS, and Node.js.", "dependencies": { "bootstrap": "~3", @@ -8,8 +8,9 @@ "angular-resource": "~1.2", "angular-animate": "~1.2", "angular-mocks": "~1.2", - "angular-bootstrap": "~0.11.2", + "angular-bootstrap": "~0.11.0", "angular-ui-utils": "~0.1.1", - "angular-ui-router": "~0.2.11" + "angular-ui-router": "~0.2.10", + "angular-file-upload": "~1.1.5" } -} \ No newline at end of file +} diff --git a/config/assets/default.js b/config/assets/default.js new file mode 100644 index 0000000000..8d614fd4d3 --- /dev/null +++ b/config/assets/default.js @@ -0,0 +1,47 @@ +'use strict'; + +module.exports = { + client: { + lib: { + css: [ + 'public/lib/bootstrap/dist/css/bootstrap.css', + 'public/lib/bootstrap/dist/css/bootstrap-theme.css' + ], + js: [ + 'public/lib/angular/angular.js', + 'public/lib/angular-resource/angular-resource.js', + 'public/lib/angular-animate/angular-animate.js', + 'public/lib/angular-ui-router/release/angular-ui-router.js', + 'public/lib/angular-ui-utils/ui-utils.js', + 'public/lib/angular-bootstrap/ui-bootstrap-tpls.js', + 'public/lib/angular-file-upload/angular-file-upload.js' + ], + tests: ['public/lib/angular-mocks/angular-mocks.js'] + }, + css: [ + 'modules/*/client/css/*.css' + ], + less: [ + 'modules/*/client/less/*.less' + ], + sass: [ + 'modules/*/client/scss/*.scss' + ], + js: [ + 'modules/core/client/app/config.js', + 'modules/core/client/app/init.js', + 'modules/*/client/*.js', + 'modules/*/client/**/*.js' + ], + views: ['modules/*/client/views/**/*.html'] + }, + server: { + allJS: ['gruntfile.js', 'server.js', 'config/**/*.js', 'modules/*/server/**/*.js'], + models: 'modules/*/server/models/**/*.js', + routes: ['modules/*[!core]/server/routes/**/*.js', 'modules/core/server/routes/**/*.js'], + sockets: 'modules/*/server/sockets/**/*.js', + config: 'modules/*/server/config/*.js', + policies: 'modules/*/server/policies/*.js', + views: 'modules/*/server/views/*.html' + } +}; diff --git a/config/assets/development.js b/config/assets/development.js new file mode 100644 index 0000000000..47a2c6d20a --- /dev/null +++ b/config/assets/development.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + // Development assets +}; \ No newline at end of file diff --git a/config/assets/production.js b/config/assets/production.js new file mode 100644 index 0000000000..9a664c455b --- /dev/null +++ b/config/assets/production.js @@ -0,0 +1,23 @@ +'use strict'; + +module.exports = { + client: { + lib: { + css: [ + 'public/lib/bootstrap/dist/css/bootstrap.min.css', + 'public/lib/bootstrap/dist/css/bootstrap-theme.min.css', + ], + js: [ + 'public/lib/angular/angular.min.js', + 'public/lib/angular-resource/angular-resource.min.js', + 'public/lib/angular-animate/angular-animate.min.js', + 'public/lib/angular-ui-router/release/angular-ui-router.min.js', + 'public/lib/angular-ui-utils/ui-utils.min.js', + 'public/lib/angular-bootstrap/ui-bootstrap-tpls.min.js', + 'public/lib/angular-file-upload/angular-file-upload.min.js' + ] + }, + css: 'public/dist/application.min.css', + js: 'public/dist/application.min.js' + } +}; diff --git a/config/assets/test.js b/config/assets/test.js new file mode 100644 index 0000000000..ecc4cac91d --- /dev/null +++ b/config/assets/test.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = { + tests: { + client: ['modules/*/tests/client/**/*.js'], + server: ['modules/*/tests/server/**/*.js'], + e2e: ['modules/*/tests/e2e/**/*.js'] + } +}; \ No newline at end of file diff --git a/config/config.js b/config/config.js index 3baa02e552..a966dde2f1 100644 --- a/config/config.js +++ b/config/config.js @@ -4,73 +4,169 @@ * Module dependencies. */ var _ = require('lodash'), - glob = require('glob'); + chalk = require('chalk'), + glob = require('glob'), + path = require('path'); /** - * Load app configurations + * Get files by glob patterns */ -module.exports = _.extend( - require('./env/all'), - require('./env/' + process.env.NODE_ENV) || {} -); +var getGlobbedPaths = function(globPatterns, excludes) { + // URL paths regex + var urlRegex = new RegExp('^(?:[a-z]+:)?\/\/', 'i'); + + // The output array + var output = []; + + // If glob pattern is array so we use each pattern in a recursive way, otherwise we use glob + if (_.isArray(globPatterns)) { + globPatterns.forEach(function(globPattern) { + output = _.union(output, getGlobbedPaths(globPattern, excludes)); + }); + } else if (_.isString(globPatterns)) { + if (urlRegex.test(globPatterns)) { + output.push(globPatterns); + } else { + glob(globPatterns, { + sync: true + }, function(err, files) { + if (excludes) { + files = files.map(function(file) { + if (_.isArray(excludes)) { + for (var i in excludes) { + file = file.replace(excludes[i], ''); + } + } else { + file = file.replace(excludes, ''); + } + + return file; + }); + } + + output = _.union(output, files); + }); + } + } + + return output; +}; /** - * Get files by glob patterns + * Validate NODE_ENV existance + */ +var validateEnvironmentVariable = function() { + glob('./config/env/' + process.env.NODE_ENV + '.js', { + sync: true + }, function(err, environmentFiles) { + console.log(); + + if (!environmentFiles.length) { + if (process.env.NODE_ENV) { + console.error(chalk.red('No configuration file found for "' + process.env.NODE_ENV + '" environment using development instead')); + } else { + console.error(chalk.red('NODE_ENV is not defined! Using default development environment')); + } + + process.env.NODE_ENV = 'development'; + } else { + console.log(chalk.bold('Application loaded using the "' + process.env.NODE_ENV + '" environment configuration')); + } + + // Reset console color + console.log(chalk.white('')); + }); +}; + +/** + * Initialize global configuration files + */ +var initGlobalConfigFolders = function(config, assets) { + // Appending files + config.folders = { + server: {}, + client: {} + }; + + // Setting globbed client paths + config.folders.client = getGlobbedPaths(path.join(process.cwd(), 'modules/*/client/'), process.cwd()); +}; + +/** + * Initialize global configuration files */ -module.exports.getGlobbedFiles = function(globPatterns, removeRoot) { - // For context switching - var _this = this; - - // URL paths regex - var urlRegex = new RegExp('^(?:[a-z]+:)?\/\/', 'i'); - - // The output array - var output = []; - - // If glob pattern is array so we use each pattern in a recursive way, otherwise we use glob - if (_.isArray(globPatterns)) { - globPatterns.forEach(function(globPattern) { - output = _.union(output, _this.getGlobbedFiles(globPattern, removeRoot)); - }); - } else if (_.isString(globPatterns)) { - if (urlRegex.test(globPatterns)) { - output.push(globPatterns); - } else { - glob(globPatterns, { - sync: true - }, function(err, files) { - if (removeRoot) { - files = files.map(function(file) { - return file.replace(removeRoot, ''); - }); - } - - output = _.union(output, files); - }); - } - } - - return output; +var initGlobalConfigFiles = function(config, assets) { + // Appending files + config.files = { + server: {}, + client: {} + }; + + // Setting Globbed model files + config.files.server.models = getGlobbedPaths(assets.server.models); + + // Setting Globbed route files + config.files.server.routes = getGlobbedPaths(assets.server.routes); + + // Setting Globbed config files + config.files.server.configs = getGlobbedPaths(assets.server.config); + + // Setting Globbed socket files + config.files.server.sockets = getGlobbedPaths(assets.server.sockets); + + // Setting Globbed policies files + config.files.server.policies = getGlobbedPaths(assets.server.policies); + + // Setting Globbed js files + config.files.client.js = getGlobbedPaths(assets.client.lib.js, 'public/').concat(getGlobbedPaths(assets.client.js, ['client/', 'public/'])); + + // Setting Globbed css files + config.files.client.css = getGlobbedPaths(assets.client.lib.css, 'public/').concat(getGlobbedPaths(assets.client.css, ['client/', 'public/'])); + + // Setting Globbed test files + config.files.client.tests = getGlobbedPaths(assets.client.tests); }; /** - * Get the modules JavaScript files + * Initialize global configuration */ -module.exports.getJavaScriptAssets = function(includeTests) { - var output = this.getGlobbedFiles(this.assets.lib.js.concat(this.assets.js), 'public/'); +var initGlobalConfig = function() { + // Validate NDOE_ENV existance + validateEnvironmentVariable(); + + // Get the default assets + var defaultAssets = require(path.join(process.cwd(), 'config/assets/default')); + + // Get the current assets + var environmentAssets = require(path.join(process.cwd(), 'config/assets/', process.env.NODE_ENV)) || {}; + + // Merge assets + var assets = _.extend(defaultAssets, environmentAssets); + + // Get the default config + var defaultConfig = require(path.join(process.cwd(), 'config/env/default')); + + // Get the current config + var environmentConfig = require(path.join(process.cwd(), 'config/env/', process.env.NODE_ENV)) || {}; + + // Merge config files + var config = _.extend(defaultConfig, environmentConfig); + + // Initialize global globbed files + initGlobalConfigFiles(config, assets); + + // Initialize global globbed folders + initGlobalConfigFolders(config, assets); - // To include tests - if (includeTests) { - output = _.union(output, this.getGlobbedFiles(this.assets.tests)); - } + // Expose configuration utilities + config.utils = { + getGlobbedPaths: getGlobbedPaths + }; - return output; + return config; }; /** - * Get the modules CSS files + * Set configuration object */ -module.exports.getCSSAssets = function() { - var output = this.getGlobbedFiles(this.assets.lib.css.concat(this.assets.css), 'public/'); - return output; -}; \ No newline at end of file +module.exports = initGlobalConfig(); diff --git a/config/env/all.js b/config/env/all.js deleted file mode 100644 index b4e1749e75..0000000000 --- a/config/env/all.js +++ /dev/null @@ -1,51 +0,0 @@ -'use strict'; - -module.exports = { - app: { - title: 'MEAN.JS', - description: 'Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js', - keywords: 'mongodb, express, angularjs, node.js, mongoose, passport' - }, - port: process.env.PORT || 3000, - templateEngine: 'swig', - sessionSecret: 'MEAN', - sessionCollection: 'sessions', - log: { - // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' - format: 'combined', - // Stream defaults to process.stdout - // Uncomment to enable logging to a log on the file system - options: { - stream: 'access.log' - } - }, - assets: { - lib: { - css: [ - 'public/lib/bootstrap/dist/css/bootstrap.css', - 'public/lib/bootstrap/dist/css/bootstrap-theme.css', - ], - js: [ - 'public/lib/angular/angular.js', - 'public/lib/angular-resource/angular-resource.js', - 'public/lib/angular-animate/angular-animate.js', - 'public/lib/angular-ui-router/release/angular-ui-router.js', - 'public/lib/angular-ui-utils/ui-utils.js', - 'public/lib/angular-bootstrap/ui-bootstrap-tpls.js' - ] - }, - css: [ - 'public/modules/**/css/*.css' - ], - js: [ - 'public/config.js', - 'public/application.js', - 'public/modules/*/*.js', - 'public/modules/*/*[!tests]*/*.js' - ], - tests: [ - 'public/lib/angular-mocks/angular-mocks.js', - 'public/modules/*/tests/*.js' - ] - } -}; diff --git a/config/env/default.js b/config/env/default.js new file mode 100644 index 0000000000..8acd131a0b --- /dev/null +++ b/config/env/default.js @@ -0,0 +1,14 @@ +'use strict'; + +module.exports = { + app: { + title: 'MEAN.JS', + description: 'Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js', + keywords: 'mongodb, express, angularjs, node.js, mongoose, passport', + googleAnalyticsTrackingID: process.env.GOOGLE_ANALYTICS_TRACKING_ID || 'GOOGLE_ANALYTICS_TRACKING_ID' + }, + port: process.env.PORT || 3000, + templateEngine: 'swig', + sessionSecret: 'MEAN', + sessionCollection: 'sessions' +}; diff --git a/config/env/development.js b/config/env/development.js index 2b895b8bbc..310189454b 100644 --- a/config/env/development.js +++ b/config/env/development.js @@ -2,43 +2,34 @@ module.exports = { db: 'mongodb://localhost/mean-dev', - log: { - // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' - format: 'dev', - // Stream defaults to process.stdout - // Uncomment to enable logging to a log on the file system - options: { - //stream: 'access.log' - } - }, app: { title: 'MEAN.JS - Development Environment' }, facebook: { - clientID: process.env.FACEBOOK_ID || 'APP_ID', - clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', - callbackURL: '/auth/facebook/callback' - }, - twitter: { - clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY', - clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET', - callbackURL: '/auth/twitter/callback' - }, - google: { - clientID: process.env.GOOGLE_ID || 'APP_ID', - clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET', - callbackURL: '/auth/google/callback' - }, - linkedin: { - clientID: process.env.LINKEDIN_ID || 'APP_ID', - clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', - callbackURL: '/auth/linkedin/callback' - }, - github: { - clientID: process.env.GITHUB_ID || 'APP_ID', - clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET', - callbackURL: '/auth/github/callback' - }, + clientID: process.env.FACEBOOK_ID || 'APP_ID', + clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', + callbackURL: '/api/auth/facebook/callback' + }, + twitter: { + clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY', + clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET', + callbackURL: '/api/auth/twitter/callback' + }, + google: { + clientID: process.env.GOOGLE_ID || 'APP_ID', + clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET', + callbackURL: '/api/auth/google/callback' + }, + linkedin: { + clientID: process.env.LINKEDIN_ID || 'APP_ID', + clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', + callbackURL: '/api/auth/linkedin/callback' + }, + github: { + clientID: process.env.GITHUB_ID || 'APP_ID', + clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET', + callbackURL: '/api/auth/github/callback' + }, mailer: { from: process.env.MAILER_FROM || 'MAILER_FROM', options: { diff --git a/config/env/production.js b/config/env/production.js index c9f2817382..3e87554779 100644 --- a/config/env/production.js +++ b/config/env/production.js @@ -1,67 +1,40 @@ 'use strict'; module.exports = { - db: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean', - log: { - // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' - format: 'combined', - // Stream defaults to process.stdout - // Uncomment to enable logging to a log on the file system - options: { - stream: 'access.log' - } - }, - assets: { - lib: { - css: [ - 'public/lib/bootstrap/dist/css/bootstrap.min.css', - 'public/lib/bootstrap/dist/css/bootstrap-theme.min.css', - ], - js: [ - 'public/lib/angular/angular.min.js', - 'public/lib/angular-resource/angular-resource.min.js', - 'public/lib/angular-animate/angular-animate.min.js', - 'public/lib/angular-ui-router/release/angular-ui-router.min.js', - 'public/lib/angular-ui-utils/ui-utils.min.js', - 'public/lib/angular-bootstrap/ui-bootstrap-tpls.min.js' - ] - }, - css: 'public/dist/application.min.css', - js: 'public/dist/application.min.js' - }, - facebook: { - clientID: process.env.FACEBOOK_ID || 'APP_ID', - clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', - callbackURL: '/auth/facebook/callback' - }, - twitter: { - clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY', - clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET', - callbackURL: '/auth/twitter/callback' - }, - google: { - clientID: process.env.GOOGLE_ID || 'APP_ID', - clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET', - callbackURL: '/auth/google/callback' - }, - linkedin: { - clientID: process.env.LINKEDIN_ID || 'APP_ID', - clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', - callbackURL: '/auth/linkedin/callback' - }, - github: { - clientID: process.env.GITHUB_ID || 'APP_ID', - clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET', - callbackURL: '/auth/github/callback' - }, - mailer: { - from: process.env.MAILER_FROM || 'MAILER_FROM', - options: { - service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER', - auth: { - user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID', - pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD' - } - } - } -}; + db: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean', + facebook: { + clientID: process.env.FACEBOOK_ID || 'APP_ID', + clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', + callbackURL: '/api/auth/facebook/callback' + }, + twitter: { + clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY', + clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET', + callbackURL: '/api/auth/twitter/callback' + }, + google: { + clientID: process.env.GOOGLE_ID || 'APP_ID', + clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET', + callbackURL: '/api/auth/google/callback' + }, + linkedin: { + clientID: process.env.LINKEDIN_ID || 'APP_ID', + clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', + callbackURL: '/api/auth/linkedin/callback' + }, + github: { + clientID: process.env.GITHUB_ID || 'APP_ID', + clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET', + callbackURL: '/api/auth/github/callback' + }, + mailer: { + from: process.env.MAILER_FROM || 'MAILER_FROM', + options: { + service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER', + auth: { + user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID', + pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD' + } + } + } +}; \ No newline at end of file diff --git a/config/env/secure.js b/config/env/secure.js deleted file mode 100644 index ee2b270ad1..0000000000 --- a/config/env/secure.js +++ /dev/null @@ -1,68 +0,0 @@ -'use strict'; - -module.exports = { - port: 443, - db: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://localhost/mean', - log: { - // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' - format: 'combined', - // Stream defaults to process.stdout - // Uncomment to enable logging to a log on the file system - options: { - stream: 'access.log' - } - }, - assets: { - lib: { - css: [ - 'public/lib/bootstrap/dist/css/bootstrap.min.css', - 'public/lib/bootstrap/dist/css/bootstrap-theme.min.css', - ], - js: [ - 'public/lib/angular/angular.min.js', - 'public/lib/angular-resource/angular-resource.min.js', - 'public/lib/angular-animate/angular-animate.min.js', - 'public/lib/angular-ui-router/release/angular-ui-router.min.js', - 'public/lib/angular-ui-utils/ui-utils.min.js', - 'public/lib/angular-bootstrap/ui-bootstrap-tpls.min.js' - ] - }, - css: 'public/dist/application.min.css', - js: 'public/dist/application.min.js' - }, - facebook: { - clientID: process.env.FACEBOOK_ID || 'APP_ID', - clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', - callbackURL: 'https://localhost:443/auth/facebook/callback' - }, - twitter: { - clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY', - clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET', - callbackURL: 'https://localhost:443/auth/twitter/callback' - }, - google: { - clientID: process.env.GOOGLE_ID || 'APP_ID', - clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET', - callbackURL: 'https://localhost:443/auth/google/callback' - }, - linkedin: { - clientID: process.env.LINKEDIN_ID || 'APP_ID', - clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', - callbackURL: 'https://localhost:443/auth/linkedin/callback' - }, - github: { - clientID: process.env.GITHUB_ID || 'APP_ID', - clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET', - callbackURL: 'https://localhost:443/auth/github/callback' - }, - mailer: { - from: process.env.MAILER_FROM || 'MAILER_FROM', - options: { - service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER', - auth: { - user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID', - pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD' - } - } - } -}; \ No newline at end of file diff --git a/config/env/test.js b/config/env/test.js index 22c47c9fb2..6f95fdd3f5 100644 --- a/config/env/test.js +++ b/config/env/test.js @@ -3,42 +3,33 @@ module.exports = { db: 'mongodb://localhost/mean-test', port: 3001, - log: { - // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' - format: 'dev', - // Stream defaults to process.stdout - // Uncomment to enable logging to a log on the file system - options: { - //stream: 'access.log' - } - }, app: { title: 'MEAN.JS - Test Environment' }, facebook: { clientID: process.env.FACEBOOK_ID || 'APP_ID', clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', - callbackURL: '/auth/facebook/callback' + callbackURL: '/api/auth/facebook/callback' }, twitter: { clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY', clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET', - callbackURL: '/auth/twitter/callback' + callbackURL: '/api/auth/twitter/callback' }, google: { clientID: process.env.GOOGLE_ID || 'APP_ID', clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET', - callbackURL: '/auth/google/callback' + callbackURL: '/api/auth/google/callback' }, linkedin: { clientID: process.env.LINKEDIN_ID || 'APP_ID', clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', - callbackURL: '/auth/linkedin/callback' + callbackURL: '/api/auth/linkedin/callback' }, github: { clientID: process.env.GITHUB_ID || 'APP_ID', clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET', - callbackURL: '/auth/github/callback' + callbackURL: '/api/auth/github/callback' }, mailer: { from: process.env.MAILER_FROM || 'MAILER_FROM', diff --git a/config/express.js b/config/express.js deleted file mode 100755 index f19ba586bb..0000000000 --- a/config/express.js +++ /dev/null @@ -1,164 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ -var fs = require('fs'), - http = require('http'), - https = require('https'), - express = require('express'), - morgan = require('morgan'), - logger = require('./logger'), - bodyParser = require('body-parser'), - session = require('express-session'), - compress = require('compression'), - methodOverride = require('method-override'), - cookieParser = require('cookie-parser'), - helmet = require('helmet'), - passport = require('passport'), - mongoStore = require('connect-mongo')({ - session: session - }), - flash = require('connect-flash'), - config = require('./config'), - consolidate = require('consolidate'), - path = require('path'); - -module.exports = function(db) { - // Initialize express app - var app = express(); - - // Globbing model files - config.getGlobbedFiles('./app/models/**/*.js').forEach(function(modelPath) { - require(path.resolve(modelPath)); - }); - - // Setting application local variables - app.locals.title = config.app.title; - app.locals.description = config.app.description; - app.locals.keywords = config.app.keywords; - app.locals.facebookAppId = config.facebook.clientID; - app.locals.jsFiles = config.getJavaScriptAssets(); - app.locals.cssFiles = config.getCSSAssets(); - - // Passing the request url to environment locals - app.use(function(req, res, next) { - res.locals.url = req.protocol + '://' + req.headers.host + req.url; - next(); - }); - - // Should be placed before express.static - app.use(compress({ - filter: function(req, res) { - return (/json|text|javascript|css/).test(res.getHeader('Content-Type')); - }, - level: 9 - })); - - // Showing stack errors - app.set('showStackError', true); - - // Set swig as the template engine - app.engine('server.view.html', consolidate[config.templateEngine]); - - // Set views path and view engine - app.set('view engine', 'server.view.html'); - app.set('views', './app/views'); - - // Enable logger (morgan) - app.use(morgan(logger.getLogFormat(), logger.getLogOptions())); - - // Environment dependent middleware - if (process.env.NODE_ENV === 'development') { - // Disable views cache - app.set('view cache', false); - } else if (process.env.NODE_ENV === 'production') { - app.locals.cache = 'memory'; - } - - // Request body parsing middleware should be above methodOverride - app.use(bodyParser.urlencoded({ - extended: true - })); - app.use(bodyParser.json()); - app.use(methodOverride()); - - // CookieParser should be above session - app.use(cookieParser()); - - // Express MongoDB session storage - app.use(session({ - saveUninitialized: true, - resave: true, - secret: config.sessionSecret, - store: new mongoStore({ - db: db.connection.db, - collection: config.sessionCollection - }) - })); - - // use passport session - app.use(passport.initialize()); - app.use(passport.session()); - - // connect flash for flash messages - app.use(flash()); - - // Use helmet to secure Express headers - app.use(helmet.xframe()); - app.use(helmet.xssFilter()); - app.use(helmet.nosniff()); - app.use(helmet.ienoopen()); - app.disable('x-powered-by'); - - // Setting the app router and static folder - app.use(express.static(path.resolve('./public'))); - - // Globbing routing files - config.getGlobbedFiles('./app/routes/**/*.js').forEach(function(routePath) { - require(path.resolve(routePath))(app); - }); - - // Assume 'not found' in the error msgs is a 404. this is somewhat silly, but valid, you can do whatever you like, set properties, use instanceof etc. - app.use(function(err, req, res, next) { - // If the error object doesn't exists - if (!err) return next(); - - // Log it - console.error(err.stack); - - // Error page - res.status(500).render('500', { - error: err.stack - }); - }); - - // Assume 404 since no middleware responded - app.use(function(req, res) { - res.status(404).render('404', { - url: req.originalUrl, - error: 'Not Found' - }); - }); - - if (process.env.NODE_ENV === 'secure') { - // Log SSL usage - console.log('Securely using https protocol'); - - // Load SSL key and certificate - var privateKey = fs.readFileSync('./config/sslcerts/key.pem', 'utf8'); - var certificate = fs.readFileSync('./config/sslcerts/cert.pem', 'utf8'); - - // Create HTTPS Server - var httpsServer = https.createServer({ - key: privateKey, - cert: certificate - }, app); - - // Return HTTPS server instance - return httpsServer; - } - - // Return Express server instance - return app; -}; \ No newline at end of file diff --git a/config/init.js b/config/init.js deleted file mode 100644 index 6facd9d0f8..0000000000 --- a/config/init.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ -var glob = require('glob'), - chalk = require('chalk'); - -/** - * Module init function. - */ -module.exports = function() { - /** - * Before we begin, lets set the environment variable - * We'll Look for a valid NODE_ENV variable and if one cannot be found load the development NODE_ENV - */ - glob('./config/env/' + process.env.NODE_ENV + '.js', { - sync: true - }, function(err, environmentFiles) { - if (!environmentFiles.length) { - if (process.env.NODE_ENV) { - console.error(chalk.red('No configuration file found for "' + process.env.NODE_ENV + '" environment using development instead')); - } else { - console.error(chalk.red('NODE_ENV is not defined! Using default development environment')); - } - - process.env.NODE_ENV = 'development'; - } else { - console.log(chalk.black.bgWhite('Application loaded using the "' + process.env.NODE_ENV + '" environment configuration')); - } - }); - -}; \ No newline at end of file diff --git a/config/lib/express.js b/config/lib/express.js new file mode 100755 index 0000000000..2ad68145b2 --- /dev/null +++ b/config/lib/express.js @@ -0,0 +1,251 @@ +'use strict'; + +/** + * Module dependencies. + */ +var config = require('../config'), + express = require('express'), + morgan = require('morgan'), + bodyParser = require('body-parser'), + session = require('express-session'), + MongoStore = require('connect-mongo')(session), + multer = require('multer'), + favicon = require('serve-favicon'), + compress = require('compression'), + methodOverride = require('method-override'), + cookieParser = require('cookie-parser'), + helmet = require('helmet'), + passport = require('passport'), + flash = require('connect-flash'), + consolidate = require('consolidate'), + path = require('path'); + +/** + * Initialize local variables + */ +module.exports.initLocalVariables = function (app) { + // Setting application local variables + app.locals.title = config.app.title; + app.locals.description = config.app.description; + app.locals.keywords = config.app.keywords; + app.locals.googleAnalyticsTrackingID = config.app.googleAnalyticsTrackingID; + app.locals.facebookAppId = config.facebook.clientID; + app.locals.jsFiles = config.files.client.js; + app.locals.cssFiles = config.files.client.css; + + // Passing the request url to environment locals + app.use(function (req, res, next) { + res.locals.host = req.protocol + '://' + req.hostname; + res.locals.url = req.protocol + '://' + req.headers.host + req.originalUrl; + next(); + }); +}; + +/** + * Initialize application middleware + */ +module.exports.initMiddleware = function (app) { + // Showing stack errors + app.set('showStackError', true); + + // Enable jsonp + app.enable('jsonp callback'); + + // Should be placed before express.static + app.use(compress({ + filter: function (req, res) { + return (/json|text|javascript|css/).test(res.getHeader('Content-Type')); + }, + level: 9 + })); + + // Initialize favicon middleware + app.use(favicon('./modules/core/client/img/brand/favicon.ico')); + + // Environment dependent middleware + if (process.env.NODE_ENV === 'development') { + // Enable logger (morgan) + app.use(morgan('dev')); + + // Disable views cache + app.set('view cache', false); + } else if (process.env.NODE_ENV === 'production') { + app.locals.cache = 'memory'; + } + + // Request body parsing middleware should be above methodOverride + app.use(bodyParser.urlencoded({ + extended: true + })); + app.use(bodyParser.json()); + app.use(methodOverride()); + + // Add the cookie parser and flash middleware + app.use(cookieParser()); + app.use(flash()); + + // Add multipart handling middleware + app.use(multer({ + dest: './uploads/', + inMemory: true + })); +}; + +/** + * Configure view engine + */ +module.exports.initViewEngine = function (app) { + // Set swig as the template engine + app.engine('server.view.html', consolidate[config.templateEngine]); + + // Set views path and view engine + app.set('view engine', 'server.view.html'); + app.set('views', './'); +}; + +/** + * Configure Express session + */ +module.exports.initSession = function (app, db) { + // Express MongoDB session storage + app.use(session({ + saveUninitialized: true, + resave: true, + secret: config.sessionSecret, + store: new MongoStore({ + db: db.connection.db, + collection: config.sessionCollection + }) + })); +}; + +/** + * Invoke modules server configuration + */ +module.exports.initModulesConfiguration = function (app, db) { + config.files.server.configs.forEach(function (configPath) { + require(path.resolve(configPath))(app, db); + }); +}; + +/** + * Configure Helmet headers configuration + */ +module.exports.initHelmetHeaders = function (app) { + // Use helmet to secure Express headers + app.use(helmet.xframe()); + app.use(helmet.xssFilter()); + app.use(helmet.nosniff()); + app.use(helmet.ienoopen()); + app.disable('x-powered-by'); +}; + +/** + * Configure the modules static routes + */ +module.exports.initModulesClientRoutes = function (app) { + // Setting the app router and static folder + app.use('/', express.static(path.resolve('./public'))); + + // Globbing static routing + config.folders.client.forEach(function (staticPath) { + app.use(staticPath.replace('/client', ''), express.static(path.resolve('./' + staticPath))); + }); +}; + +/** + * Configure the modules ACL policies + */ +module.exports.initModulesServerPolicies = function (app) { + // Globbing policy files + config.files.server.policies.forEach(function (policyPath) { + require(path.resolve(policyPath)).invokeRolesPolicies(); + }); +}; + +/** + * Configure the modules server routes + */ +module.exports.initModulesServerRoutes = function (app) { + // Globbing routing files + config.files.server.routes.forEach(function (routePath) { + require(path.resolve(routePath))(app); + }); +}; + +/** + * Configure error handling + */ +module.exports.initErrorRoutes = function (app) { + // Assume 'not found' in the error msgs is a 404. this is somewhat silly, but valid, you can do whatever you like, set properties, use instanceof etc. + app.use(function (err, req, res, next) { + // If the error object doesn't exists + if (!err) return next(); + + // Log it + console.error(err.stack); + + // Redirect to error page + res.redirect('/server-error'); + }); + + // Assume 404 since no middleware responded + app.use(function (req, res) { + // Redirect to not found page + res.redirect('/not-found'); + }); +}; + +/** + * Configure Socket.io + */ +module.exports.configureSocketIO = function (app, db) { + // Load the Socket.io configuration + var server = require('./socket.io')(app, db); + + // Return server object + return server; +}; + +/** + * Initialize the Express application + */ +module.exports.init = function (db) { + // Initialize express app + var app = express(); + + // Initialize local variables + this.initLocalVariables(app); + + // Initialize Express middleware + this.initMiddleware(app); + + // Initialize Express view engine + this.initViewEngine(app); + + // Initialize Express session + this.initSession(app, db); + + // Initialize Modules configuration + this.initModulesConfiguration(app); + + // Initialize Helmet security headers + this.initHelmetHeaders(app); + + // Initialize modules static client routes + this.initModulesClientRoutes(app); + + // Initialize modules server authorization policies + this.initModulesServerPolicies(app); + + // Initialize modules server routes + this.initModulesServerRoutes(app); + + // Initialize error routes + this.initErrorRoutes(app); + + // Configure Socket.io + app = this.configureSocketIO(app, db); + + return app; +}; diff --git a/config/lib/mongoose.js b/config/lib/mongoose.js new file mode 100644 index 0000000000..9bb0b74a0e --- /dev/null +++ b/config/lib/mongoose.js @@ -0,0 +1,36 @@ +'use strict'; + +/** + * Module dependencies. + */ +var config = require('../config'), + chalk = require('chalk'), + path = require('path'), + mongoose = require('mongoose'); + +// Load the mongoose models +module.exports.loadModels = function() { + // Globbing model files + config.files.server.models.forEach(function(modelPath) { + require(path.resolve(modelPath)); + }); +}; + +// Initialize Mongoose +module.exports.connect = function(cb) { + var _this = this; + + var db = mongoose.connect(config.db, function (err) { + // Log Error + if (err) { + console.error(chalk.red('Could not connect to MongoDB!')); + console.log(err); + } else { + // Load modules + _this.loadModels(); + + // Call callback FN + if (cb) cb(db); + } + }); +}; diff --git a/config/lib/socket.io.js b/config/lib/socket.io.js new file mode 100644 index 0000000000..4e89bdee4b --- /dev/null +++ b/config/lib/socket.io.js @@ -0,0 +1,61 @@ +'use strict'; + +// Load the module dependencies +var config = require('../config'), + path = require('path'), + cookieParser = require('cookie-parser'), + passport = require('passport'), + socketio = require('socket.io'), + session = require('express-session'), + MongoStore = require('connect-mongo')(session), + http = require('http'); + +// Define the Socket.io configuration method +module.exports = function(app, db) { + // Create a new HTTP server + var server = http.createServer(app); + + // Create a new Socket.io server + var io = socketio.listen(server); + + // Create a MongoDB storage object + var mongoStore = new MongoStore({ + db: db.connection.db, + collection: config.sessionCollection + }); + + // Intercept Socket.io's handshake request + io.use(function(socket, next) { + // Use the 'cookie-parser' module to parse the request cookies + cookieParser(config.sessionSecret)(socket.request, {}, function(err) { + // Get the session id from the request cookies + var sessionId = socket.request.signedCookies['connect.sid']; + + // Use the mongoStorage instance to get the Express session information + mongoStore.get(sessionId, function(err, session) { + // Set the Socket.io session information + socket.request.session = session; + + // Use Passport to populate the user details + passport.initialize()(socket.request, {}, function() { + passport.session()(socket.request, {}, function() { + if (socket.request.user) { + next(null, true); + } else { + next(new Error('User is not authenticated'), false); + } + }); + }); + }); + }); + }); + + // Add an event listener to the 'connection' event + io.on('connection', function(socket) { + config.files.server.sockets.forEach(function(socketConfiguration) { + require(path.resolve(socketConfiguration))(io, socket); + }); + }); + + return server; +}; \ No newline at end of file diff --git a/config/logger.js b/config/logger.js deleted file mode 100644 index 98b3a4c5b9..0000000000 --- a/config/logger.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict'; - -/** - * Module dependencies. - */ - -var morgan = require('morgan'); -var config = require('./config'); -var fs = require('fs'); - -/** - * Module init function. - */ -module.exports = { - - getLogFormat: function() { - return config.log.format; - }, - - getLogOptions: function() { - var options = {}; - - try { - if ('stream' in config.log.options) { - options = { - stream: fs.createWriteStream(process.cwd() + '/' + config.log.options.stream, {flags: 'a'}) - }; - } - } catch (e) { - options = {}; - } - - return options; - } - -}; \ No newline at end of file diff --git a/gruntfile.js b/gruntfile.js index 4555485743..ea4eb0d88e 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -1,68 +1,117 @@ 'use strict'; -module.exports = function(grunt) { - // Unified Watch Object - var watchFiles = { - serverViews: ['app/views/**/*.*'], - serverJS: ['gruntfile.js', 'server.js', 'config/**/*.js', 'app/**/*.js'], - clientViews: ['public/modules/**/views/**/*.html'], - clientJS: ['public/js/*.js', 'public/modules/**/*.js'], - clientCSS: ['public/modules/**/*.css'], - mochaTests: ['app/tests/**/*.js'] - }; +/** + * Module dependencies. + */ +var _ = require('lodash'), + defaultAssets = require('./config/assets/default'), + testAssets = require('./config/assets/test'); +module.exports = function (grunt) { // Project Configuration grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), + env: { + test: { + NODE_ENV: 'test' + }, + dev: { + NODE_ENV: 'development' + }, + prod: { + NODE_ENV: 'production' + } + }, watch: { serverViews: { - files: watchFiles.serverViews, + files: defaultAssets.server.views, options: { livereload: true } }, serverJS: { - files: watchFiles.serverJS, + files: defaultAssets.server.allJS, tasks: ['jshint'], options: { livereload: true } }, clientViews: { - files: watchFiles.clientViews, + files: defaultAssets.client.views, options: { - livereload: true, + livereload: true } }, clientJS: { - files: watchFiles.clientJS, + files: defaultAssets.client.js, tasks: ['jshint'], options: { livereload: true } }, clientCSS: { - files: watchFiles.clientCSS, + files: defaultAssets.client.css, tasks: ['csslint'], options: { livereload: true } + }, + clientSCSS: { + files: defaultAssets.client.sass, + tasks: ['sass', 'csslint'], + options: { + livereload: true + } + }, + clientLESS: { + files: defaultAssets.client.less, + tasks: ['less', 'csslint'], + options: { + livereload: true + } + } + }, + nodemon: { + dev: { + script: 'server.js', + options: { + nodeArgs: ['--debug'], + ext: 'js,html', + watch: _.union(defaultAssets.server.views, defaultAssets.server.allJS, defaultAssets.server.config) + } + } + }, + concurrent: { + default: ['nodemon', 'watch'], + debug: ['nodemon', 'watch', 'node-inspector'], + options: { + logConcurrentOutput: true } }, jshint: { all: { - src: watchFiles.clientJS.concat(watchFiles.serverJS), + src: _.union(defaultAssets.server.allJS, defaultAssets.client.js, testAssets.tests.server, testAssets.tests.client, testAssets.tests.e2e), options: { - jshintrc: true + jshintrc: true, + node: true, + mocha: true, + jasmine: true } } }, csslint: { options: { - csslintrc: '.csslintrc', + csslintrc: '.csslintrc' }, all: { - src: watchFiles.clientCSS + src: defaultAssets.client.css + } + }, + ngAnnotate: { + production: { + files: { + 'public/dist/application.js': defaultAssets.client.js + } } }, uglify: { @@ -78,18 +127,32 @@ module.exports = function(grunt) { cssmin: { combine: { files: { - 'public/dist/application.min.css': '<%= applicationCSSFiles %>' + 'public/dist/application.min.css': defaultAssets.client.css } } }, - nodemon: { - dev: { - script: 'server.js', - options: { - nodeArgs: ['--debug'], - ext: 'js,html', - watch: watchFiles.serverViews.concat(watchFiles.serverJS) - } + sass: { + dist: { + files: [{ + expand: true, + src: defaultAssets.client.sass, + ext: '.css', + rename: function(base, src) { + return src.replace('/scss/', '/css/'); + } + }] + } + }, + less: { + dist: { + files: [{ + expand: true, + src: defaultAssets.client.less, + ext: '.css', + rename: function(base, src) { + return src.replace('/less/', '/css/'); + } + }] } }, 'node-inspector': { @@ -105,73 +168,66 @@ module.exports = function(grunt) { } } }, - ngAnnotate: { - production: { - files: { - 'public/dist/application.js': '<%= applicationJavaScriptFiles %>' - } - } - }, - concurrent: { - default: ['nodemon', 'watch'], - debug: ['nodemon', 'watch', 'node-inspector'], - options: { - logConcurrentOutput: true, - limit: 10 - } - }, - env: { - test: { - NODE_ENV: 'test' - }, - secure: { - NODE_ENV: 'secure' - } - }, mochaTest: { - src: watchFiles.mochaTests, + src: testAssets.tests.server, options: { - reporter: 'spec', - require: 'server.js' + reporter: 'spec' } }, karma: { unit: { configFile: 'karma.conf.js' } + }, + protractor: { + options: { + configFile: 'protractor.conf.js', + keepAlive: true, + noColor: false + }, + e2e: { + options: { + args: {} // Target-specific arguments + } + } } }); - // Load NPM tasks + // Load NPM tasks require('load-grunt-tasks')(grunt); // Making grunt default to force in order not to break the project. grunt.option('force', true); - // A Task for loading the configuration object - grunt.task.registerTask('loadConfig', 'Task that loads the config into a grunt option.', function() { - var init = require('./config/init')(); - var config = require('./config/config'); + // Connect to the MongoDB instance and load the models + grunt.task.registerTask('mongoose', 'Task that connects to the MongoDB instance and loads the application models.', function() { + // Get the callback + var done = this.async(); + + // Use mongoose configuration + var mongoose = require('./config/lib/mongoose.js'); - grunt.config.set('applicationJavaScriptFiles', config.assets.js); - grunt.config.set('applicationCSSFiles', config.assets.css); + // Connect to database + mongoose.connect(function(db) { + done(); + }); }); - // Default task(s). - grunt.registerTask('default', ['lint', 'concurrent:default']); + // Lint CSS and JavaScript files. + grunt.registerTask('lint', ['sass', 'less', 'jshint', 'csslint']); - // Debug task. - grunt.registerTask('debug', ['lint', 'concurrent:debug']); + // Lint project files and minify them into two production files. + grunt.registerTask('build', ['env:dev', 'lint', 'ngAnnotate', 'uglify', 'cssmin']); - // Secure task(s). - grunt.registerTask('secure', ['env:secure', 'lint', 'concurrent:default']); + // Run the project tests + grunt.registerTask('test', ['env:test', 'mongoose', 'mochaTest', 'karma:unit']); - // Lint task(s). - grunt.registerTask('lint', ['jshint', 'csslint']); + // Run the project in development mode + grunt.registerTask('default', ['env:dev', 'lint', 'concurrent:default']); - // Build task(s). - grunt.registerTask('build', ['lint', 'loadConfig', 'ngAnnotate', 'uglify', 'cssmin']); + // Run the project in debug mode + grunt.registerTask('debug', ['env:dev', 'lint', 'concurrent:debug']); - // Test task. - grunt.registerTask('test', ['env:test', 'mochaTest', 'karma:unit']); -}; \ No newline at end of file + // Run the project in production mode + grunt.registerTask('prod', ['build', 'env:prod', 'concurrent:default']); +}; diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000000..b0a7ceda7f --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,187 @@ +'use strict'; + +/** + * Module dependencies. + */ +var _ = require('lodash'), + defaultAssets = require('./config/assets/default'), + testAssets = require('./config/assets/test'), + gulp = require('gulp'), + gulpLoadPlugins = require('gulp-load-plugins'), + runSequence = require('run-sequence'), + plugins = gulpLoadPlugins(); + +// Set NODE_ENV to 'test' +gulp.task('env:test', function () { + process.env.NODE_ENV = 'test'; +}); + +// Set NODE_ENV to 'development' +gulp.task('env:dev', function () { + process.env.NODE_ENV = 'development'; +}); + +// Set NODE_ENV to 'production' +gulp.task('env:prod', function () { + process.env.NODE_ENV = 'production'; +}); + +// Nodemon task +gulp.task('nodemon', function () { + return plugins.nodemon({ + script: 'server.js', + nodeArgs: ['--debug'], + ext: 'js,html', + watch: _.union(defaultAssets.server.views, defaultAssets.server.allJS, defaultAssets.server.config) + }); +}); + +// Watch Files For Changes +gulp.task('watch', function() { + // Start livereload + plugins.livereload.listen(); + + // Add watch rules + gulp.watch(defaultAssets.server.views).on('change', plugins.livereload.changed); + gulp.watch(defaultAssets.server.allJS, ['jshint']).on('change', plugins.livereload.changed); + gulp.watch(defaultAssets.client.views).on('change', plugins.livereload.changed); + gulp.watch(defaultAssets.client.js, ['jshint']).on('change', plugins.livereload.changed); + gulp.watch(defaultAssets.client.css, ['csslint']).on('change', plugins.livereload.changed); + gulp.watch(defaultAssets.client.sass, ['sass', 'csslint']).on('change', plugins.livereload.changed); + gulp.watch(defaultAssets.client.less, ['less', 'csslint']).on('change', plugins.livereload.changed); +}); + +// CSS linting task +gulp.task('csslint', function (done) { + return gulp.src(defaultAssets.client.css) + .pipe(plugins.csslint('.csslintrc')) + .pipe(plugins.csslint.reporter()) + .pipe(plugins.csslint.reporter(function (file) { + if (!file.csslint.errorCount) { + done(); + } + })); +}); + +// JS linting task +gulp.task('jshint', function () { + return gulp.src(_.union(defaultAssets.server.allJS, defaultAssets.client.js, testAssets.tests.server, testAssets.tests.client, testAssets.tests.e2e)) + .pipe(plugins.jshint()) + .pipe(plugins.jshint.reporter('default')) + .pipe(plugins.jshint.reporter('fail')); +}); + + +// JS minifying task +gulp.task('uglify', function () { + return gulp.src(defaultAssets.client.js) + .pipe(plugins.ngAnnotate()) + .pipe(plugins.uglify({ + mangle: false + })) + .pipe(plugins.concat('application.min.js')) + .pipe(gulp.dest('public/dist')); +}); + +// CSS minifying task +gulp.task('cssmin', function () { + return gulp.src(defaultAssets.client.css) + .pipe(plugins.cssmin()) + .pipe(plugins.concat('application.min.css')) + .pipe(gulp.dest('public/dist')); +}); + +// Sass task +gulp.task('sass', function () { + return gulp.src(defaultAssets.client.sass) + .pipe(plugins.sass()) + .pipe(plugins.rename(function (path) { + path.dirname = path.dirname.replace('/scss', '/css'); + })) + .pipe(gulp.dest('./modules/')); +}); + +// Less task +gulp.task('less', function () { + return gulp.src(defaultAssets.client.less) + .pipe(plugins.less()) + .pipe(plugins.rename(function (path) { + path.dirname = path.dirname.replace('/less', '/css'); + })) + .pipe(gulp.dest('./modules/')); +}); + +// Connect to MongoDB using the mongoose module +gulp.task('mongoose', function (done) { + var mongoose = require('./config/lib/mongoose.js'); + + mongoose.connect(function(db) { + done(); + }); +}); + +// Mocha tests task +gulp.task('mocha', function () { + return gulp.src(testAssets.tests.server) + .pipe(plugins.mocha({ + reporter: 'spec' + })); +}); + +// Karma test runner task +gulp.task('karma', function (done) { + return gulp.src([]) + .pipe(plugins.karma({ + configFile: 'karma.conf.js', + action: 'run', + singleRun: true + })) + .on('error', function (err) { + // Make sure failed tests cause gulp to exit non-zero + throw err; + }); +}); + +// Selenium standalone WebDriver update task +gulp.task('webdriver-update', plugins.protractor.webdriver_update); + +// Protractor test runner task +gulp.task('protractor', function () { + gulp.src([]) + .pipe(plugins.protractor.protractor({ + configFile: "protractor.conf.js" + })) + .on('error', function (e) { + throw e + }) +}); + +// Lint CSS and JavaScript files. +gulp.task('lint', function(done) { + runSequence('less', 'sass', ['csslint', 'jshint'], done); +}); + +// Lint project files and minify them into two production files. +gulp.task('build', function(done) { + runSequence('env:dev' ,'lint', ['uglify', 'cssmin'], done); +}); + +// Run the project tests +gulp.task('test', function(done) { + runSequence('env:test', 'mongoose', ['karma', 'mocha'], done); +}); + +// Run the project in development mode +gulp.task('default', function(done) { + runSequence('env:dev', 'lint', ['nodemon', 'watch'], done); +}); + +// Run the project in debug mode +gulp.task('debug', function(done) { + runSequence('env:dev', 'lint', ['nodemon', 'watch'], done); +}); + +// Run the project in production mode +gulp.task('prod', function(done) { + runSequence('build', 'lint', ['nodemon', 'watch'], done); +}); diff --git a/karma.conf.js b/karma.conf.js index e85f39b7ca..0b6ead26de 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -3,16 +3,18 @@ /** * Module dependencies. */ -var applicationConfiguration = require('./config/config'); +var _ = require('lodash'), + defaultAssets = require('./config/assets/default'), + testAssets = require('./config/assets/test'); // Karma configuration -module.exports = function(config) { - config.set({ +module.exports = function(karmaConfig) { + karmaConfig.set({ // Frameworks to use frameworks: ['jasmine'], // List of files / patterns to load in the browser - files: applicationConfiguration.assets.lib.js.concat(applicationConfiguration.assets.js, applicationConfiguration.assets.tests), + files: _.union(defaultAssets.client.lib.js, defaultAssets.client.lib.tests, defaultAssets.client.js, testAssets.tests.client), // Test results reporter to use // Possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' @@ -26,8 +28,8 @@ module.exports = function(config) { colors: true, // Level of logging - // Possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, + // Possible values: karmaConfig.LOG_DISABLE || karmaConfig.LOG_ERROR || karmaConfig.LOG_WARN || karmaConfig.LOG_INFO || karmaConfig.LOG_DEBUG + logLevel: karmaConfig.LOG_INFO, // Enable / disable watching file and executing tests whenever any file changes autoWatch: true, @@ -49,4 +51,4 @@ module.exports = function(config) { // If true, it capture browsers, run tests and exit singleRun: true }); -}; \ No newline at end of file +}; diff --git a/public/modules/articles/articles.client.module.js b/modules/articles/client/articles.client.module.js similarity index 100% rename from public/modules/articles/articles.client.module.js rename to modules/articles/client/articles.client.module.js diff --git a/modules/articles/client/config/articles.client.config.js b/modules/articles/client/config/articles.client.config.js new file mode 100644 index 0000000000..d43e0893f4 --- /dev/null +++ b/modules/articles/client/config/articles.client.config.js @@ -0,0 +1,25 @@ +'use strict'; + +// Configuring the Articles module +angular.module('articles').run(['Menus', + function(Menus) { + // Add the articles dropdown item + Menus.addMenuItem('topbar', { + title: 'Articles', + state: 'articles', + type: 'dropdown' + }); + + // Add the dropdown list item + Menus.addSubMenuItem('topbar', 'articles', { + title: 'List Articles', + state: 'articles.list' + }); + + // Add the dropdown create item + Menus.addSubMenuItem('topbar', 'articles', { + title: 'Create Articles', + state: 'articles.create' + }); + } +]); diff --git a/public/modules/articles/config/articles.client.routes.js b/modules/articles/client/config/articles.client.routes.js similarity index 64% rename from public/modules/articles/config/articles.client.routes.js rename to modules/articles/client/config/articles.client.routes.js index 1531a9a57c..c6890be3ef 100755 --- a/public/modules/articles/config/articles.client.routes.js +++ b/modules/articles/client/config/articles.client.routes.js @@ -5,21 +5,26 @@ angular.module('articles').config(['$stateProvider', function($stateProvider) { // Articles state routing $stateProvider. - state('listArticles', { + state('articles', { + abstract: true, url: '/articles', + template: '' + }). + state('articles.list', { + url: '', templateUrl: 'modules/articles/views/list-articles.client.view.html' }). - state('createArticle', { - url: '/articles/create', + state('articles.create', { + url: '/create', templateUrl: 'modules/articles/views/create-article.client.view.html' }). - state('viewArticle', { - url: '/articles/:articleId', + state('articles.view', { + url: '/:articleId', templateUrl: 'modules/articles/views/view-article.client.view.html' }). - state('editArticle', { - url: '/articles/:articleId/edit', + state('articles.edit', { + url: '/:articleId/edit', templateUrl: 'modules/articles/views/edit-article.client.view.html' }); } -]); \ No newline at end of file +]); diff --git a/public/modules/articles/controllers/articles.client.controller.js b/modules/articles/client/controllers/articles.client.controller.js similarity index 100% rename from public/modules/articles/controllers/articles.client.controller.js rename to modules/articles/client/controllers/articles.client.controller.js diff --git a/public/modules/articles/services/articles.client.service.js b/modules/articles/client/services/articles.client.service.js similarity index 82% rename from public/modules/articles/services/articles.client.service.js rename to modules/articles/client/services/articles.client.service.js index deeb7da58c..5c8967f5a0 100644 --- a/public/modules/articles/services/articles.client.service.js +++ b/modules/articles/client/services/articles.client.service.js @@ -3,7 +3,7 @@ //Articles service used for communicating with the articles REST endpoints angular.module('articles').factory('Articles', ['$resource', function($resource) { - return $resource('articles/:articleId', { + return $resource('api/articles/:articleId', { articleId: '@_id' }, { update: { @@ -11,4 +11,4 @@ angular.module('articles').factory('Articles', ['$resource', } }); } -]); \ No newline at end of file +]); diff --git a/public/modules/articles/views/create-article.client.view.html b/modules/articles/client/views/create-article.client.view.html similarity index 83% rename from public/modules/articles/views/create-article.client.view.html rename to modules/articles/client/views/create-article.client.view.html index ab8db8ef61..79ea510e65 100644 --- a/public/modules/articles/views/create-article.client.view.html +++ b/modules/articles/client/views/create-article.client.view.html @@ -5,10 +5,10 @@

New Article

-
+
- +
@@ -26,4 +26,4 @@

New Article

- \ No newline at end of file + diff --git a/public/modules/articles/views/edit-article.client.view.html b/modules/articles/client/views/edit-article.client.view.html similarity index 59% rename from public/modules/articles/views/edit-article.client.view.html rename to modules/articles/client/views/edit-article.client.view.html index 353cb8e666..7a4d4ca01a 100644 --- a/public/modules/articles/views/edit-article.client.view.html +++ b/modules/articles/client/views/edit-article.client.view.html @@ -3,24 +3,18 @@

Edit Article

-
+
-
+
-
-

Title is required

-
-
+
- -
-
-

Content is required

+
@@ -32,4 +26,4 @@

Edit Article

- \ No newline at end of file + diff --git a/public/modules/articles/views/list-articles.client.view.html b/modules/articles/client/views/list-articles.client.view.html similarity index 74% rename from public/modules/articles/views/list-articles.client.view.html rename to modules/articles/client/views/list-articles.client.view.html index 861ae5b6ba..0d8c3b7900 100644 --- a/public/modules/articles/views/list-articles.client.view.html +++ b/modules/articles/client/views/list-articles.client.view.html @@ -3,7 +3,7 @@

Articles

- No articles yet, why don't you create one? + No articles yet, why don't you create one?
- \ No newline at end of file + diff --git a/public/modules/articles/views/view-article.client.view.html b/modules/articles/client/views/view-article.client.view.html similarity index 87% rename from public/modules/articles/views/view-article.client.view.html rename to modules/articles/client/views/view-article.client.view.html index 312d25c84e..a298412b4e 100644 --- a/public/modules/articles/views/view-article.client.view.html +++ b/modules/articles/client/views/view-article.client.view.html @@ -3,7 +3,7 @@

- + @@ -19,4 +19,4 @@

- \ No newline at end of file + diff --git a/app/controllers/articles.server.controller.js b/modules/articles/server/controllers/articles.server.controller.js similarity index 81% rename from app/controllers/articles.server.controller.js rename to modules/articles/server/controllers/articles.server.controller.js index 0a24e81733..d9c9b45e53 100644 --- a/app/controllers/articles.server.controller.js +++ b/modules/articles/server/controllers/articles.server.controller.js @@ -3,10 +3,11 @@ /** * Module dependencies. */ -var mongoose = require('mongoose'), - errorHandler = require('./errors.server.controller'), +var _ = require('lodash'), + path = require('path'), + mongoose = require('mongoose'), Article = mongoose.model('Article'), - _ = require('lodash'); + errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller')); /** * Create a article @@ -39,7 +40,8 @@ exports.read = function(req, res) { exports.update = function(req, res) { var article = req.article; - article = _.extend(article, req.body); + article.title = req.body.title; + article.content = req.body.content; article.save(function(err) { if (err) { @@ -95,15 +97,3 @@ exports.articleByID = function(req, res, next, id) { next(); }); }; - -/** - * Article authorization middleware - */ -exports.hasAuthorization = function(req, res, next) { - if (req.article.user.id !== req.user.id) { - return res.status(403).send({ - message: 'User is not authorized' - }); - } - next(); -}; \ No newline at end of file diff --git a/app/models/article.server.model.js b/modules/articles/server/models/article.server.model.js similarity index 100% rename from app/models/article.server.model.js rename to modules/articles/server/models/article.server.model.js diff --git a/modules/articles/server/policies/articles.server.policy.js b/modules/articles/server/policies/articles.server.policy.js new file mode 100644 index 0000000000..a8572d9e92 --- /dev/null +++ b/modules/articles/server/policies/articles.server.policy.js @@ -0,0 +1,72 @@ +'use strict'; + +/** + * Module dependencies. + */ +var acl = require('acl'); + +// Using the memory backend +acl = new acl(new acl.memoryBackend()); + +/** + * Invoke Articles Permissions + */ +exports.invokeRolesPolicies = function() { + acl.allow([{ + roles: ['admin'], + allows: [{ + resources: '/api/articles', + permissions: '*' + }, { + resources: '/api/articles/:articleId', + permissions: '*' + }] + }, { + roles: ['user'], + allows: [{ + resources: '/api/articles', + permissions: ['get', 'post'] + }, { + resources: '/api/articles/:articleId', + permissions: ['get'] + }] + }, { + roles: ['guest'], + allows: [{ + resources: '/api/articles', + permissions: ['get'] + }, { + resources: '/api/articles/:articleId', + permissions: ['get'] + }] + }]); +}; + +/** + * Check If Articles Policy Allows + */ +exports.isAllowed = function(req, res, next) { + var roles = (req.user) ? req.user.roles : ['guest']; + + // If an article is being processed and the current user created it then allow any manipulation + if (req.article && req.user && req.article.user.id === req.user.id) { + return next(); + } + + // Check for user roles + acl.areAnyRolesAllowed(roles, req.route.path, req.method.toLowerCase(), function(err, isAllowed) { + if (err) { + // An authorization error occurred. + return res.status(500).send('Unexpected authorization error'); + } else { + if (isAllowed) { + // Access granted! Invoke next middleware + return next(); + } else { + return res.status(403).json({ + message: 'User is not authorized' + }); + } + } + }); +}; diff --git a/modules/articles/server/routes/articles.server.routes.js b/modules/articles/server/routes/articles.server.routes.js new file mode 100644 index 0000000000..49e3697f9f --- /dev/null +++ b/modules/articles/server/routes/articles.server.routes.js @@ -0,0 +1,23 @@ +'use strict'; + +/** + * Module dependencies. + */ +var articlesPolicy = require('../policies/articles.server.policy'), + articles = require('../controllers/articles.server.controller'); + +module.exports = function(app) { + // Articles collection routes + app.route('/api/articles').all(articlesPolicy.isAllowed) + .get(articles.list) + .post(articles.create); + + // Single article routes + app.route('/api/articles/:articleId').all(articlesPolicy.isAllowed) + .get(articles.read) + .put(articles.update) + .delete(articles.delete); + + // Finish by binding the article middleware + app.param('articleId', articles.articleByID); +}; diff --git a/public/modules/articles/tests/articles.client.controller.test.js b/modules/articles/tests/client/articles.client.controller.tests.js similarity index 92% rename from public/modules/articles/tests/articles.client.controller.test.js rename to modules/articles/tests/client/articles.client.controller.tests.js index 7e25c699bb..859a967e47 100644 --- a/public/modules/articles/tests/articles.client.controller.test.js +++ b/modules/articles/tests/client/articles.client.controller.tests.js @@ -61,7 +61,7 @@ var sampleArticles = [sampleArticle]; // Set GET response - $httpBackend.expectGET('articles').respond(sampleArticles); + $httpBackend.expectGET('api/articles').respond(sampleArticles); // Run controller functionality scope.find(); @@ -82,7 +82,7 @@ $stateParams.articleId = '525a8422f6d0f87f0e407a33'; // Set GET response - $httpBackend.expectGET(/articles\/([0-9a-fA-F]{24})$/).respond(sampleArticle); + $httpBackend.expectGET(/api\/articles\/([0-9a-fA-F]{24})$/).respond(sampleArticle); // Run controller functionality scope.findOne(); @@ -111,7 +111,7 @@ scope.content = 'MEAN rocks!'; // Set POST response - $httpBackend.expectPOST('articles', sampleArticlePostData).respond(sampleArticleResponse); + $httpBackend.expectPOST('api/articles', sampleArticlePostData).respond(sampleArticleResponse); // Run controller functionality scope.create(); @@ -137,7 +137,7 @@ scope.article = sampleArticlePutData; // Set PUT response - $httpBackend.expectPUT(/articles\/([0-9a-fA-F]{24})$/).respond(); + $httpBackend.expectPUT(/api\/articles\/([0-9a-fA-F]{24})$/).respond(); // Run controller functionality scope.update(); @@ -157,7 +157,7 @@ scope.articles = [sampleArticle]; // Set expected DELETE response - $httpBackend.expectDELETE(/articles\/([0-9a-fA-F]{24})$/).respond(204); + $httpBackend.expectDELETE(/api\/articles\/([0-9a-fA-F]{24})$/).respond(204); // Run controller functionality scope.remove(sampleArticle); diff --git a/modules/articles/tests/e2e/articles.e2e.tests.js b/modules/articles/tests/e2e/articles.e2e.tests.js new file mode 100644 index 0000000000..2c6e1dd3fd --- /dev/null +++ b/modules/articles/tests/e2e/articles.e2e.tests.js @@ -0,0 +1,10 @@ +'use strict'; + +describe('Articles E2E Tests:', function() { + describe('Test articles page', function() { + it('Should report missing credentials', function() { + browser.get('http://localhost:3000/#!/articles'); + expect(element.all(by.repeater('article in articles')).count()).toEqual(0); + }); + }); +}); diff --git a/app/tests/article.server.model.test.js b/modules/articles/tests/server/article.server.model.tests.js similarity index 99% rename from app/tests/article.server.model.test.js rename to modules/articles/tests/server/article.server.model.tests.js index e3dd60c889..ee5fd067b5 100644 --- a/app/tests/article.server.model.test.js +++ b/modules/articles/tests/server/article.server.model.tests.js @@ -61,4 +61,4 @@ describe('Article Model Unit Tests:', function() { User.remove().exec(); done(); }); -}); \ No newline at end of file +}); diff --git a/app/tests/article.server.routes.test.js b/modules/articles/tests/server/article.server.routes.tests.js similarity index 86% rename from app/tests/article.server.routes.test.js rename to modules/articles/tests/server/article.server.routes.tests.js index 2576095ae1..04874135ef 100644 --- a/app/tests/article.server.routes.test.js +++ b/modules/articles/tests/server/article.server.routes.tests.js @@ -2,21 +2,29 @@ var should = require('should'), request = require('supertest'), - app = require('../../server'), + path = require('path'), mongoose = require('mongoose'), User = mongoose.model('User'), Article = mongoose.model('Article'), - agent = request.agent(app); + express = require(path.resolve('./config/lib/express')); /** * Globals */ -var credentials, user, article; +var app, agent, credentials, user, article; /** * Article routes tests */ describe('Article CRUD tests', function() { + before(function(done) { + // Get application + app = express.init(mongoose); + agent = request.agent(app); + + done(); + }); + beforeEach(function(done) { // Create user credentials credentials = { @@ -47,7 +55,7 @@ describe('Article CRUD tests', function() { }); it('should be able to save an article if logged in', function(done) { - agent.post('/auth/signin') + agent.post('/api/auth/signin') .send(credentials) .expect(200) .end(function(signinErr, signinRes) { @@ -58,7 +66,7 @@ describe('Article CRUD tests', function() { var userId = user.id; // Save a new article - agent.post('/articles') + agent.post('/api/articles') .send(article) .expect(200) .end(function(articleSaveErr, articleSaveRes) { @@ -66,7 +74,7 @@ describe('Article CRUD tests', function() { if (articleSaveErr) done(articleSaveErr); // Get a list of articles - agent.get('/articles') + agent.get('/api/articles') .end(function(articlesGetErr, articlesGetRes) { // Handle article save error if (articlesGetErr) done(articlesGetErr); @@ -86,9 +94,9 @@ describe('Article CRUD tests', function() { }); it('should not be able to save an article if not logged in', function(done) { - agent.post('/articles') + agent.post('/api/articles') .send(article) - .expect(401) + .expect(403) .end(function(articleSaveErr, articleSaveRes) { // Call the assertion callback done(articleSaveErr); @@ -99,7 +107,7 @@ describe('Article CRUD tests', function() { // Invalidate title field article.title = ''; - agent.post('/auth/signin') + agent.post('/api/auth/signin') .send(credentials) .expect(200) .end(function(signinErr, signinRes) { @@ -110,13 +118,13 @@ describe('Article CRUD tests', function() { var userId = user.id; // Save a new article - agent.post('/articles') + agent.post('/api/articles') .send(article) .expect(400) .end(function(articleSaveErr, articleSaveRes) { // Set message assertion (articleSaveRes.body.message).should.match('Title cannot be blank'); - + // Handle article save error done(articleSaveErr); }); @@ -124,7 +132,7 @@ describe('Article CRUD tests', function() { }); it('should be able to update an article if signed in', function(done) { - agent.post('/auth/signin') + agent.post('/api/auth/signin') .send(credentials) .expect(200) .end(function(signinErr, signinRes) { @@ -135,7 +143,7 @@ describe('Article CRUD tests', function() { var userId = user.id; // Save a new article - agent.post('/articles') + agent.post('/api/articles') .send(article) .expect(200) .end(function(articleSaveErr, articleSaveRes) { @@ -146,7 +154,7 @@ describe('Article CRUD tests', function() { article.title = 'WHY YOU GOTTA BE SO MEAN?'; // Update an existing article - agent.put('/articles/' + articleSaveRes.body._id) + agent.put('/api/articles/' + articleSaveRes.body._id) .send(article) .expect(200) .end(function(articleUpdateErr, articleUpdateRes) { @@ -171,7 +179,7 @@ describe('Article CRUD tests', function() { // Save the article articleObj.save(function() { // Request articles - request(app).get('/articles') + request(app).get('/api/articles') .end(function(req, res) { // Set assertion res.body.should.be.an.Array.with.lengthOf(1); @@ -190,7 +198,7 @@ describe('Article CRUD tests', function() { // Save the article articleObj.save(function() { - request(app).get('/articles/' + articleObj._id) + request(app).get('/api/articles/' + articleObj._id) .end(function(req, res) { // Set assertion res.body.should.be.an.Object.with.property('title', article.title); @@ -202,7 +210,7 @@ describe('Article CRUD tests', function() { }); it('should be able to delete an article if signed in', function(done) { - agent.post('/auth/signin') + agent.post('/api/auth/signin') .send(credentials) .expect(200) .end(function(signinErr, signinRes) { @@ -213,7 +221,7 @@ describe('Article CRUD tests', function() { var userId = user.id; // Save a new article - agent.post('/articles') + agent.post('/api/articles') .send(article) .expect(200) .end(function(articleSaveErr, articleSaveRes) { @@ -221,7 +229,7 @@ describe('Article CRUD tests', function() { if (articleSaveErr) done(articleSaveErr); // Delete an existing article - agent.delete('/articles/' + articleSaveRes.body._id) + agent.delete('/api/articles/' + articleSaveRes.body._id) .send(article) .expect(200) .end(function(articleDeleteErr, articleDeleteRes) { @@ -248,11 +256,11 @@ describe('Article CRUD tests', function() { // Save the article articleObj.save(function() { // Try deleting article - request(app).delete('/articles/' + articleObj._id) - .expect(401) + request(app).delete('/api/articles/' + articleObj._id) + .expect(403) .end(function(articleDeleteErr, articleDeleteRes) { // Set message assertion - (articleDeleteRes.body.message).should.match('User is not logged in'); + (articleDeleteRes.body.message).should.match('User is not authorized'); // Handle article error error done(articleDeleteErr); @@ -266,4 +274,4 @@ describe('Article CRUD tests', function() { Article.remove().exec(); done(); }); -}); \ No newline at end of file +}); diff --git a/public/modules/users/users.client.module.js b/modules/chat/client/chat.client.module.js old mode 100755 new mode 100644 similarity index 61% rename from public/modules/users/users.client.module.js rename to modules/chat/client/chat.client.module.js index b11998607b..80ef9c2941 --- a/public/modules/users/users.client.module.js +++ b/modules/chat/client/chat.client.module.js @@ -1,4 +1,4 @@ 'use strict'; // Use Applicaion configuration module to register a new module -ApplicationConfiguration.registerModule('users'); \ No newline at end of file +ApplicationConfiguration.registerModule('chat'); diff --git a/modules/chat/client/config/chat.client.config.js b/modules/chat/client/config/chat.client.config.js new file mode 100644 index 0000000000..67a4719f4a --- /dev/null +++ b/modules/chat/client/config/chat.client.config.js @@ -0,0 +1,12 @@ +'use strict'; + +// Configuring the Chat module +angular.module('chat').run(['Menus', + function(Menus) { + // Set top bar menu items + Menus.addMenuItem('topbar', { + title: 'Chat', + state: 'chat' + }); + } +]); diff --git a/modules/chat/client/config/chat.client.routes.js b/modules/chat/client/config/chat.client.routes.js new file mode 100644 index 0000000000..a86defc19d --- /dev/null +++ b/modules/chat/client/config/chat.client.routes.js @@ -0,0 +1,12 @@ +'use strict'; + +// Configure the 'chat' module routes +angular.module('chat').config(['$stateProvider', + function($stateProvider) { + $stateProvider. + state('chat', { + url: '/chat', + templateUrl: 'modules/chat/views/chat.client.view.html' + }); + } +]); \ No newline at end of file diff --git a/modules/chat/client/controllers/chat.client.controller.js b/modules/chat/client/controllers/chat.client.controller.js new file mode 100644 index 0000000000..ffb83330ad --- /dev/null +++ b/modules/chat/client/controllers/chat.client.controller.js @@ -0,0 +1,34 @@ +'use strict'; + +// Create the 'chat' controller +angular.module('chat').controller('ChatController', ['$scope', 'Socket', + function($scope, Socket) { + // Create a messages array + $scope.messages = []; + + // Add an event listener to the 'chatMessage' event + Socket.on('chatMessage', function(message) { + $scope.messages.unshift(message); + }); + + // Create a controller method for sending messages + $scope.sendMessage = function() { + // Create a new message object + var message = { + text: this.messageText + }; + + // Emit a 'chatMessage' message event + Socket.emit('chatMessage', message); + + // Clear the message text + this.messageText = ''; + }; + + // Remove the event listener when the controller instance is destroyed + $scope.$on('$destroy', function() { + Socket.removeListener('chatMessage'); + }); + + } +]); diff --git a/modules/chat/client/css/chat.css b/modules/chat/client/css/chat.css new file mode 100644 index 0000000000..d04a8689e3 --- /dev/null +++ b/modules/chat/client/css/chat.css @@ -0,0 +1,18 @@ +.chat-message { + margin-top: 10px; + padding-top: 10px; +} + +.chat-message:not(:first-child) { + border-top: 1px solid #e7e7e7; +} + +.chat-message-details { + margin-left: 10px; +} + +.chat-profile-image { + height: 28px; + width: 28px; + border-radius: 50%; +} diff --git a/modules/chat/client/views/chat.client.view.html b/modules/chat/client/views/chat.client.view.html new file mode 100644 index 0000000000..c3cd60190b --- /dev/null +++ b/modules/chat/client/views/chat.client.view.html @@ -0,0 +1,28 @@ + +
+ + +
+
+
+ + + + +
+
+
+
    + +
  • + + {{message.username}} +
    +
    + +
    +
  • +
+
diff --git a/modules/chat/server/sockets/chat.server.socket.config.js b/modules/chat/server/sockets/chat.server.socket.config.js new file mode 100644 index 0000000000..d48791d66d --- /dev/null +++ b/modules/chat/server/sockets/chat.server.socket.config.js @@ -0,0 +1,34 @@ +'use strict'; + +// Create the chat configuration +module.exports = function(io, socket) { + // Emit the status event when a new socket client is connected + io.emit('chatMessage', { + type: 'status', + text: 'Is now connected', + created: Date.now(), + profileImageURL: socket.request.user.profileImageURL, + username: socket.request.user.username + }); + + // Send a chat messages to all connected sockets when a message is received + socket.on('chatMessage', function(message) { + message.type = 'message'; + message.created = Date.now(); + message.profileImageURL = socket.request.user.profileImageURL; + message.username = socket.request.user.username; + + // Emit the 'chatMessage' event + io.emit('chatMessage', message); + }); + + // Emit the status event when a socket client is disconnected + socket.on('disconnect', function() { + io.emit('chatMessage', { + type: 'status', + text: 'disconnected', + created: Date.now(), + username: socket.request.user.username + }); + }); +}; diff --git a/modules/chat/tests/client/chat.client.controller.tests.js b/modules/chat/tests/client/chat.client.controller.tests.js new file mode 100644 index 0000000000..e842322891 --- /dev/null +++ b/modules/chat/tests/client/chat.client.controller.tests.js @@ -0,0 +1,10 @@ +'use strict'; + +/** + * Chat client controller tests + */ +(function() { + describe('ChatController', function() { + // TODO: Add chat client controller tests + }); +}()); \ No newline at end of file diff --git a/modules/chat/tests/e2e/chat.e2e.tests.js b/modules/chat/tests/e2e/chat.e2e.tests.js new file mode 100644 index 0000000000..06f5fc6226 --- /dev/null +++ b/modules/chat/tests/e2e/chat.e2e.tests.js @@ -0,0 +1,8 @@ +'use strict'; + +/** + * Chat e2e tests + */ +describe('Chat E2E Tests:', function() { + // TODO: Add chat e2e tests +}); \ No newline at end of file diff --git a/modules/chat/tests/server/chat.socket.tests.js b/modules/chat/tests/server/chat.socket.tests.js new file mode 100644 index 0000000000..8a82c74ea2 --- /dev/null +++ b/modules/chat/tests/server/chat.socket.tests.js @@ -0,0 +1,8 @@ +'use strict'; + +/** + * Chat socket tests + */ +describe('Chat Socket Tests:', function() { + // TODO: Add chat socket tests +}); \ No newline at end of file diff --git a/public/config.js b/modules/core/client/app/config.js similarity index 90% rename from public/config.js rename to modules/core/client/app/config.js index 75de1c4bce..b4b5947cd3 100644 --- a/public/config.js +++ b/modules/core/client/app/config.js @@ -4,7 +4,7 @@ var ApplicationConfiguration = (function() { // Init module configuration options var applicationModuleName = 'mean'; - var applicationModuleVendorDependencies = ['ngResource', 'ngAnimate', 'ui.router', 'ui.bootstrap', 'ui.utils']; + var applicationModuleVendorDependencies = ['ngResource', 'ngAnimate', 'ui.router', 'ui.bootstrap', 'ui.utils', 'angularFileUpload']; // Add a new vertical module var registerModule = function(moduleName, dependencies) { @@ -20,4 +20,4 @@ var ApplicationConfiguration = (function() { applicationModuleVendorDependencies: applicationModuleVendorDependencies, registerModule: registerModule }; -})(); \ No newline at end of file +})(); diff --git a/public/application.js b/modules/core/client/app/init.js similarity index 92% rename from public/application.js rename to modules/core/client/app/init.js index 19bb411ef2..5e144afce5 100644 --- a/public/application.js +++ b/modules/core/client/app/init.js @@ -6,7 +6,7 @@ angular.module(ApplicationConfiguration.applicationModuleName, ApplicationConfig // Setting HTML5 Location Mode angular.module(ApplicationConfiguration.applicationModuleName).config(['$locationProvider', function($locationProvider) { - $locationProvider.hashPrefix('!'); + $locationProvider.html5Mode(true).hashPrefix('!'); } ]); @@ -17,4 +17,4 @@ angular.element(document).ready(function() { //Then init the app angular.bootstrap(document, [ApplicationConfiguration.applicationModuleName]); -}); \ No newline at end of file +}); diff --git a/public/modules/core/config/core.client.routes.js b/modules/core/client/config/core.client.routes.js similarity index 100% rename from public/modules/core/config/core.client.routes.js rename to modules/core/client/config/core.client.routes.js diff --git a/public/modules/core/controllers/header.client.controller.js b/modules/core/client/controllers/header.client.controller.js similarity index 67% rename from public/modules/core/controllers/header.client.controller.js rename to modules/core/client/controllers/header.client.controller.js index 1b8c2b7bec..64c3019724 100644 --- a/public/modules/core/controllers/header.client.controller.js +++ b/modules/core/client/controllers/header.client.controller.js @@ -1,11 +1,16 @@ 'use strict'; -angular.module('core').controller('HeaderController', ['$scope', 'Authentication', 'Menus', - function($scope, Authentication, Menus) { +angular.module('core').controller('HeaderController', ['$scope', '$state', 'Authentication', 'Menus', + function($scope, $state, Authentication, Menus) { + // Expose view variables + $scope.$state = $state; $scope.authentication = Authentication; - $scope.isCollapsed = false; + + // Get the topbar menu $scope.menu = Menus.getMenu('topbar'); + // Toggle the menu items + $scope.isCollapsed = false; $scope.toggleCollapsibleMenu = function() { $scope.isCollapsed = !$scope.isCollapsed; }; @@ -15,4 +20,4 @@ angular.module('core').controller('HeaderController', ['$scope', 'Authentication $scope.isCollapsed = false; }); } -]); \ No newline at end of file +]); diff --git a/public/modules/core/controllers/home.client.controller.js b/modules/core/client/controllers/home.client.controller.js similarity index 98% rename from public/modules/core/controllers/home.client.controller.js rename to modules/core/client/controllers/home.client.controller.js index 63d0f297ae..086632edd3 100644 --- a/public/modules/core/controllers/home.client.controller.js +++ b/modules/core/client/controllers/home.client.controller.js @@ -1,6 +1,5 @@ 'use strict'; - angular.module('core').controller('HomeController', ['$scope', 'Authentication', function($scope, Authentication) { // This provides Authentication context. diff --git a/public/modules/core/core.client.module.js b/modules/core/client/core.client.module.js similarity index 100% rename from public/modules/core/core.client.module.js rename to modules/core/client/core.client.module.js diff --git a/modules/core/client/css/core.css b/modules/core/client/css/core.css new file mode 100644 index 0000000000..e89af83986 --- /dev/null +++ b/modules/core/client/css/core.css @@ -0,0 +1,33 @@ +.content { + margin-top: 50px; +} +.undecorated-link:hover { + text-decoration: none; +} +[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { + display: none !important; +} +.ng-invalid.ng-dirty{ + border-color:#FA787E; +} +.ng-valid.ng-dirty{ + border-color:#78FA89; +} + +.header-profile-image { + opacity: 0.8; + height: 28px; + width: 28px; + border-radius: 50%; + margin-right: 5px; +} + +.open .header-profile-image, +a:hover .header-profile-image { + opacity: 1; +} + +.user-header-dropdown-toggle { + padding-top: 11px !important; + padding-bottom: 11px !important; +} diff --git a/public/modules/core/img/brand/favicon.ico b/modules/core/client/img/brand/favicon.ico similarity index 100% rename from public/modules/core/img/brand/favicon.ico rename to modules/core/client/img/brand/favicon.ico diff --git a/public/modules/core/img/brand/logo.png b/modules/core/client/img/brand/logo.png similarity index 100% rename from public/modules/core/img/brand/logo.png rename to modules/core/client/img/brand/logo.png diff --git a/public/modules/core/img/loaders/loader.gif b/modules/core/client/img/loaders/loader.gif similarity index 100% rename from public/modules/core/img/loaders/loader.gif rename to modules/core/client/img/loaders/loader.gif diff --git a/modules/core/client/services/menus.client.service.js b/modules/core/client/services/menus.client.service.js new file mode 100644 index 0000000000..020d7cf32b --- /dev/null +++ b/modules/core/client/services/menus.client.service.js @@ -0,0 +1,179 @@ +'use strict'; + +//Menu service used for managing menus +angular.module('core').service('Menus', [ + + function() { + // Define a set of default roles + this.defaultRoles = ['*']; + + // Define the menus object + this.menus = {}; + + // A private function for rendering decision + var shouldRender = function(user) { + if (user) { + if (!!~this.roles.indexOf('*')) { + return true; + } else { + for (var userRoleIndex in user.roles) { + for (var roleIndex in this.roles) { + if (this.roles[roleIndex] === user.roles[userRoleIndex]) { + return true; + } + } + } + } + } else { + return this.isPublic; + } + + return false; + }; + + // Validate menu existance + this.validateMenuExistance = function(menuId) { + if (menuId && menuId.length) { + if (this.menus[menuId]) { + return true; + } else { + throw new Error('Menu does not exists'); + } + } else { + throw new Error('MenuId was not provided'); + } + + return false; + }; + + // Get the menu object by menu id + this.getMenu = function(menuId) { + // Validate that the menu exists + this.validateMenuExistance(menuId); + + // Return the menu object + return this.menus[menuId]; + }; + + // Add new menu object by menu id + this.addMenu = function(menuId, options) { + options = options || {}; + + // Create the new menu + this.menus[menuId] = { + isPublic: ((options.isPublic === null || typeof options.isPublic === 'undefined') ? true : options.isPublic), + roles: options.roles || this.defaultRoles, + items: options.items || [], + shouldRender: shouldRender + }; + + // Return the menu object + return this.menus[menuId]; + }; + + // Remove existing menu object by menu id + this.removeMenu = function(menuId) { + // Validate that the menu exists + this.validateMenuExistance(menuId); + + // Return the menu object + delete this.menus[menuId]; + }; + + // Add menu item object + this.addMenuItem = function(menuId, options) { + options = options || {}; + + // Validate that the menu exists + this.validateMenuExistance(menuId); + + // Push new menu item + this.menus[menuId].items.push({ + title: options.title || '', + state: options.state || '', + type: options.type || 'item', + class: options.class, + isPublic: ((options.isPublic === null || typeof options.isPublic === 'undefined') ? this.menus[menuId].isPublic : options.isPublic), + roles: ((options.roles === null || typeof options.roles === 'undefined') ? this.menus[menuId].roles : options.roles), + position: options.position || 0, + items: [], + shouldRender: shouldRender + }); + + // Add submenu items + if (options.items) { + for (var i in options.items) { + this.addSubMenuItem(menuId, options.link, options.items[i]); + } + } + + // Return the menu object + return this.menus[menuId]; + }; + + // Add submenu item object + this.addSubMenuItem = function(menuId, parentItemState, options) { + options = options || {}; + + // Validate that the menu exists + this.validateMenuExistance(menuId); + + // Search for menu item + for (var itemIndex in this.menus[menuId].items) { + if (this.menus[menuId].items[itemIndex].state === parentItemState) { + // Push new submenu item + this.menus[menuId].items[itemIndex].items.push({ + title: options.title || '', + state: options.state|| '', + isPublic: ((options.isPublic === null || typeof options.isPublic === 'undefined') ? this.menus[menuId].items[itemIndex].isPublic : options.isPublic), + roles: ((options.roles === null || typeof options.roles === 'undefined') ? this.menus[menuId].items[itemIndex].roles : options.roles), + position: options.position || 0, + shouldRender: shouldRender + }); + } + } + + // Return the menu object + return this.menus[menuId]; + }; + + // Remove existing menu object by menu id + this.removeMenuItem = function(menuId, menuItemURL) { + // Validate that the menu exists + this.validateMenuExistance(menuId); + + // Search for menu item to remove + for (var itemIndex in this.menus[menuId].items) { + if (this.menus[menuId].items[itemIndex].link === menuItemURL) { + this.menus[menuId].items.splice(itemIndex, 1); + } + } + + // Return the menu object + return this.menus[menuId]; + }; + + // Remove existing menu object by menu id + this.removeSubMenuItem = function(menuId, submenuItemURL) { + // Validate that the menu exists + this.validateMenuExistance(menuId); + + // Search for menu item to remove + for (var itemIndex in this.menus[menuId].items) { + for (var subitemIndex in this.menus[menuId].items[itemIndex].items) { + if (this.menus[menuId].items[itemIndex].items[subitemIndex].link === submenuItemURL) { + this.menus[menuId].items[itemIndex].items.splice(subitemIndex, 1); + } + } + } + + // Return the menu object + return this.menus[menuId]; + }; + + //Adding the topbar menu + this.addMenu('topbar', { + isPublic: false + }); + } +]); diff --git a/modules/core/client/services/socket.io.client.service.js b/modules/core/client/services/socket.io.client.service.js new file mode 100644 index 0000000000..e66224cf5c --- /dev/null +++ b/modules/core/client/services/socket.io.client.service.js @@ -0,0 +1,38 @@ +'use strict'; + +// Create the Socket.io wrapper service +angular.module('core').service('Socket', ['Authentication', '$state', '$timeout', + function(Authentication, $state, $timeout) { + // Connect to the Socket.io server only when authenticated + if (Authentication.user) { + this.socket = io(); + } else { + $state.go('home'); + } + + // Wrap the Socket.io 'on' method + this.on = function(eventName, callback) { + if (this.socket) { + this.socket.on(eventName, function(data) { + $timeout(function() { + callback(data); + }); + }); + } + }; + + // Wrap the Socket.io 'emit' method + this.emit = function(eventName, data) { + if (this.socket) { + this.socket.emit(eventName, data); + } + }; + + // Wrap the Socket.io 'removeListener' method + this.removeListener = function(eventName) { + if (this.socket) { + this.socket.removeListener(eventName); + } + }; + } +]); diff --git a/public/modules/core/views/header.client.view.html b/modules/core/client/views/header.client.view.html similarity index 50% rename from public/modules/core/views/header.client.view.html rename to modules/core/client/views/header.client.view.html index eb6ac6dfa4..754e9bc241 100644 --- a/public/modules/core/views/header.client.view.html +++ b/modules/core/client/views/header.client.view.html @@ -6,53 +6,57 @@ -
MEAN.JS + MEAN.JS
- \ No newline at end of file + diff --git a/public/modules/core/views/home.client.view.html b/modules/core/client/views/home.client.view.html similarity index 90% rename from public/modules/core/views/home.client.view.html rename to modules/core/client/views/home.client.view.html index 0fa572df5f..3f8242f280 100644 --- a/public/modules/core/views/home.client.view.html +++ b/modules/core/client/views/home.client.view.html @@ -18,7 +18,7 @@
-

Congrats! You've configured and ran the sample application successfully.

+

Congrats! You've configured and run the sample application.

MEAN.JS is a web application boilerplate, which means you should start changing everything :-)

This sample application tracks users and articles.

@@ -91,4 +91,4 @@

MEAN.JS Documentation


Enjoy & Keep Us Updated,
The MEAN.JS Team. - \ No newline at end of file + diff --git a/modules/core/server/controllers/core.server.controller.js b/modules/core/server/controllers/core.server.controller.js new file mode 100644 index 0000000000..3b427c38f7 --- /dev/null +++ b/modules/core/server/controllers/core.server.controller.js @@ -0,0 +1,28 @@ +'use strict'; + +/** + * Render the main applicaion page + */ +exports.renderIndex = function(req, res) { + res.render('modules/core/server/views/index', { + user: req.user || null + }); +}; + +/** + * Render the server error page + */ +exports.renderServerError = function(req, res) { + res.status(500).render('modules/core/server/views/500', { + error: 'Oops! Something went wrong...' + }); +}; + +/** + * Render the server not found page + */ +exports.renderNotFound = function(req, res) { + res.status(404).render('modules/core/server/views/404', { + url: req.originalUrl + }); +}; diff --git a/app/controllers/errors.server.controller.js b/modules/core/server/controllers/errors.server.controller.js similarity index 89% rename from app/controllers/errors.server.controller.js rename to modules/core/server/controllers/errors.server.controller.js index 41078b4d3a..e4604f8c9c 100644 --- a/app/controllers/errors.server.controller.js +++ b/modules/core/server/controllers/errors.server.controller.js @@ -8,10 +8,10 @@ var getUniqueErrorMessage = function(err) { try { var fieldName = err.err.substring(err.err.lastIndexOf('.$') + 2, err.err.lastIndexOf('_1')); - output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists'; + output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exist'; - } catch (ex) { - output = 'Unique field already exists'; + } catch(ex) { + output = 'Unique field already exist'; } return output; @@ -22,7 +22,7 @@ var getUniqueErrorMessage = function(err) { */ exports.getErrorMessage = function(err) { var message = ''; - + if (err.code) { switch (err.code) { case 11000: diff --git a/modules/core/server/routes/core.server.routes.js b/modules/core/server/routes/core.server.routes.js new file mode 100644 index 0000000000..33a436641c --- /dev/null +++ b/modules/core/server/routes/core.server.routes.js @@ -0,0 +1,13 @@ +'use strict'; + +module.exports = function(app) { + // Root routing + var core = require('../controllers/core.server.controller'); + + // Define error pages + app.route('/server-error').get(core.renderServerError); + app.route('/not-found').get(core.renderNotFound); + + // Define application route + app.route('/*').get(core.renderIndex); +}; diff --git a/app/views/404.server.view.html b/modules/core/server/views/404.server.view.html similarity index 100% rename from app/views/404.server.view.html rename to modules/core/server/views/404.server.view.html diff --git a/app/views/500.server.view.html b/modules/core/server/views/500.server.view.html similarity index 100% rename from app/views/500.server.view.html rename to modules/core/server/views/500.server.view.html diff --git a/app/views/index.server.view.html b/modules/core/server/views/index.server.view.html similarity index 100% rename from app/views/index.server.view.html rename to modules/core/server/views/index.server.view.html diff --git a/app/views/layout.server.view.html b/modules/core/server/views/layout.server.view.html similarity index 75% rename from app/views/layout.server.view.html rename to modules/core/server/views/layout.server.view.html index 9f47509e80..4a55542ab5 100644 --- a/app/views/layout.server.view.html +++ b/modules/core/server/views/layout.server.view.html @@ -8,6 +8,10 @@ + + + + @@ -55,14 +59,28 @@ + + + {% for jsFile in jsFiles %}{% endfor %} {% if process.env.NODE_ENV === 'development' %} - + {% endif %} + + + - \ No newline at end of file + diff --git a/public/modules/core/tests/header.client.controller.test.js b/modules/core/tests/client/header.client.controller.tests.js similarity index 100% rename from public/modules/core/tests/header.client.controller.test.js rename to modules/core/tests/client/header.client.controller.tests.js diff --git a/public/modules/core/tests/home.client.controller.test.js b/modules/core/tests/client/home.client.controller.tests.js similarity index 100% rename from public/modules/core/tests/home.client.controller.test.js rename to modules/core/tests/client/home.client.controller.tests.js diff --git a/public/modules/users/config/users.client.config.js b/modules/users/client/config/users.client.config.js similarity index 77% rename from public/modules/users/config/users.client.config.js rename to modules/users/client/config/users.client.config.js index 0bfc8b640b..19d2c115e1 100644 --- a/public/modules/users/config/users.client.config.js +++ b/modules/users/client/config/users.client.config.js @@ -2,12 +2,12 @@ // Config HTTP Error Handling angular.module('users').config(['$httpProvider', - function($httpProvider) { + function ($httpProvider) { // Set the httpProvider "not authorized" interceptor $httpProvider.interceptors.push(['$q', '$location', 'Authentication', - function($q, $location, Authentication) { + function ($q, $location, Authentication) { return { - responseError: function(rejection) { + responseError: function (rejection) { switch (rejection.status) { case 401: // Deauthenticate the global user @@ -17,7 +17,7 @@ angular.module('users').config(['$httpProvider', $location.path('signin'); break; case 403: - // Add unauthorized behaviour + // Add unauthorized behaviour break; } @@ -27,4 +27,4 @@ angular.module('users').config(['$httpProvider', } ]); } -]); \ No newline at end of file +]); diff --git a/modules/users/client/config/users.client.routes.js b/modules/users/client/config/users.client.routes.js new file mode 100755 index 0000000000..9b0546360e --- /dev/null +++ b/modules/users/client/config/users.client.routes.js @@ -0,0 +1,69 @@ +'use strict'; + +// Setting up route +angular.module('users').config(['$stateProvider', + function ($stateProvider) { + // Users state routing + $stateProvider. + state('settings', { + abstract: true, + url: '/settings', + templateUrl: 'modules/users/views/settings/settings.client.view.html' + }). + state('settings.profile', { + url: '/profile', + templateUrl: 'modules/users/views/settings/edit-profile.client.view.html' + }). + state('settings.password', { + url: '/password', + templateUrl: 'modules/users/views/settings/change-password.client.view.html' + }). + state('settings.accounts', { + url: '/accounts', + templateUrl: 'modules/users/views/settings/manage-social-accounts.client.view.html' + }). + state('settings.picture', { + url: '/picture', + templateUrl: 'modules/users/views/settings/change-profile-picture.client.view.html' + }). + state('authentication', { + abstract: true, + url: '/authentication', + templateUrl: 'modules/users/views/authentication/authentication.client.view.html' + }). + state('authentication.signup', { + url: '/signup', + templateUrl: 'modules/users/views/authentication/signup.client.view.html' + }). + state('authentication.signin', { + url: '/signin', + templateUrl: 'modules/users/views/authentication/signin.client.view.html' + }). + state('password', { + abstract: true, + url: '/password', + template: '' + }). + state('password.forgot', { + url: '/forgot', + templateUrl: 'modules/users/views/password/forgot-password.client.view.html' + }). + state('password.reset', { + abstract: true, + url: '/reset', + template: '' + }). + state('password.reset.invalid', { + url: '/invalid', + templateUrl: 'modules/users/views/password/reset-password-invalid.client.view.html' + }). + state('password.reset.success', { + url: '/success', + templateUrl: 'modules/users/views/password/reset-password-success.client.view.html' + }). + state('password.reset.form', { + url: '/:token', + templateUrl: 'modules/users/views/password/reset-password.client.view.html' + }); + } +]); diff --git a/public/modules/users/controllers/authentication.client.controller.js b/modules/users/client/controllers/authentication.client.controller.js similarity index 84% rename from public/modules/users/controllers/authentication.client.controller.js rename to modules/users/client/controllers/authentication.client.controller.js index 3e27cc3b88..dbc1cc0ea2 100644 --- a/public/modules/users/controllers/authentication.client.controller.js +++ b/modules/users/client/controllers/authentication.client.controller.js @@ -8,7 +8,7 @@ angular.module('users').controller('AuthenticationController', ['$scope', '$http if ($scope.authentication.user) $location.path('/'); $scope.signup = function() { - $http.post('/auth/signup', $scope.credentials).success(function(response) { + $http.post('/api/auth/signup', $scope.credentials).success(function(response) { // If successful we assign the response to the global user model $scope.authentication.user = response; @@ -20,7 +20,7 @@ angular.module('users').controller('AuthenticationController', ['$scope', '$http }; $scope.signin = function() { - $http.post('/auth/signin', $scope.credentials).success(function(response) { + $http.post('/api/auth/signin', $scope.credentials).success(function(response) { // If successful we assign the response to the global user model $scope.authentication.user = response; diff --git a/public/modules/users/controllers/password.client.controller.js b/modules/users/client/controllers/password.client.controller.js similarity index 86% rename from public/modules/users/controllers/password.client.controller.js rename to modules/users/client/controllers/password.client.controller.js index dbc9e92977..f5bc915c82 100644 --- a/public/modules/users/controllers/password.client.controller.js +++ b/modules/users/client/controllers/password.client.controller.js @@ -11,7 +11,7 @@ angular.module('users').controller('PasswordController', ['$scope', '$stateParam $scope.askForPasswordReset = function() { $scope.success = $scope.error = null; - $http.post('/auth/forgot', $scope.credentials).success(function(response) { + $http.post('/api/auth/forgot', $scope.credentials).success(function(response) { // Show user success message and clear form $scope.credentials = null; $scope.success = response.message; @@ -27,7 +27,7 @@ angular.module('users').controller('PasswordController', ['$scope', '$stateParam $scope.resetUserPassword = function() { $scope.success = $scope.error = null; - $http.post('/auth/reset/' + $stateParams.token, $scope.passwordDetails).success(function(response) { + $http.post('/api/auth/reset/' + $stateParams.token, $scope.passwordDetails).success(function(response) { // If successful show success message and clear form $scope.passwordDetails = null; diff --git a/public/modules/users/controllers/settings.client.controller.js b/modules/users/client/controllers/settings.client.controller.js similarity index 92% rename from public/modules/users/controllers/settings.client.controller.js rename to modules/users/client/controllers/settings.client.controller.js index 8616fc9463..70b16c5151 100644 --- a/public/modules/users/controllers/settings.client.controller.js +++ b/modules/users/client/controllers/settings.client.controller.js @@ -25,7 +25,7 @@ angular.module('users').controller('SettingsController', ['$scope', '$http', '$l $scope.removeUserSocialAccount = function(provider) { $scope.success = $scope.error = null; - $http.delete('/users/accounts', { + $http.delete('/api/users/accounts', { params: { provider: provider } @@ -40,10 +40,10 @@ angular.module('users').controller('SettingsController', ['$scope', '$http', '$l // Update a user profile $scope.updateUserProfile = function(isValid) { - if (isValid) { + if (isValid){ $scope.success = $scope.error = null; var user = new Users($scope.user); - + user.$update(function(response) { $scope.success = true; Authentication.user = response; @@ -59,7 +59,7 @@ angular.module('users').controller('SettingsController', ['$scope', '$http', '$l $scope.changeUserPassword = function() { $scope.success = $scope.error = null; - $http.post('/users/password', $scope.passwordDetails).success(function(response) { + $http.post('/api/users/password', $scope.passwordDetails).success(function(response) { // If successful show success message and clear form $scope.success = true; $scope.passwordDetails = null; @@ -68,4 +68,4 @@ angular.module('users').controller('SettingsController', ['$scope', '$http', '$l }); }; } -]); \ No newline at end of file +]); diff --git a/modules/users/client/controllers/settings/change-password.client.controller.js b/modules/users/client/controllers/settings/change-password.client.controller.js new file mode 100644 index 0000000000..26575a81bb --- /dev/null +++ b/modules/users/client/controllers/settings/change-password.client.controller.js @@ -0,0 +1,20 @@ +'use strict'; + +angular.module('users').controller('ChangePasswordController', ['$scope', '$http', '$location', 'Users', 'Authentication', + function($scope, $http, $location, Users, Authentication) { + $scope.user = Authentication.user; + + // Change user password + $scope.changeUserPassword = function() { + $scope.success = $scope.error = null; + + $http.post('/api/users/password', $scope.passwordDetails).success(function(response) { + // If successful show success message and clear form + $scope.success = true; + $scope.passwordDetails = null; + }).error(function(response) { + $scope.error = response.message; + }); + }; + } +]); diff --git a/modules/users/client/controllers/settings/change-profile-picture.client.controller.js b/modules/users/client/controllers/settings/change-profile-picture.client.controller.js new file mode 100644 index 0000000000..d131c720fe --- /dev/null +++ b/modules/users/client/controllers/settings/change-profile-picture.client.controller.js @@ -0,0 +1,72 @@ +'use strict'; + +angular.module('users').controller('ChangeProfilePictureController', ['$scope', '$timeout', '$window', 'Authentication', 'FileUploader', + function ($scope, $timeout, $window, Authentication, FileUploader) { + $scope.user = Authentication.user; + $scope.imageURL = $scope.user.profileImageURL; + + // Create file uploader instance + $scope.uploader = new FileUploader({ + url: 'api/users/picture' + }); + + // Set file uploader image filter + $scope.uploader.filters.push({ + name: 'imageFilter', + fn: function (item, options) { + var type = '|' + item.type.slice(item.type.lastIndexOf('/') + 1) + '|'; + return '|jpg|png|jpeg|bmp|gif|'.indexOf(type) !== -1; + } + }); + + // Called after the user selected a new picture file + $scope.uploader.onAfterAddingFile = function (fileItem) { + if ($window.FileReader) { + var fileReader = new FileReader(); + fileReader.readAsDataURL(fileItem._file); + + fileReader.onload = function (fileReaderEvent) { + $timeout(function () { + $scope.imageURL = fileReaderEvent.target.result; + }, 0); + }; + } + }; + + // Called after the user has successfully uploaded a new picture + $scope.uploader.onSuccessItem = function (fileItem, response, status, headers) { + // Show success message + $scope.success = true; + + // Populate user object + $scope.user = Authentication.user = response; + + // Clear upload buttons + $scope.cancelUpload(); + }; + + // Called after the user has failed to uploaded a new picture + $scope.uploader.onErrorItem = function (fileItem, response, status, headers) { + // Clear upload buttons + $scope.cancelUpload(); + + // Show error message + $scope.error = response.message; + }; + + // Change user profile picture + $scope.uploadProfilePicture = function () { + // Clear messages + $scope.success = $scope.error = null; + + // Start upload + $scope.uploader.uploadAll(); + }; + + // Cancel the upload process + $scope.cancelUpload = function () { + $scope.uploader.clearQueue(); + $scope.imageURL = $scope.user.profileImageURL; + }; + } +]); diff --git a/modules/users/client/controllers/settings/edit-profile.client.controller.js b/modules/users/client/controllers/settings/edit-profile.client.controller.js new file mode 100644 index 0000000000..8e1d423819 --- /dev/null +++ b/modules/users/client/controllers/settings/edit-profile.client.controller.js @@ -0,0 +1,24 @@ +'use strict'; + +angular.module('users').controller('EditProfileController', ['$scope', '$http', '$location', 'Users', 'Authentication', + function($scope, $http, $location, Users, Authentication) { + $scope.user = Authentication.user; + + // Update a user profile + $scope.updateUserProfile = function(isValid) { + if (isValid){ + $scope.success = $scope.error = null; + var user = new Users($scope.user); + + user.$update(function(response) { + $scope.success = true; + Authentication.user = response; + }, function(response) { + $scope.error = response.data.message; + }); + } else { + $scope.submitted = true; + } + }; + } +]); diff --git a/modules/users/client/controllers/settings/manage-social-accounts.client.controller.js b/modules/users/client/controllers/settings/manage-social-accounts.client.controller.js new file mode 100644 index 0000000000..74774f06d1 --- /dev/null +++ b/modules/users/client/controllers/settings/manage-social-accounts.client.controller.js @@ -0,0 +1,38 @@ +'use strict'; + +angular.module('users').controller('SocialAccountsController', ['$scope', '$http', '$location', 'Users', 'Authentication', + function($scope, $http, $location, Users, Authentication) { + $scope.user = Authentication.user; + + // Check if there are additional accounts + $scope.hasConnectedAdditionalSocialAccounts = function(provider) { + for (var i in $scope.user.additionalProvidersData) { + return true; + } + + return false; + }; + + // Check if provider is already in use with current user + $scope.isConnectedSocialAccount = function(provider) { + return $scope.user.provider === provider || ($scope.user.additionalProvidersData && $scope.user.additionalProvidersData[provider]); + }; + + // Remove a user social account + $scope.removeUserSocialAccount = function(provider) { + $scope.success = $scope.error = null; + + $http.delete('/api/users/accounts', { + params: { + provider: provider + } + }).success(function(response) { + // If successful show success message and clear form + $scope.success = true; + $scope.user = Authentication.user = response; + }).error(function(response) { + $scope.error = response.message; + }); + }; + } +]); diff --git a/modules/users/client/controllers/settings/settings.client.controller.js b/modules/users/client/controllers/settings/settings.client.controller.js new file mode 100644 index 0000000000..25af58435f --- /dev/null +++ b/modules/users/client/controllers/settings/settings.client.controller.js @@ -0,0 +1,10 @@ +'use strict'; + +angular.module('users').controller('SettingsController', ['$scope', '$http', '$location', 'Users', 'Authentication', + function($scope, $http, $location, Users, Authentication) { + $scope.user = Authentication.user; + + // If user is not signed in then redirect back home + if (!$scope.user) $location.path('/'); + } +]); diff --git a/modules/users/client/css/users.css b/modules/users/client/css/users.css new file mode 100644 index 0000000000..e1727b2153 --- /dev/null +++ b/modules/users/client/css/users.css @@ -0,0 +1,41 @@ +@media (min-width: 992px) { + .nav-users { + position: fixed; + } +} + +.social-account-container { + display: inline-block; + position: relative; +} + +.btn-remove-account { + top: 10px; + right: 10px; + position: absolute; +} + +.btn-file { + position: relative; + overflow: hidden; +} + +.btn-file input[type=file] { + position: absolute; + top: 0; + right: 0; + min-width: 100%; + min-height: 100%; + font-size: 100px; + text-align: right; + filter: alpha(opacity=0); + opacity: 0; + background: white; + cursor: inherit; + display: block; +} + +.user-profile-picture { + min-height: 150px; + max-height: 150px; +} diff --git a/public/modules/users/img/buttons/facebook.png b/modules/users/client/img/buttons/facebook.png similarity index 100% rename from public/modules/users/img/buttons/facebook.png rename to modules/users/client/img/buttons/facebook.png diff --git a/public/modules/users/img/buttons/github.png b/modules/users/client/img/buttons/github.png similarity index 100% rename from public/modules/users/img/buttons/github.png rename to modules/users/client/img/buttons/github.png diff --git a/public/modules/users/img/buttons/google.png b/modules/users/client/img/buttons/google.png similarity index 100% rename from public/modules/users/img/buttons/google.png rename to modules/users/client/img/buttons/google.png diff --git a/public/modules/users/img/buttons/linkedin.png b/modules/users/client/img/buttons/linkedin.png similarity index 100% rename from public/modules/users/img/buttons/linkedin.png rename to modules/users/client/img/buttons/linkedin.png diff --git a/public/modules/users/img/buttons/twitter.png b/modules/users/client/img/buttons/twitter.png similarity index 100% rename from public/modules/users/img/buttons/twitter.png rename to modules/users/client/img/buttons/twitter.png diff --git a/modules/users/client/img/profile/default.png b/modules/users/client/img/profile/default.png new file mode 100644 index 0000000000000000000000000000000000000000..edd013a68b8e1023bb0213247f05cc6caf99d6e9 GIT binary patch literal 65838 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 Tef9uzhrj*8q4&P|qo4gBW5LW< literal 0 HcmV?d00001 diff --git a/modules/users/client/img/profile/uploads/117bf261c0152c79a428db522715cdd7.png b/modules/users/client/img/profile/uploads/117bf261c0152c79a428db522715cdd7.png new file mode 100644 index 0000000000000000000000000000000000000000..65c6f11e89128853b86ae1cdad4f653601c75779 GIT binary patch literal 3835 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?O3?zSk_}l@c*aCb)Tu)n=|NsAADcb2PP(;Pk z#WBR=cyfXSYqRK|_U1sw9Sk$%L?ysb!8bvK$Bu!)%{q3!l-1G2K=llsu6{1-oD!N8 pM!{$ZjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb8|b1OPT~@1Otx literal 0 HcmV?d00001 diff --git a/modules/users/client/img/profile/uploads/5323300de0498510f42b6bbb57800b77.png b/modules/users/client/img/profile/uploads/5323300de0498510f42b6bbb57800b77.png new file mode 100644 index 0000000000000000000000000000000000000000..65c6f11e89128853b86ae1cdad4f653601c75779 GIT binary patch literal 3835 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?O3?zSk_}l@c*aCb)Tu)n=|NsAADcb2PP(;Pk z#WBR=cyfXSYqRK|_U1sw9Sk$%L?ysb!8bvK$Bu!)%{q3!l-1G2K=llsu6{1-oD!N8 pM!{$ZjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb8|b1OPT~@1Otx literal 0 HcmV?d00001 diff --git a/modules/users/client/img/profile/uploads/97ddbe2719b2f5da90c24b8194cca2c0.png b/modules/users/client/img/profile/uploads/97ddbe2719b2f5da90c24b8194cca2c0.png new file mode 100644 index 0000000000000000000000000000000000000000..65c6f11e89128853b86ae1cdad4f653601c75779 GIT binary patch literal 3835 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?O3?zSk_}l@c*aCb)Tu)n=|NsAADcb2PP(;Pk z#WBR=cyfXSYqRK|_U1sw9Sk$%L?ysb!8bvK$Bu!)%{q3!l-1G2K=llsu6{1-oD!N8 pM!{$ZjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb8|b1OPT~@1Otx literal 0 HcmV?d00001 diff --git a/modules/users/client/img/profile/uploads/edb5fdc047848ce1089c0e78693f7652.png b/modules/users/client/img/profile/uploads/edb5fdc047848ce1089c0e78693f7652.png new file mode 100644 index 0000000000000000000000000000000000000000..2ed647ce9d074b185efb50605a50cac0c5d78b6d GIT binary patch literal 23176 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%0D1RU@} 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 +

Sign in using your social accounts

+ +
+ diff --git a/modules/users/client/views/authentication/signin.client.view.html b/modules/users/client/views/authentication/signin.client.view.html new file mode 100644 index 0000000000..5345a5dc6c --- /dev/null +++ b/modules/users/client/views/authentication/signin.client.view.html @@ -0,0 +1,30 @@ +
+

Or with your account

+
+ +
+
diff --git a/modules/users/client/views/authentication/signup.client.view.html b/modules/users/client/views/authentication/signup.client.view.html new file mode 100644 index 0000000000..bb711b3e05 --- /dev/null +++ b/modules/users/client/views/authentication/signup.client.view.html @@ -0,0 +1,42 @@ +
+

Or sign up using your email

+
+ +
+
diff --git a/modules/users/client/views/password/forgot-password.client.view.html b/modules/users/client/views/password/forgot-password.client.view.html new file mode 100644 index 0000000000..02feebfc2d --- /dev/null +++ b/modules/users/client/views/password/forgot-password.client.view.html @@ -0,0 +1,22 @@ +
+

Restore your password

+

Enter your account username.

+
+
+
+
+ +
+
+ +
+
+ {{error}} +
+
+ {{success}} +
+
+
+
+
diff --git a/modules/users/client/views/password/reset-password-invalid.client.view.html b/modules/users/client/views/password/reset-password-invalid.client.view.html new file mode 100644 index 0000000000..a9b8512b15 --- /dev/null +++ b/modules/users/client/views/password/reset-password-invalid.client.view.html @@ -0,0 +1,4 @@ +
+

Password reset is invalid

+ Ask for a new password reset +
diff --git a/public/modules/users/views/password/reset-password-success.client.view.html b/modules/users/client/views/password/reset-password-success.client.view.html similarity index 53% rename from public/modules/users/views/password/reset-password-success.client.view.html rename to modules/users/client/views/password/reset-password-success.client.view.html index 4de46c4b22..a15df19248 100644 --- a/public/modules/users/views/password/reset-password-success.client.view.html +++ b/modules/users/client/views/password/reset-password-success.client.view.html @@ -1,4 +1,4 @@

Password successfully reset

- Continue to home page -
\ No newline at end of file + Continue to home page + diff --git a/modules/users/client/views/password/reset-password.client.view.html b/modules/users/client/views/password/reset-password.client.view.html new file mode 100644 index 0000000000..69d1f346d2 --- /dev/null +++ b/modules/users/client/views/password/reset-password.client.view.html @@ -0,0 +1,26 @@ +
+

Reset your password

+
+ +
+
diff --git a/public/modules/users/views/settings/change-password.client.view.html b/modules/users/client/views/settings/change-password.client.view.html similarity index 86% rename from public/modules/users/views/settings/change-password.client.view.html rename to modules/users/client/views/settings/change-password.client.view.html index 9811011a53..7f965801bb 100644 --- a/public/modules/users/views/settings/change-password.client.view.html +++ b/modules/users/client/views/settings/change-password.client.view.html @@ -1,6 +1,5 @@ -
-

Change your password

-
+
+
-
\ No newline at end of file +
diff --git a/modules/users/client/views/settings/change-profile-picture.client.view.html b/modules/users/client/views/settings/change-profile-picture.client.view.html new file mode 100644 index 0000000000..e0e4f92572 --- /dev/null +++ b/modules/users/client/views/settings/change-profile-picture.client.view.html @@ -0,0 +1,26 @@ +
+
+ +
+
diff --git a/public/modules/users/views/settings/edit-profile.client.view.html b/modules/users/client/views/settings/edit-profile.client.view.html similarity index 87% rename from public/modules/users/views/settings/edit-profile.client.view.html rename to modules/users/client/views/settings/edit-profile.client.view.html index a4be680f41..ae8cd30327 100644 --- a/public/modules/users/views/settings/edit-profile.client.view.html +++ b/modules/users/client/views/settings/edit-profile.client.view.html @@ -1,6 +1,5 @@ -
-

Edit your profile

-
+
+
-
\ No newline at end of file +
diff --git a/modules/users/client/views/settings/manage-social-accounts.client.view.html b/modules/users/client/views/settings/manage-social-accounts.client.view.html new file mode 100644 index 0000000000..b40b203e6c --- /dev/null +++ b/modules/users/client/views/settings/manage-social-accounts.client.view.html @@ -0,0 +1,44 @@ +
+

Connected social accounts:

+
+ +
+

Unconnected social accounts:

+
+ + + + + +
+
diff --git a/modules/users/client/views/settings/settings.client.view.html b/modules/users/client/views/settings/settings.client.view.html new file mode 100644 index 0000000000..9a05049fcd --- /dev/null +++ b/modules/users/client/views/settings/settings.client.view.html @@ -0,0 +1,26 @@ +
+ + +
diff --git a/config/strategies/facebook.js b/modules/users/server/config/strategies/facebook.js similarity index 79% rename from config/strategies/facebook.js rename to modules/users/server/config/strategies/facebook.js index 11bd3686cc..4329c4d03b 100644 --- a/config/strategies/facebook.js +++ b/modules/users/server/config/strategies/facebook.js @@ -6,15 +6,15 @@ var passport = require('passport'), url = require('url'), FacebookStrategy = require('passport-facebook').Strategy, - config = require('../config'), - users = require('../../app/controllers/users.server.controller'); + users = require('../../controllers/users.server.controller'); -module.exports = function() { +module.exports = function(config) { // Use facebook strategy passport.use(new FacebookStrategy({ clientID: config.facebook.clientID, clientSecret: config.facebook.clientSecret, callbackURL: config.facebook.callbackURL, + profileFields: ['id', 'name', 'displayName', 'email', 'username', 'photos'], passReqToCallback: true }, function(req, accessToken, refreshToken, profile, done) { @@ -30,6 +30,7 @@ module.exports = function() { displayName: profile.displayName, email: profile.emails[0].value, username: profile.username, + profileImageURL: (profile.photos && profile.photos.length) ? profile.photos[0].value : undefined, provider: 'facebook', providerIdentifierField: 'id', providerData: providerData @@ -39,4 +40,4 @@ module.exports = function() { users.saveOAuthUserProfile(req, providerUserProfile, done); } )); -}; \ No newline at end of file +}; diff --git a/config/strategies/github.js b/modules/users/server/config/strategies/github.js similarity index 84% rename from config/strategies/github.js rename to modules/users/server/config/strategies/github.js index 799574f58c..228fdd6915 100644 --- a/config/strategies/github.js +++ b/modules/users/server/config/strategies/github.js @@ -6,10 +6,9 @@ var passport = require('passport'), url = require('url'), GithubStrategy = require('passport-github').Strategy, - config = require('../config'), - users = require('../../app/controllers/users.server.controller'); + users = require('../../controllers/users.server.controller'); -module.exports = function() { +module.exports = function(config) { // Use github strategy passport.use(new GithubStrategy({ clientID: config.github.clientID, @@ -28,6 +27,7 @@ module.exports = function() { displayName: profile.displayName, email: profile.emails[0].value, username: profile.username, + profileImageURL: (providerData.avatar_url) ? providerData.avatar_url : undefined, provider: 'github', providerIdentifierField: 'id', providerData: providerData @@ -37,4 +37,4 @@ module.exports = function() { users.saveOAuthUserProfile(req, providerUserProfile, done); } )); -}; \ No newline at end of file +}; diff --git a/config/strategies/google.js b/modules/users/server/config/strategies/google.js similarity index 85% rename from config/strategies/google.js rename to modules/users/server/config/strategies/google.js index 80caaa2cff..1c095763cc 100644 --- a/config/strategies/google.js +++ b/modules/users/server/config/strategies/google.js @@ -6,10 +6,9 @@ var passport = require('passport'), url = require('url'), GoogleStrategy = require('passport-google-oauth').OAuth2Strategy, - config = require('../config'), - users = require('../../app/controllers/users.server.controller'); + users = require('../../controllers/users.server.controller'); -module.exports = function() { +module.exports = function(config) { // Use google strategy passport.use(new GoogleStrategy({ clientID: config.google.clientID, @@ -30,6 +29,7 @@ module.exports = function() { displayName: profile.displayName, email: profile.emails[0].value, username: profile.username, + profileImageURL: (providerData.picture) ? providerData.picture : undefined, provider: 'google', providerIdentifierField: 'id', providerData: providerData @@ -39,4 +39,4 @@ module.exports = function() { users.saveOAuthUserProfile(req, providerUserProfile, done); } )); -}; \ No newline at end of file +}; diff --git a/config/strategies/linkedin.js b/modules/users/server/config/strategies/linkedin.js similarity index 84% rename from config/strategies/linkedin.js rename to modules/users/server/config/strategies/linkedin.js index cfc173ffc2..a3c97d2440 100644 --- a/config/strategies/linkedin.js +++ b/modules/users/server/config/strategies/linkedin.js @@ -6,17 +6,16 @@ var passport = require('passport'), url = require('url'), LinkedInStrategy = require('passport-linkedin').Strategy, - config = require('../config'), - users = require('../../app/controllers/users.server.controller'); + users = require('../../controllers/users.server.controller'); -module.exports = function() { +module.exports = function(config) { // Use linkedin strategy passport.use(new LinkedInStrategy({ consumerKey: config.linkedin.clientID, consumerSecret: config.linkedin.clientSecret, callbackURL: config.linkedin.callbackURL, passReqToCallback: true, - profileFields: ['id', 'first-name', 'last-name', 'email-address'] + profileFields: ['id', 'first-name', 'last-name', 'email-address', 'picture-url'] }, function(req, accessToken, refreshToken, profile, done) { // Set the provider data and include tokens @@ -31,6 +30,7 @@ module.exports = function() { displayName: profile.displayName, email: profile.emails[0].value, username: profile.username, + profileImageURL: (providerData.pictureUrl) ? providerData.pictureUrl : undefined, provider: 'linkedin', providerIdentifierField: 'id', providerData: providerData @@ -40,4 +40,4 @@ module.exports = function() { users.saveOAuthUserProfile(req, providerUserProfile, done); } )); -}; \ No newline at end of file +}; diff --git a/config/strategies/local.js b/modules/users/server/config/strategies/local.js similarity index 86% rename from config/strategies/local.js rename to modules/users/server/config/strategies/local.js index 7101cac2bc..5471473df2 100644 --- a/config/strategies/local.js +++ b/modules/users/server/config/strategies/local.js @@ -22,12 +22,12 @@ module.exports = function() { } if (!user) { return done(null, false, { - message: 'Unknown user or invalid password' + message: 'Unknown user' }); } if (!user.authenticate(password)) { return done(null, false, { - message: 'Unknown user or invalid password' + message: 'Invalid password' }); } diff --git a/config/strategies/twitter.js b/modules/users/server/config/strategies/twitter.js similarity index 82% rename from config/strategies/twitter.js rename to modules/users/server/config/strategies/twitter.js index 3ffb08ba10..f58de392c2 100644 --- a/config/strategies/twitter.js +++ b/modules/users/server/config/strategies/twitter.js @@ -6,10 +6,9 @@ var passport = require('passport'), url = require('url'), TwitterStrategy = require('passport-twitter').Strategy, - config = require('../config'), - users = require('../../app/controllers/users.server.controller'); + users = require('../../controllers/users.server.controller'); -module.exports = function() { +module.exports = function(config) { // Use twitter strategy passport.use(new TwitterStrategy({ consumerKey: config.twitter.clientID, @@ -27,6 +26,7 @@ module.exports = function() { var providerUserProfile = { displayName: profile.displayName, username: profile.username, + profileImageURL: (profile.photos && profile.photos.length) ? profile.photos[0].value : undefined, provider: 'twitter', providerIdentifierField: 'id_str', providerData: providerData @@ -36,4 +36,4 @@ module.exports = function() { users.saveOAuthUserProfile(req, providerUserProfile, done); } )); -}; \ No newline at end of file +}; diff --git a/config/passport.js b/modules/users/server/config/users.server.config.js old mode 100755 new mode 100644 similarity index 57% rename from config/passport.js rename to modules/users/server/config/users.server.config.js index fabbf77c05..86ff6090b1 --- a/config/passport.js +++ b/modules/users/server/config/users.server.config.js @@ -6,12 +6,9 @@ var passport = require('passport'), User = require('mongoose').model('User'), path = require('path'), - config = require('./config'); - -/** - * Module init function. - */ -module.exports = function() { + config = require(path.resolve('./config/config')); + +module.exports = function(app, db) { // Serialize sessions passport.serializeUser(function(user, done) { done(null, user.id); @@ -27,7 +24,11 @@ module.exports = function() { }); // Initialize strategies - config.getGlobbedFiles('./config/strategies/**/*.js').forEach(function(strategy) { - require(path.resolve(strategy))(); + config.utils.getGlobbedPaths(path.join(__dirname, './strategies/**/*.js')).forEach(function(strategy) { + require(path.resolve(strategy))(config); }); -}; \ No newline at end of file + + // Add passport's middleware + app.use(passport.initialize()); + app.use(passport.session()); +}; diff --git a/app/controllers/users.server.controller.js b/modules/users/server/controllers/users.server.controller.js similarity index 100% rename from app/controllers/users.server.controller.js rename to modules/users/server/controllers/users.server.controller.js diff --git a/app/controllers/users/users.authentication.server.controller.js b/modules/users/server/controllers/users/users.authentication.server.controller.js similarity index 96% rename from app/controllers/users/users.authentication.server.controller.js rename to modules/users/server/controllers/users/users.authentication.server.controller.js index c15c8a117c..2998a836a2 100644 --- a/app/controllers/users/users.authentication.server.controller.js +++ b/modules/users/server/controllers/users/users.authentication.server.controller.js @@ -4,7 +4,8 @@ * Module dependencies. */ var _ = require('lodash'), - errorHandler = require('../errors.server.controller'), + path = require('path'), + errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller')), mongoose = require('mongoose'), passport = require('passport'), User = mongoose.model('User'); @@ -134,6 +135,7 @@ exports.saveOAuthUserProfile = function(req, providerUserProfile, done) { username: availableUsername, displayName: providerUserProfile.displayName, email: providerUserProfile.email, + profileImageURL: providerUserProfile.profileImageURL, provider: providerUserProfile.provider, providerData: providerUserProfile.providerData }); @@ -203,4 +205,4 @@ exports.removeOAuthProvider = function(req, res, next) { } }); } -}; \ No newline at end of file +}; diff --git a/app/controllers/users/users.authorization.server.controller.js b/modules/users/server/controllers/users/users.authorization.server.controller.js similarity index 100% rename from app/controllers/users/users.authorization.server.controller.js rename to modules/users/server/controllers/users/users.authorization.server.controller.js diff --git a/app/controllers/users/users.password.server.controller.js b/modules/users/server/controllers/users/users.password.server.controller.js similarity index 91% rename from app/controllers/users/users.password.server.controller.js rename to modules/users/server/controllers/users/users.password.server.controller.js index 1d4ae65e8a..9b4ee83812 100644 --- a/app/controllers/users/users.password.server.controller.js +++ b/modules/users/server/controllers/users/users.password.server.controller.js @@ -4,12 +4,14 @@ * Module dependencies. */ var _ = require('lodash'), - errorHandler = require('../errors.server.controller'), + path = require('path'), + config = require(path.resolve('./config/config')), + errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller')), mongoose = require('mongoose'), passport = require('passport'), User = mongoose.model('User'), - config = require('../../../config/config'), nodemailer = require('nodemailer'), + crypto = require('crypto'), async = require('async'), crypto = require('crypto'); @@ -55,10 +57,10 @@ exports.forgot = function(req, res, next) { } }, function(token, user, done) { - res.render('templates/reset-password-email', { + res.render(path.resolve('modules/users/server/templates/reset-password-email'), { name: user.displayName, appName: config.app.title, - url: 'http://' + req.headers.host + '/auth/reset/' + token + url: 'http://' + req.headers.host + '/api/auth/reset/' + token }, function(err, emailHTML) { done(err, emailHTML, user); }); @@ -111,6 +113,7 @@ exports.validateResetToken = function(req, res) { exports.reset = function(req, res, next) { // Init Variables var passwordDetails = req.body; + var message = null; async.waterfall([ @@ -158,7 +161,7 @@ exports.reset = function(req, res, next) { }); }, function(user, done) { - res.render('templates/reset-password-confirm-email', { + res.render('modules/users/server/templates/reset-password-confirm-email', { name: user.displayName, appName: config.app.title }, function(err, emailHTML) { @@ -174,7 +177,7 @@ exports.reset = function(req, res, next) { subject: 'Your password has been changed', html: emailHTML }; - + smtpTransport.sendMail(mailOptions, function(err) { done(err, 'done'); }); @@ -187,9 +190,10 @@ exports.reset = function(req, res, next) { /** * Change Password */ -exports.changePassword = function(req, res) { +exports.changePassword = function(req, res, next) { // Init Variables var passwordDetails = req.body; + var message = null; if (req.user) { if (passwordDetails.newPassword) { @@ -242,4 +246,4 @@ exports.changePassword = function(req, res) { message: 'User is not signed in' }); } -}; \ No newline at end of file +}; diff --git a/modules/users/server/controllers/users/users.profile.server.controller.js b/modules/users/server/controllers/users/users.profile.server.controller.js new file mode 100644 index 0000000000..ec2ad9c999 --- /dev/null +++ b/modules/users/server/controllers/users/users.profile.server.controller.js @@ -0,0 +1,97 @@ +'use strict'; + +/** + * Module dependencies. + */ +var _ = require('lodash'), + fs = require('fs'), + path = require('path'), + errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller')), + mongoose = require('mongoose'), + passport = require('passport'), + User = mongoose.model('User'); + +/** + * Update user details + */ +exports.update = function (req, res) { + // Init Variables + var user = req.user; + + // For security measurement we remove the roles from the req.body object + delete req.body.roles; + + if (user) { + // Merge existing user + user = _.extend(user, req.body); + user.updated = Date.now(); + user.displayName = user.firstName + ' ' + user.lastName; + + user.save(function (err) { + if (err) { + return res.status(400).send({ + message: errorHandler.getErrorMessage(err) + }); + } else { + req.login(user, function (err) { + if (err) { + res.status(400).send(err); + } else { + res.json(user); + } + }); + } + }); + } else { + res.status(400).send({ + message: 'User is not signed in' + }); + } +}; + +/** + * Update profile picture + */ +exports.changeProfilePicture = function (req, res) { + var user = req.user; + var message = null; + + if (user) { + fs.writeFile('./modules/users/client/img/profile/uploads/' + req.files.file.name, req.files.file.buffer, function (uploadError) { + if (uploadError) { + return res.status(400).send({ + message: 'Error occurred while uploading profile picture' + }); + } else { + user.profileImageURL = 'modules/users/img/profile/uploads/' + req.files.file.name; + + user.save(function (saveError) { + if (saveError) { + return res.status(400).send({ + message: errorHandler.getErrorMessage(saveError) + }); + } else { + req.login(user, function (err) { + if (err) { + res.status(400).send(err); + } else { + res.json(user); + } + }); + } + }); + } + }); + } else { + res.status(400).send({ + message: 'User is not signed in' + }); + } +}; + +/** + * Send User + */ +exports.me = function (req, res) { + res.json(req.user || null); +}; diff --git a/app/models/user.server.model.js b/modules/users/server/models/user.server.model.js similarity index 94% rename from app/models/user.server.model.js rename to modules/users/server/models/user.server.model.js index 9370da01c6..d92d29aff9 100755 --- a/app/models/user.server.model.js +++ b/modules/users/server/models/user.server.model.js @@ -62,6 +62,10 @@ var UserSchema = new Schema({ salt: { type: String }, + profileImageURL: { + type: String, + default: 'modules/users/img/profile/default.png' + }, provider: { type: String, required: 'Provider is required' @@ -86,9 +90,9 @@ var UserSchema = new Schema({ resetPasswordToken: { type: String }, - resetPasswordExpires: { - type: Date - } + resetPasswordExpires: { + type: Date + } }); /** @@ -143,4 +147,4 @@ UserSchema.statics.findUniqueUsername = function(username, suffix, callback) { }); }; -mongoose.model('User', UserSchema); \ No newline at end of file +mongoose.model('User', UserSchema); diff --git a/modules/users/server/routes/auth.server.routes.js b/modules/users/server/routes/auth.server.routes.js new file mode 100644 index 0000000000..6390beed5f --- /dev/null +++ b/modules/users/server/routes/auth.server.routes.js @@ -0,0 +1,53 @@ +'use strict'; + +/** + * Module dependencies. + */ +var passport = require('passport'); + +module.exports = function(app) { + // User Routes + var users = require('../controllers/users.server.controller'); + + // Setting up the users password api + app.route('/api/auth/forgot').post(users.forgot); + app.route('/api/auth/reset/:token').get(users.validateResetToken); + app.route('/api/auth/reset/:token').post(users.reset); + + // Setting up the users authentication api + app.route('/api/auth/signup').post(users.signup); + app.route('/api/auth/signin').post(users.signin); + app.route('/api/auth/signout').get(users.signout); + + // Setting the facebook oauth routes + app.route('/api/auth/facebook').get(passport.authenticate('facebook', { + scope: ['email'] + })); + app.route('/api/auth/facebook/callback').get(users.oauthCallback('facebook')); + + // Setting the twitter oauth routes + app.route('/api/auth/twitter').get(passport.authenticate('twitter')); + app.route('/api/auth/twitter/callback').get(users.oauthCallback('twitter')); + + // Setting the google oauth routes + app.route('/api/auth/google').get(passport.authenticate('google', { + scope: [ + 'https://www.googleapis.com/auth/userinfo.profile', + 'https://www.googleapis.com/auth/userinfo.email' + ] + })); + app.route('/api/auth/google/callback').get(users.oauthCallback('google')); + + // Setting the linkedin oauth routes + app.route('/api/auth/linkedin').get(passport.authenticate('linkedin', { + scope: [ + 'r_basicprofile', + 'r_emailaddress' + ] + })); + app.route('/api/auth/linkedin/callback').get(users.oauthCallback('linkedin')); + + // Setting the github oauth routes + app.route('/api/auth/github').get(passport.authenticate('github')); + app.route('/api/auth/github/callback').get(users.oauthCallback('github')); +}; diff --git a/modules/users/server/routes/users.server.routes.js b/modules/users/server/routes/users.server.routes.js new file mode 100644 index 0000000000..80e528324b --- /dev/null +++ b/modules/users/server/routes/users.server.routes.js @@ -0,0 +1,21 @@ +'use strict'; + +/** + * Module dependencies. + */ +var passport = require('passport'); + +module.exports = function(app) { + // User Routes + var users = require('../controllers/users.server.controller'); + + // Setting up the users profile api + app.route('/api/users/me').get(users.me); + app.route('/api/users').put(users.update); + app.route('/api/users/accounts').delete(users.removeOAuthProvider); + app.route('/api/users/password').post(users.changePassword); + app.route('/api/users/picture').post(users.changeProfilePicture); + + // Finish by binding the user middleware + app.param('userId', users.userByID); +}; diff --git a/app/views/templates/reset-password-confirm-email.server.view.html b/modules/users/server/templates/reset-password-confirm-email.server.view.html similarity index 96% rename from app/views/templates/reset-password-confirm-email.server.view.html rename to modules/users/server/templates/reset-password-confirm-email.server.view.html index 626ddc3fa4..eec61a672c 100644 --- a/app/views/templates/reset-password-confirm-email.server.view.html +++ b/modules/users/server/templates/reset-password-confirm-email.server.view.html @@ -1,7 +1,9 @@ + +

Dear {{name}},

@@ -10,4 +12,5 @@

The {{appName}} Support Team

+ \ No newline at end of file diff --git a/app/views/templates/reset-password-email.server.view.html b/modules/users/server/templates/reset-password-email.server.view.html similarity index 97% rename from app/views/templates/reset-password-email.server.view.html rename to modules/users/server/templates/reset-password-email.server.view.html index 262edf0020..eb73cb83e3 100644 --- a/app/views/templates/reset-password-email.server.view.html +++ b/modules/users/server/templates/reset-password-email.server.view.html @@ -1,8 +1,11 @@ + + +

Dear {{name}},


@@ -15,4 +18,5 @@

The {{appName}} Support Team

+ \ No newline at end of file diff --git a/public/modules/users/tests/authentication.client.controller.test.js b/modules/users/tests/client/authentication.client.controller.tests.js similarity index 89% rename from public/modules/users/tests/authentication.client.controller.test.js rename to modules/users/tests/client/authentication.client.controller.tests.js index 4c95d686ae..5f2d662624 100644 --- a/public/modules/users/tests/authentication.client.controller.test.js +++ b/modules/users/tests/client/authentication.client.controller.tests.js @@ -48,7 +48,7 @@ it('$scope.signin() should login with a correct user and password', function() { // Test expected GET request - $httpBackend.when('POST', '/auth/signin').respond(200, 'Fred'); + $httpBackend.when('POST', '/api/auth/signin').respond(200, 'Fred'); scope.signin(); $httpBackend.flush(); @@ -60,7 +60,7 @@ it('$scope.signin() should fail to log in with nothing', function() { // Test expected POST request - $httpBackend.expectPOST('/auth/signin').respond(400, { + $httpBackend.expectPOST('/api/auth/signin').respond(400, { 'message': 'Missing credentials' }); @@ -77,7 +77,7 @@ scope.credentials = 'Bar'; // Test expected POST request - $httpBackend.expectPOST('/auth/signin').respond(400, { + $httpBackend.expectPOST('/api/auth/signin').respond(400, { 'message': 'Unknown user' }); @@ -91,7 +91,7 @@ it('$scope.signup() should register with correct data', function() { // Test expected GET request scope.authentication.user = 'Fred'; - $httpBackend.when('POST', '/auth/signup').respond(200, 'Fred'); + $httpBackend.when('POST', '/api/auth/signup').respond(200, 'Fred'); scope.signup(); $httpBackend.flush(); @@ -104,7 +104,7 @@ it('$scope.signup() should fail to register with duplicate Username', function() { // Test expected POST request - $httpBackend.when('POST', '/auth/signup').respond(400, { + $httpBackend.when('POST', '/api/auth/signup').respond(400, { 'message': 'Username already exists' }); diff --git a/modules/users/tests/e2e/users.e2e.tests.js b/modules/users/tests/e2e/users.e2e.tests.js new file mode 100644 index 0000000000..ef761097bc --- /dev/null +++ b/modules/users/tests/e2e/users.e2e.tests.js @@ -0,0 +1,13 @@ +'use strict'; + +describe('Users E2E Tests:', function() { + describe('Signin Validation', function() { + it('Should report missing credentials', function() { + browser.get('http://localhost:3000/#!/authentication/signin'); + element(by.css('button[type=submit]')).click(); + element(by.binding('error')).getText().then(function(errorText) { + expect(errorText).toBe('Missing credentials'); + }); + }); + }); +}); diff --git a/app/tests/user.server.model.test.js b/modules/users/tests/server/user.server.model.tests.js similarity index 99% rename from app/tests/user.server.model.test.js rename to modules/users/tests/server/user.server.model.tests.js index 7369d41464..63983d1a15 100644 --- a/app/tests/user.server.model.test.js +++ b/modules/users/tests/server/user.server.model.tests.js @@ -72,4 +72,4 @@ describe('User Model Unit Tests:', function() { User.remove().exec(); done(); }); -}); \ No newline at end of file +}); diff --git a/package.json b/package.json index 80638c37b1..b114c2829b 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "meanjs", "description": "Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js.", - "version": "0.3.3", + "version": "0.4.0", "private": false, "author": "https://github.com/meanjs/mean/graphs/contributors", "repository": { @@ -18,16 +18,18 @@ "postinstall": "bower install --config.interactive=false" }, "dependencies": { - "express": "~4.10.1", + "express": "~4.10.0", "express-session": "~1.9.1", + "serve-favicon": "~2.1.6", "body-parser": "~1.9.0", "cookie-parser": "~1.3.2", "compression": "~1.2.0", "method-override": "~2.3.0", "morgan": "~1.4.1", + "multer": "0.1.6", "connect-mongo": "~0.4.1", "connect-flash": "~0.1.1", - "helmet": "~0.5.0", + "helmet": "~0.4.0", "consolidate": "~0.10.0", "swig": "~1.4.1", "mongoose": "~3.8.8", @@ -38,14 +40,16 @@ "passport-linkedin": "~0.1.3", "passport-google-oauth": "~0.1.5", "passport-github": "~0.1.5", + "acl": "~0.4.4", + "socket.io": "~1.1.0", "lodash": "~2.4.1", "forever": "~0.11.0", "bower": "~1.3.8", "grunt-cli": "~0.1.13", + "chalk": "~0.5.1", "glob": "~4.0.5", "async": "~0.9.0", - "nodemailer": "~1.3.0", - "chalk": "~0.5" + "nodemailer": "~1.3.0" }, "devDependencies": { "supertest": "~0.14.0", @@ -62,7 +66,28 @@ "grunt-concurrent": "~1.0.0", "grunt-mocha-test": "~0.12.1", "grunt-karma": "~0.9.0", + "grunt-protractor-runner": "1.1.4", + "grunt-contrib-sass": "~0.8.1", + "grunt-contrib-less": "~0.12.0", "load-grunt-tasks": "~1.0.0", + "gulp": "~3.8.9", + "run-sequence": "~1.0.1", + "gulp-rename": "~1.2.0", + "gulp-concat": "~2.4.1", + "gulp-nodemon": "~1.0.4", + "gulp-watch": "~1.1.0", + "gulp-livereload": "~2.1.1", + "gulp-jshint": "~1.8.6", + "gulp-csslint": "~0.1.5", + "gulp-ng-annotate": "~0.3.3", + "gulp-uglify": "~1.0.1", + "gulp-cssmin": "~0.1.6", + "gulp-mocha": "~1.1.1", + "gulp-karma": "~0.0.4", + "gulp-protractor": "~0.0.11", + "gulp-sass": "~1.2.2", + "gulp-less": "~1.3.6", + "gulp-load-plugins": "~0.7.0", "karma": "~0.12.0", "karma-jasmine": "~0.2.1", "karma-coverage": "~0.2.0", @@ -70,4 +95,4 @@ "karma-firefox-launcher": "~0.1.3", "karma-phantomjs-launcher": "~0.1.2" } -} \ No newline at end of file +} diff --git a/protractor.conf.js b/protractor.conf.js new file mode 100644 index 0000000000..a2308500e5 --- /dev/null +++ b/protractor.conf.js @@ -0,0 +1,6 @@ +'use strict'; + +// Protractor configuration +exports.config = { + specs: ['modules/*/tests/e2e/*.js'] +}; diff --git a/public/dist/application.js b/public/dist/application.js deleted file mode 100644 index 4d0efe1ece..0000000000 --- a/public/dist/application.js +++ /dev/null @@ -1,622 +0,0 @@ -'use strict'; - -// Init the application configuration module for AngularJS application -var ApplicationConfiguration = (function() { - // Init module configuration options - var applicationModuleName = 'mean'; - var applicationModuleVendorDependencies = ['ngResource', 'ngAnimate', 'ui.router', 'ui.bootstrap', 'ui.utils']; - - // Add a new vertical module - var registerModule = function(moduleName, dependencies) { - // Create angular module - angular.module(moduleName, dependencies || []); - - // Add the module to the AngularJS configuration file - angular.module(applicationModuleName).requires.push(moduleName); - }; - - return { - applicationModuleName: applicationModuleName, - applicationModuleVendorDependencies: applicationModuleVendorDependencies, - registerModule: registerModule - }; -})(); -'use strict'; - -//Start by defining the main module and adding the module dependencies -angular.module(ApplicationConfiguration.applicationModuleName, ApplicationConfiguration.applicationModuleVendorDependencies); - -// Setting HTML5 Location Mode -angular.module(ApplicationConfiguration.applicationModuleName).config(['$locationProvider', - function($locationProvider) { - $locationProvider.hashPrefix('!'); - } -]); - -//Then define the init function for starting up the application -angular.element(document).ready(function() { - //Fixing facebook bug with redirect - if (window.location.hash === '#_=_') window.location.hash = '#!'; - - //Then init the app - angular.bootstrap(document, [ApplicationConfiguration.applicationModuleName]); -}); -'use strict'; - -// Use Applicaion configuration module to register a new module -ApplicationConfiguration.registerModule('articles'); -'use strict'; - -// Use Applicaion configuration module to register a new module -ApplicationConfiguration.registerModule('core'); -'use strict'; - -// Use Applicaion configuration module to register a new module -ApplicationConfiguration.registerModule('users'); -'use strict'; - -// Configuring the Articles module -angular.module('articles').run(['Menus', - function(Menus) { - // Set top bar menu items - Menus.addMenuItem('topbar', 'Articles', 'articles', 'dropdown', '/articles(/create)?'); - Menus.addSubMenuItem('topbar', 'articles', 'List Articles', 'articles'); - Menus.addSubMenuItem('topbar', 'articles', 'New Article', 'articles/create'); - } -]); -'use strict'; - -// Setting up route -angular.module('articles').config(['$stateProvider', - function($stateProvider) { - // Articles state routing - $stateProvider. - state('listArticles', { - url: '/articles', - templateUrl: 'modules/articles/views/list-articles.client.view.html' - }). - state('createArticle', { - url: '/articles/create', - templateUrl: 'modules/articles/views/create-article.client.view.html' - }). - state('viewArticle', { - url: '/articles/:articleId', - templateUrl: 'modules/articles/views/view-article.client.view.html' - }). - state('editArticle', { - url: '/articles/:articleId/edit', - templateUrl: 'modules/articles/views/edit-article.client.view.html' - }); - } -]); -'use strict'; - -angular.module('articles').controller('ArticlesController', ['$scope', '$stateParams', '$location', 'Authentication', 'Articles', - function($scope, $stateParams, $location, Authentication, Articles) { - $scope.authentication = Authentication; - - $scope.create = function() { - var article = new Articles({ - title: this.title, - content: this.content - }); - article.$save(function(response) { - $location.path('articles/' + response._id); - - $scope.title = ''; - $scope.content = ''; - }, function(errorResponse) { - $scope.error = errorResponse.data.message; - }); - }; - - $scope.remove = function(article) { - if (article) { - article.$remove(); - - for (var i in $scope.articles) { - if ($scope.articles[i] === article) { - $scope.articles.splice(i, 1); - } - } - } else { - $scope.article.$remove(function() { - $location.path('articles'); - }); - } - }; - - $scope.update = function() { - var article = $scope.article; - - article.$update(function() { - $location.path('articles/' + article._id); - }, function(errorResponse) { - $scope.error = errorResponse.data.message; - }); - }; - - $scope.find = function() { - $scope.articles = Articles.query(); - }; - - $scope.findOne = function() { - $scope.article = Articles.get({ - articleId: $stateParams.articleId - }); - }; - } -]); -'use strict'; - -//Articles service used for communicating with the articles REST endpoints -angular.module('articles').factory('Articles', ['$resource', - function($resource) { - return $resource('articles/:articleId', { - articleId: '@_id' - }, { - update: { - method: 'PUT' - } - }); - } -]); -'use strict'; - -// Setting up route -angular.module('core').config(['$stateProvider', '$urlRouterProvider', - function($stateProvider, $urlRouterProvider) { - // Redirect to home view when route not found - $urlRouterProvider.otherwise('/'); - - // Home state routing - $stateProvider. - state('home', { - url: '/', - templateUrl: 'modules/core/views/home.client.view.html' - }); - } -]); -'use strict'; - -angular.module('core').controller('HeaderController', ['$scope', 'Authentication', 'Menus', - function($scope, Authentication, Menus) { - $scope.authentication = Authentication; - $scope.isCollapsed = false; - $scope.menu = Menus.getMenu('topbar'); - - $scope.toggleCollapsibleMenu = function() { - $scope.isCollapsed = !$scope.isCollapsed; - }; - - // Collapsing the menu after navigation - $scope.$on('$stateChangeSuccess', function() { - $scope.isCollapsed = false; - }); - } -]); -'use strict'; - - -angular.module('core').controller('HomeController', ['$scope', 'Authentication', - function($scope, Authentication) { - // This provides Authentication context. - $scope.authentication = Authentication; - } -]); -'use strict'; - -//Menu service used for managing menus -angular.module('core').service('Menus', [ - - function() { - // Define a set of default roles - this.defaultRoles = ['*']; - - // Define the menus object - this.menus = {}; - - // A private function for rendering decision - var shouldRender = function(user) { - if (user) { - if (!!~this.roles.indexOf('*')) { - return true; - } else { - for (var userRoleIndex in user.roles) { - for (var roleIndex in this.roles) { - if (this.roles[roleIndex] === user.roles[userRoleIndex]) { - return true; - } - } - } - } - } else { - return this.isPublic; - } - - return false; - }; - - // Validate menu existance - this.validateMenuExistance = function(menuId) { - if (menuId && menuId.length) { - if (this.menus[menuId]) { - return true; - } else { - throw new Error('Menu does not exists'); - } - } else { - throw new Error('MenuId was not provided'); - } - - return false; - }; - - // Get the menu object by menu id - this.getMenu = function(menuId) { - // Validate that the menu exists - this.validateMenuExistance(menuId); - - // Return the menu object - return this.menus[menuId]; - }; - - // Add new menu object by menu id - this.addMenu = function(menuId, isPublic, roles) { - // Create the new menu - this.menus[menuId] = { - isPublic: isPublic || false, - roles: roles || this.defaultRoles, - items: [], - shouldRender: shouldRender - }; - - // Return the menu object - return this.menus[menuId]; - }; - - // Remove existing menu object by menu id - this.removeMenu = function(menuId) { - // Validate that the menu exists - this.validateMenuExistance(menuId); - - // Return the menu object - delete this.menus[menuId]; - }; - - // Add menu item object - this.addMenuItem = function(menuId, menuItemTitle, menuItemURL, menuItemType, menuItemUIRoute, isPublic, roles, position) { - // Validate that the menu exists - this.validateMenuExistance(menuId); - - // Push new menu item - this.menus[menuId].items.push({ - title: menuItemTitle, - link: menuItemURL, - menuItemType: menuItemType || 'item', - menuItemClass: menuItemType, - uiRoute: menuItemUIRoute || ('/' + menuItemURL), - isPublic: ((isPublic === null || typeof isPublic === 'undefined') ? this.menus[menuId].isPublic : isPublic), - roles: ((roles === null || typeof roles === 'undefined') ? this.menus[menuId].roles : roles), - position: position || 0, - items: [], - shouldRender: shouldRender - }); - - // Return the menu object - return this.menus[menuId]; - }; - - // Add submenu item object - this.addSubMenuItem = function(menuId, rootMenuItemURL, menuItemTitle, menuItemURL, menuItemUIRoute, isPublic, roles, position) { - // Validate that the menu exists - this.validateMenuExistance(menuId); - - // Search for menu item - for (var itemIndex in this.menus[menuId].items) { - if (this.menus[menuId].items[itemIndex].link === rootMenuItemURL) { - // Push new submenu item - this.menus[menuId].items[itemIndex].items.push({ - title: menuItemTitle, - link: menuItemURL, - uiRoute: menuItemUIRoute || ('/' + menuItemURL), - isPublic: ((isPublic === null || typeof isPublic === 'undefined') ? this.menus[menuId].items[itemIndex].isPublic : isPublic), - roles: ((roles === null || typeof roles === 'undefined') ? this.menus[menuId].items[itemIndex].roles : roles), - position: position || 0, - shouldRender: shouldRender - }); - } - } - - // Return the menu object - return this.menus[menuId]; - }; - - // Remove existing menu object by menu id - this.removeMenuItem = function(menuId, menuItemURL) { - // Validate that the menu exists - this.validateMenuExistance(menuId); - - // Search for menu item to remove - for (var itemIndex in this.menus[menuId].items) { - if (this.menus[menuId].items[itemIndex].link === menuItemURL) { - this.menus[menuId].items.splice(itemIndex, 1); - } - } - - // Return the menu object - return this.menus[menuId]; - }; - - // Remove existing menu object by menu id - this.removeSubMenuItem = function(menuId, submenuItemURL) { - // Validate that the menu exists - this.validateMenuExistance(menuId); - - // Search for menu item to remove - for (var itemIndex in this.menus[menuId].items) { - for (var subitemIndex in this.menus[menuId].items[itemIndex].items) { - if (this.menus[menuId].items[itemIndex].items[subitemIndex].link === submenuItemURL) { - this.menus[menuId].items[itemIndex].items.splice(subitemIndex, 1); - } - } - } - - // Return the menu object - return this.menus[menuId]; - }; - - //Adding the topbar menu - this.addMenu('topbar'); - } -]); -'use strict'; - -// Config HTTP Error Handling -angular.module('users').config(['$httpProvider', - function($httpProvider) { - // Set the httpProvider "not authorized" interceptor - $httpProvider.interceptors.push(['$q', '$location', 'Authentication', - function($q, $location, Authentication) { - return { - responseError: function(rejection) { - switch (rejection.status) { - case 401: - // Deauthenticate the global user - Authentication.user = null; - - // Redirect to signin page - $location.path('signin'); - break; - case 403: - // Add unauthorized behaviour - break; - } - - return $q.reject(rejection); - } - }; - } - ]); - } -]); -'use strict'; - -// Setting up route -angular.module('users').config(['$stateProvider', - function($stateProvider) { - // Users state routing - $stateProvider. - state('profile', { - url: '/settings/profile', - templateUrl: 'modules/users/views/settings/edit-profile.client.view.html' - }). - state('password', { - url: '/settings/password', - templateUrl: 'modules/users/views/settings/change-password.client.view.html' - }). - state('accounts', { - url: '/settings/accounts', - templateUrl: 'modules/users/views/settings/social-accounts.client.view.html' - }). - state('signup', { - url: '/signup', - templateUrl: 'modules/users/views/authentication/signup.client.view.html' - }). - state('signin', { - url: '/signin', - templateUrl: 'modules/users/views/authentication/signin.client.view.html' - }). - state('forgot', { - url: '/password/forgot', - templateUrl: 'modules/users/views/password/forgot-password.client.view.html' - }). - state('reset-invalid', { - url: '/password/reset/invalid', - templateUrl: 'modules/users/views/password/reset-password-invalid.client.view.html' - }). - state('reset-success', { - url: '/password/reset/success', - templateUrl: 'modules/users/views/password/reset-password-success.client.view.html' - }). - state('reset', { - url: '/password/reset/:token', - templateUrl: 'modules/users/views/password/reset-password.client.view.html' - }); - } -]); -'use strict'; - -angular.module('users').controller('AuthenticationController', ['$scope', '$http', '$location', 'Authentication', - function($scope, $http, $location, Authentication) { - $scope.authentication = Authentication; - - // If user is signed in then redirect back home - if ($scope.authentication.user) $location.path('/'); - - $scope.signup = function() { - $http.post('/auth/signup', $scope.credentials).success(function(response) { - // If successful we assign the response to the global user model - $scope.authentication.user = response; - - // And redirect to the index page - $location.path('/'); - }).error(function(response) { - $scope.error = response.message; - }); - }; - - $scope.signin = function() { - $http.post('/auth/signin', $scope.credentials).success(function(response) { - // If successful we assign the response to the global user model - $scope.authentication.user = response; - - // And redirect to the index page - $location.path('/'); - }).error(function(response) { - $scope.error = response.message; - }); - }; - } -]); -'use strict'; - -angular.module('users').controller('PasswordController', ['$scope', '$stateParams', '$http', '$location', 'Authentication', - function($scope, $stateParams, $http, $location, Authentication) { - $scope.authentication = Authentication; - - //If user is signed in then redirect back home - if ($scope.authentication.user) $location.path('/'); - - // Submit forgotten password account id - $scope.askForPasswordReset = function() { - $scope.success = $scope.error = null; - - $http.post('/auth/forgot', $scope.credentials).success(function(response) { - // Show user success message and clear form - $scope.credentials = null; - $scope.success = response.message; - - }).error(function(response) { - // Show user error message and clear form - $scope.credentials = null; - $scope.error = response.message; - }); - }; - - // Change user password - $scope.resetUserPassword = function() { - $scope.success = $scope.error = null; - - $http.post('/auth/reset/' + $stateParams.token, $scope.passwordDetails).success(function(response) { - // If successful show success message and clear form - $scope.passwordDetails = null; - - // Attach user profile - Authentication.user = response; - - // And redirect to the index page - $location.path('/password/reset/success'); - }).error(function(response) { - $scope.error = response.message; - }); - }; - } -]); -'use strict'; - -angular.module('users').controller('SettingsController', ['$scope', '$http', '$location', 'Users', 'Authentication', - function($scope, $http, $location, Users, Authentication) { - $scope.user = Authentication.user; - - // If user is not signed in then redirect back home - if (!$scope.user) $location.path('/'); - - // Check if there are additional accounts - $scope.hasConnectedAdditionalSocialAccounts = function(provider) { - for (var i in $scope.user.additionalProvidersData) { - return true; - } - - return false; - }; - - // Check if provider is already in use with current user - $scope.isConnectedSocialAccount = function(provider) { - return $scope.user.provider === provider || ($scope.user.additionalProvidersData && $scope.user.additionalProvidersData[provider]); - }; - - // Remove a user social account - $scope.removeUserSocialAccount = function(provider) { - $scope.success = $scope.error = null; - - $http.delete('/users/accounts', { - params: { - provider: provider - } - }).success(function(response) { - // If successful show success message and clear form - $scope.success = true; - $scope.user = Authentication.user = response; - }).error(function(response) { - $scope.error = response.message; - }); - }; - - // Update a user profile - $scope.updateUserProfile = function(isValid) { - if (isValid) { - $scope.success = $scope.error = null; - var user = new Users($scope.user); - - user.$update(function(response) { - $scope.success = true; - Authentication.user = response; - }, function(response) { - $scope.error = response.data.message; - }); - } else { - $scope.submitted = true; - } - }; - - // Change user password - $scope.changeUserPassword = function() { - $scope.success = $scope.error = null; - - $http.post('/users/password', $scope.passwordDetails).success(function(response) { - // If successful show success message and clear form - $scope.success = true; - $scope.passwordDetails = null; - }).error(function(response) { - $scope.error = response.message; - }); - }; - } -]); -'use strict'; - -// Authentication service for user variables -angular.module('users').factory('Authentication', [ - function() { - var _this = this; - - _this._data = { - user: window.user - }; - - return _this._data; - } -]); -'use strict'; - -// Users service used for communicating with the users REST endpoint -angular.module('users').factory('Users', ['$resource', - function($resource) { - return $resource('users', {}, { - update: { - method: 'PUT' - } - }); - } -]); \ No newline at end of file diff --git a/public/dist/application.min.css b/public/dist/application.min.css index 323ffcca2e..034f618cc5 100644 --- a/public/dist/application.min.css +++ b/public/dist/application.min.css @@ -1 +1,5 @@ -.content{margin-top:50px}.undecorated-link:hover{text-decoration:none}.ng-cloak,.x-ng-cloak,[data-ng-cloak],[ng-cloak],[ng\:cloak],[x-ng-cloak]{display:none!important}.ng-invalid.ng-dirty{border-color:#FA787E}.ng-valid.ng-dirty{border-color:#78FA89}@media (min-width:992px){.nav-users{position:fixed}}.remove-account-container{display:inline-block;position:relative}.btn-remove-account{top:10px;right:10px;position:absolute} \ No newline at end of file +.chat-message{margin-top:10px;padding-top:10px}.chat-message:not(:first-child){border-top:1px solid #e7e7e7}.chat-message-details{margin-left:10px}.chat-profile-image{height:28px;width:28px;border-radius:50%} + +.content-navigation{border-color:#fff;color:#e8e8e8}.border{padding:8px;margin:8px;border-color:#fff} +.content{margin-top:50px}.undecorated-link:hover{text-decoration:none}.ng-cloak,.x-ng-cloak,[data-ng-cloak],[ng-cloak],[ng\:cloak],[x-ng-cloak]{display:none!important}.ng-invalid.ng-dirty{border-color:#FA787E}.ng-valid.ng-dirty{border-color:#78FA89}.header-profile-image{opacity:.8;height:28px;width:28px;border-radius:50%;margin-right:5px}.open .header-profile-image,a:hover .header-profile-image{opacity:1}.user-header-dropdown-toggle{padding-top:11px!important;padding-bottom:11px!important} +@media (min-width:992px){.nav-users{position:fixed}}.social-account-container{display:inline-block;position:relative}.btn-remove-account{top:10px;right:10px;position:absolute}.btn-file{position:relative;overflow:hidden}.btn-file input[type=file]{position:absolute;top:0;right:0;min-width:100%;min-height:100%;font-size:100px;text-align:right;filter:alpha(opacity=0);opacity:0;background:#fff;cursor:inherit;display:block}.user-profile-picture{min-height:150px;max-height:150px} \ No newline at end of file diff --git a/public/dist/application.min.js b/public/dist/application.min.js index 2b2b9efadb..b841413081 100644 --- a/public/dist/application.min.js +++ b/public/dist/application.min.js @@ -1 +1,25 @@ -"use strict";var ApplicationConfiguration=function(){var applicationModuleName="mean",applicationModuleVendorDependencies=["ngResource","ngAnimate","ui.router","ui.bootstrap","ui.utils"],registerModule=function(moduleName,dependencies){angular.module(moduleName,dependencies||[]),angular.module(applicationModuleName).requires.push(moduleName)};return{applicationModuleName:applicationModuleName,applicationModuleVendorDependencies:applicationModuleVendorDependencies,registerModule:registerModule}}();angular.module(ApplicationConfiguration.applicationModuleName,ApplicationConfiguration.applicationModuleVendorDependencies),angular.module(ApplicationConfiguration.applicationModuleName).config(["$locationProvider",function($locationProvider){$locationProvider.hashPrefix("!")}]),angular.element(document).ready(function(){"#_=_"===window.location.hash&&(window.location.hash="#!"),angular.bootstrap(document,[ApplicationConfiguration.applicationModuleName])}),ApplicationConfiguration.registerModule("articles"),ApplicationConfiguration.registerModule("core"),ApplicationConfiguration.registerModule("users"),angular.module("articles").run(["Menus",function(Menus){Menus.addMenuItem("topbar","Articles","articles","dropdown","/articles(/create)?"),Menus.addSubMenuItem("topbar","articles","List Articles","articles"),Menus.addSubMenuItem("topbar","articles","New Article","articles/create")}]),angular.module("articles").config(["$stateProvider",function($stateProvider){$stateProvider.state("listArticles",{url:"/articles",templateUrl:"modules/articles/views/list-articles.client.view.html"}).state("createArticle",{url:"/articles/create",templateUrl:"modules/articles/views/create-article.client.view.html"}).state("viewArticle",{url:"/articles/:articleId",templateUrl:"modules/articles/views/view-article.client.view.html"}).state("editArticle",{url:"/articles/:articleId/edit",templateUrl:"modules/articles/views/edit-article.client.view.html"})}]),angular.module("articles").controller("ArticlesController",["$scope","$stateParams","$location","Authentication","Articles",function($scope,$stateParams,$location,Authentication,Articles){$scope.authentication=Authentication,$scope.create=function(){var article=new Articles({title:this.title,content:this.content});article.$save(function(response){$location.path("articles/"+response._id),$scope.title="",$scope.content=""},function(errorResponse){$scope.error=errorResponse.data.message})},$scope.remove=function(article){if(article){article.$remove();for(var i in $scope.articles)$scope.articles[i]===article&&$scope.articles.splice(i,1)}else $scope.article.$remove(function(){$location.path("articles")})},$scope.update=function(){var article=$scope.article;article.$update(function(){$location.path("articles/"+article._id)},function(errorResponse){$scope.error=errorResponse.data.message})},$scope.find=function(){$scope.articles=Articles.query()},$scope.findOne=function(){$scope.article=Articles.get({articleId:$stateParams.articleId})}}]),angular.module("articles").factory("Articles",["$resource",function($resource){return $resource("articles/:articleId",{articleId:"@_id"},{update:{method:"PUT"}})}]),angular.module("core").config(["$stateProvider","$urlRouterProvider",function($stateProvider,$urlRouterProvider){$urlRouterProvider.otherwise("/"),$stateProvider.state("home",{url:"/",templateUrl:"modules/core/views/home.client.view.html"})}]),angular.module("core").controller("HeaderController",["$scope","Authentication","Menus",function($scope,Authentication,Menus){$scope.authentication=Authentication,$scope.isCollapsed=!1,$scope.menu=Menus.getMenu("topbar"),$scope.toggleCollapsibleMenu=function(){$scope.isCollapsed=!$scope.isCollapsed},$scope.$on("$stateChangeSuccess",function(){$scope.isCollapsed=!1})}]),angular.module("core").controller("HomeController",["$scope","Authentication",function($scope,Authentication){$scope.authentication=Authentication}]),angular.module("core").service("Menus",[function(){this.defaultRoles=["*"],this.menus={};var shouldRender=function(user){if(!user)return this.isPublic;if(~this.roles.indexOf("*"))return!0;for(var userRoleIndex in user.roles)for(var roleIndex in this.roles)if(this.roles[roleIndex]===user.roles[userRoleIndex])return!0;return!1};this.validateMenuExistance=function(menuId){if(menuId&&menuId.length){if(this.menus[menuId])return!0;throw new Error("Menu does not exists")}throw new Error("MenuId was not provided")},this.getMenu=function(menuId){return this.validateMenuExistance(menuId),this.menus[menuId]},this.addMenu=function(menuId,isPublic,roles){return this.menus[menuId]={isPublic:isPublic||!1,roles:roles||this.defaultRoles,items:[],shouldRender:shouldRender},this.menus[menuId]},this.removeMenu=function(menuId){this.validateMenuExistance(menuId),delete this.menus[menuId]},this.addMenuItem=function(menuId,menuItemTitle,menuItemURL,menuItemType,menuItemUIRoute,isPublic,roles,position){return this.validateMenuExistance(menuId),this.menus[menuId].items.push({title:menuItemTitle,link:menuItemURL,menuItemType:menuItemType||"item",menuItemClass:menuItemType,uiRoute:menuItemUIRoute||"/"+menuItemURL,isPublic:null===isPublic||"undefined"==typeof isPublic?this.menus[menuId].isPublic:isPublic,roles:null===roles||"undefined"==typeof roles?this.menus[menuId].roles:roles,position:position||0,items:[],shouldRender:shouldRender}),this.menus[menuId]},this.addSubMenuItem=function(menuId,rootMenuItemURL,menuItemTitle,menuItemURL,menuItemUIRoute,isPublic,roles,position){this.validateMenuExistance(menuId);for(var itemIndex in this.menus[menuId].items)this.menus[menuId].items[itemIndex].link===rootMenuItemURL&&this.menus[menuId].items[itemIndex].items.push({title:menuItemTitle,link:menuItemURL,uiRoute:menuItemUIRoute||"/"+menuItemURL,isPublic:null===isPublic||"undefined"==typeof isPublic?this.menus[menuId].items[itemIndex].isPublic:isPublic,roles:null===roles||"undefined"==typeof roles?this.menus[menuId].items[itemIndex].roles:roles,position:position||0,shouldRender:shouldRender});return this.menus[menuId]},this.removeMenuItem=function(menuId,menuItemURL){this.validateMenuExistance(menuId);for(var itemIndex in this.menus[menuId].items)this.menus[menuId].items[itemIndex].link===menuItemURL&&this.menus[menuId].items.splice(itemIndex,1);return this.menus[menuId]},this.removeSubMenuItem=function(menuId,submenuItemURL){this.validateMenuExistance(menuId);for(var itemIndex in this.menus[menuId].items)for(var subitemIndex in this.menus[menuId].items[itemIndex].items)this.menus[menuId].items[itemIndex].items[subitemIndex].link===submenuItemURL&&this.menus[menuId].items[itemIndex].items.splice(subitemIndex,1);return this.menus[menuId]},this.addMenu("topbar")}]),angular.module("users").config(["$httpProvider",function($httpProvider){$httpProvider.interceptors.push(["$q","$location","Authentication",function($q,$location,Authentication){return{responseError:function(rejection){switch(rejection.status){case 401:Authentication.user=null,$location.path("signin");break;case 403:}return $q.reject(rejection)}}}])}]),angular.module("users").config(["$stateProvider",function($stateProvider){$stateProvider.state("profile",{url:"/settings/profile",templateUrl:"modules/users/views/settings/edit-profile.client.view.html"}).state("password",{url:"/settings/password",templateUrl:"modules/users/views/settings/change-password.client.view.html"}).state("accounts",{url:"/settings/accounts",templateUrl:"modules/users/views/settings/social-accounts.client.view.html"}).state("signup",{url:"/signup",templateUrl:"modules/users/views/authentication/signup.client.view.html"}).state("signin",{url:"/signin",templateUrl:"modules/users/views/authentication/signin.client.view.html"}).state("forgot",{url:"/password/forgot",templateUrl:"modules/users/views/password/forgot-password.client.view.html"}).state("reset-invalid",{url:"/password/reset/invalid",templateUrl:"modules/users/views/password/reset-password-invalid.client.view.html"}).state("reset-success",{url:"/password/reset/success",templateUrl:"modules/users/views/password/reset-password-success.client.view.html"}).state("reset",{url:"/password/reset/:token",templateUrl:"modules/users/views/password/reset-password.client.view.html"})}]),angular.module("users").controller("AuthenticationController",["$scope","$http","$location","Authentication",function($scope,$http,$location,Authentication){$scope.authentication=Authentication,$scope.authentication.user&&$location.path("/"),$scope.signup=function(){$http.post("/auth/signup",$scope.credentials).success(function(response){$scope.authentication.user=response,$location.path("/")}).error(function(response){$scope.error=response.message})},$scope.signin=function(){$http.post("/auth/signin",$scope.credentials).success(function(response){$scope.authentication.user=response,$location.path("/")}).error(function(response){$scope.error=response.message})}}]),angular.module("users").controller("PasswordController",["$scope","$stateParams","$http","$location","Authentication",function($scope,$stateParams,$http,$location,Authentication){$scope.authentication=Authentication,$scope.authentication.user&&$location.path("/"),$scope.askForPasswordReset=function(){$scope.success=$scope.error=null,$http.post("/auth/forgot",$scope.credentials).success(function(response){$scope.credentials=null,$scope.success=response.message}).error(function(response){$scope.credentials=null,$scope.error=response.message})},$scope.resetUserPassword=function(){$scope.success=$scope.error=null,$http.post("/auth/reset/"+$stateParams.token,$scope.passwordDetails).success(function(response){$scope.passwordDetails=null,Authentication.user=response,$location.path("/password/reset/success")}).error(function(response){$scope.error=response.message})}}]),angular.module("users").controller("SettingsController",["$scope","$http","$location","Users","Authentication",function($scope,$http,$location,Users,Authentication){$scope.user=Authentication.user,$scope.user||$location.path("/"),$scope.hasConnectedAdditionalSocialAccounts=function(){for(var i in $scope.user.additionalProvidersData)return!0;return!1},$scope.isConnectedSocialAccount=function(provider){return $scope.user.provider===provider||$scope.user.additionalProvidersData&&$scope.user.additionalProvidersData[provider]},$scope.removeUserSocialAccount=function(provider){$scope.success=$scope.error=null,$http.delete("/users/accounts",{params:{provider:provider}}).success(function(response){$scope.success=!0,$scope.user=Authentication.user=response}).error(function(response){$scope.error=response.message})},$scope.updateUserProfile=function(isValid){if(isValid){$scope.success=$scope.error=null;var user=new Users($scope.user);user.$update(function(response){$scope.success=!0,Authentication.user=response},function(response){$scope.error=response.data.message})}else $scope.submitted=!0},$scope.changeUserPassword=function(){$scope.success=$scope.error=null,$http.post("/users/password",$scope.passwordDetails).success(function(){$scope.success=!0,$scope.passwordDetails=null}).error(function(response){$scope.error=response.message})}}]),angular.module("users").factory("Authentication",[function(){var _this=this;return _this._data={user:window.user},_this._data}]),angular.module("users").factory("Users",["$resource",function($resource){return $resource("users",{},{update:{method:"PUT"}})}]); \ No newline at end of file +"use strict";var ApplicationConfiguration=function(){var applicationModuleName="mean",applicationModuleVendorDependencies=["ngResource","ngAnimate","ui.router","ui.bootstrap","ui.utils","angularFileUpload"],registerModule=function(moduleName,dependencies){angular.module(moduleName,dependencies||[]),angular.module(applicationModuleName).requires.push(moduleName)};return{applicationModuleName:applicationModuleName,applicationModuleVendorDependencies:applicationModuleVendorDependencies,registerModule:registerModule}}(); +"use strict";angular.module(ApplicationConfiguration.applicationModuleName,ApplicationConfiguration.applicationModuleVendorDependencies),angular.module(ApplicationConfiguration.applicationModuleName).config(["$locationProvider",function($locationProvider){$locationProvider.hashPrefix("!")}]),angular.element(document).ready(function(){"#_=_"===window.location.hash&&(window.location.hash="#!"),angular.bootstrap(document,[ApplicationConfiguration.applicationModuleName])}); +"use strict";ApplicationConfiguration.registerModule("chat"); +"use strict";ApplicationConfiguration.registerModule("core"); +"use strict";ApplicationConfiguration.registerModule("users"); +"use strict";angular.module("chat").run(["Menus",function(Menus){Menus.addMenuItem("topbar",{title:"Chat",state:"chat"})}]); +"use strict";angular.module("chat").config(["$stateProvider",function($stateProvider){$stateProvider.state("chat",{url:"/chat",templateUrl:"modules/chat/views/chat.client.view.html"})}]); +"use strict";angular.module("chat").controller("ChatController",["$scope","Socket",function($scope,Socket){$scope.messages=[],Socket.on("chatMessage",function(message){$scope.messages.unshift(message)}),$scope.sendMessage=function(){var message={text:this.messageText};Socket.emit("chatMessage",message),this.messageText=""},$scope.$on("$destroy",function(){Socket.removeListener("chatMessage")})}]); +"use strict";angular.module("core").config(["$stateProvider","$urlRouterProvider",function($stateProvider,$urlRouterProvider){$urlRouterProvider.otherwise("/"),$stateProvider.state("home",{url:"/",templateUrl:"modules/core/views/home.client.view.html"})}]); +"use strict";angular.module("core").controller("HeaderController",["$scope","Authentication","Menus",function($scope,Authentication,Menus){$scope.authentication=Authentication,$scope.isCollapsed=!1,$scope.menu=Menus.getMenu("topbar"),$scope.toggleCollapsibleMenu=function(){$scope.isCollapsed=!$scope.isCollapsed},$scope.$on("$stateChangeSuccess",function(){$scope.isCollapsed=!1})}]); +"use strict";angular.module("core").controller("HomeController",["$scope","Authentication",function($scope,Authentication){$scope.authentication=Authentication}]); +"use strict";angular.module("core").service("Menus",[function(){this.defaultRoles=["*"],this.menus={};var shouldRender=function(user){if(!user)return this.isPublic;if(~this.roles.indexOf("*"))return!0;for(var userRoleIndex in user.roles)for(var roleIndex in this.roles)if(this.roles[roleIndex]===user.roles[userRoleIndex])return!0;return!1};this.validateMenuExistance=function(menuId){if(menuId&&menuId.length){if(this.menus[menuId])return!0;throw new Error("Menu does not exists")}throw new Error("MenuId was not provided")},this.getMenu=function(menuId){return this.validateMenuExistance(menuId),this.menus[menuId]},this.addMenu=function(menuId,options){return options=options||{},this.menus[menuId]={isPublic:null===options.isPublic||"undefined"==typeof options.isPublic?!0:options.isPublic,roles:options.roles||this.defaultRoles,items:options.items||[],shouldRender:shouldRender},this.menus[menuId]},this.removeMenu=function(menuId){this.validateMenuExistance(menuId),delete this.menus[menuId]},this.addMenuItem=function(menuId,options){if(options=options||{},this.validateMenuExistance(menuId),this.menus[menuId].items.push({title:options.title||"",state:options.state||"",type:options.type||"item","class":options.class,isPublic:null===options.isPublic||"undefined"==typeof options.isPublic?this.menus[menuId].isPublic:options.isPublic,roles:null===options.roles||"undefined"==typeof options.roles?this.menus[menuId].roles:options.roles,position:options.position||0,items:[],shouldRender:shouldRender}),options.items)for(var i in options.items)this.addSubMenuItem(menuId,options.link,options.items[i]);return this.menus[menuId]},this.addSubMenuItem=function(menuId,rootMenuItemURL,options){options=options||{},this.validateMenuExistance(menuId);for(var itemIndex in this.menus[menuId].items)this.menus[menuId].items[itemIndex].link===rootMenuItemURL&&this.menus[menuId].items[itemIndex].items.push({title:options.title||"",state:options.state||"",isPublic:null===options.isPublic||"undefined"==typeof options.isPublic?this.menus[menuId].items[itemIndex].isPublic:options.isPublic,roles:null===options.roles||"undefined"==typeof options.roles?this.menus[menuId].items[itemIndex].roles:options.roles,position:options.position||0,shouldRender:shouldRender});return this.menus[menuId]},this.removeMenuItem=function(menuId,menuItemURL){this.validateMenuExistance(menuId);for(var itemIndex in this.menus[menuId].items)this.menus[menuId].items[itemIndex].link===menuItemURL&&this.menus[menuId].items.splice(itemIndex,1);return this.menus[menuId]},this.removeSubMenuItem=function(menuId,submenuItemURL){this.validateMenuExistance(menuId);for(var itemIndex in this.menus[menuId].items)for(var subitemIndex in this.menus[menuId].items[itemIndex].items)this.menus[menuId].items[itemIndex].items[subitemIndex].link===submenuItemURL&&this.menus[menuId].items[itemIndex].items.splice(subitemIndex,1);return this.menus[menuId]},this.addMenu("topbar",{isPublic:!1})}]); +"use strict";angular.module("core").service("Socket",["Authentication","$state","$timeout",function(Authentication,$state,$timeout){Authentication.user?this.socket=io():$state.go("home"),this.on=function(eventName,callback){this.socket&&this.socket.on(eventName,function(data){$timeout(function(){callback(data)})})},this.emit=function(eventName,data){this.socket&&this.socket.emit(eventName,data)},this.removeListener=function(eventName){this.socket&&this.socket.removeListener(eventName)}}]); +"use strict";angular.module("users").config(["$httpProvider",function($httpProvider){$httpProvider.interceptors.push(["$q","$location","Authentication",function($q,$location,Authentication){return{responseError:function(rejection){switch(rejection.status){case 401:Authentication.user=null,$location.path("signin");break;case 403:}return $q.reject(rejection)}}}])}]); +"use strict";angular.module("users").config(["$stateProvider",function($stateProvider){$stateProvider.state("settings",{"abstract":!0,url:"/settings",templateUrl:"modules/users/views/settings/settings.client.view.html"}).state("settings.profile",{url:"/profile",templateUrl:"modules/users/views/settings/edit-profile.client.view.html"}).state("settings.password",{url:"/password",templateUrl:"modules/users/views/settings/change-password.client.view.html"}).state("settings.accounts",{url:"/accounts",templateUrl:"modules/users/views/settings/manage-social-accounts.client.view.html"}).state("settings.picture",{url:"/picture",templateUrl:"modules/users/views/settings/change-profile-picture.client.view.html"}).state("authentication",{"abstract":!0,url:"/authentication",templateUrl:"modules/users/views/authentication/authentication.client.view.html"}).state("authentication.signup",{url:"/signup",templateUrl:"modules/users/views/authentication/signup.client.view.html"}).state("authentication.signin",{url:"/signin",templateUrl:"modules/users/views/authentication/signin.client.view.html"}).state("password",{"abstract":!0,url:"/password",template:""}).state("password.forgot",{url:"/forgot",templateUrl:"modules/users/views/password/forgot-password.client.view.html"}).state("password.reset",{"abstract":!0,url:"/reset",template:""}).state("password.reset.invalid",{url:"/invalid",templateUrl:"modules/users/views/password/reset-password-invalid.client.view.html"}).state("password.reset.success",{url:"/success",templateUrl:"modules/users/views/password/reset-password-success.client.view.html"}).state("password.reset.form",{url:"/:token",templateUrl:"modules/users/views/password/reset-password.client.view.html"})}]); +"use strict";angular.module("users").controller("AuthenticationController",["$scope","$http","$location","Authentication",function($scope,$http,$location,Authentication){$scope.authentication=Authentication,$scope.authentication.user&&$location.path("/"),$scope.signup=function(){$http.post("/api/auth/signup",$scope.credentials).success(function(response){$scope.authentication.user=response,$location.path("/")}).error(function(response){$scope.error=response.message})},$scope.signin=function(){$http.post("/api/auth/signin",$scope.credentials).success(function(response){$scope.authentication.user=response,$location.path("/")}).error(function(response){$scope.error=response.message})}}]); +"use strict";angular.module("users").controller("PasswordController",["$scope","$stateParams","$http","$location","Authentication",function($scope,$stateParams,$http,$location,Authentication){$scope.authentication=Authentication,$scope.authentication.user&&$location.path("/"),$scope.askForPasswordReset=function(){$scope.success=$scope.error=null,$http.post("/api/auth/forgot",$scope.credentials).success(function(response){$scope.credentials=null,$scope.success=response.message}).error(function(response){$scope.credentials=null,$scope.error=response.message})},$scope.resetUserPassword=function(){$scope.success=$scope.error=null,$http.post("/api/auth/reset/"+$stateParams.token,$scope.passwordDetails).success(function(response){$scope.passwordDetails=null,Authentication.user=response,$location.path("/password/reset/success")}).error(function(response){$scope.error=response.message})}}]); +"use strict";angular.module("users").controller("SettingsController",["$scope","$http","$location","Users","Authentication",function($scope,$http,$location,Users,Authentication){$scope.user=Authentication.user,$scope.user||$location.path("/"),$scope.hasConnectedAdditionalSocialAccounts=function(){for(var i in $scope.user.additionalProvidersData)return!0;return!1},$scope.isConnectedSocialAccount=function(provider){return $scope.user.provider===provider||$scope.user.additionalProvidersData&&$scope.user.additionalProvidersData[provider]},$scope.removeUserSocialAccount=function(provider){$scope.success=$scope.error=null,$http.delete("/api/users/accounts",{params:{provider:provider}}).success(function(response){$scope.success=!0,$scope.user=Authentication.user=response}).error(function(response){$scope.error=response.message})},$scope.updateUserProfile=function(isValid){if(isValid){$scope.success=$scope.error=null;var user=new Users($scope.user);user.$update(function(response){$scope.success=!0,Authentication.user=response},function(response){$scope.error=response.data.message})}else $scope.submitted=!0},$scope.changeUserPassword=function(){$scope.success=$scope.error=null,$http.post("/api/users/password",$scope.passwordDetails).success(function(){$scope.success=!0,$scope.passwordDetails=null}).error(function(response){$scope.error=response.message})}}]); +"use strict";angular.module("users").factory("Authentication",[function(){var _this=this;return _this._data={user:window.user},_this._data}]); +"use strict";angular.module("users").factory("Users",["$resource",function($resource){return $resource("api/users",{},{update:{method:"PUT"}})}]); +"use strict";angular.module("users").controller("ChangePasswordController",["$scope","$http","$location","Users","Authentication",function($scope,$http,$location,Users,Authentication){$scope.user=Authentication.user,$scope.changeUserPassword=function(){$scope.success=$scope.error=null,$http.post("/api/users/password",$scope.passwordDetails).success(function(){$scope.success=!0,$scope.passwordDetails=null}).error(function(response){$scope.error=response.message})}}]); +"use strict";angular.module("users").controller("ChangeProfilePictureController",["$scope","$timeout","$window","Authentication","FileUploader",function($scope,$timeout,$window,Authentication,FileUploader){$scope.user=Authentication.user,$scope.imageURL=$scope.user.profileImageURL,$scope.uploader=new FileUploader({url:"api/users/picture"}),$scope.uploader.filters.push({name:"imageFilter",fn:function(item){var type="|"+item.type.slice(item.type.lastIndexOf("/")+1)+"|";return-1!=="|jpg|png|jpeg|bmp|gif|".indexOf(type)}}),$scope.uploader.onAfterAddingFile=function(fileItem){if($window.FileReader){var fileReader=new FileReader;fileReader.readAsDataURL(fileItem._file),fileReader.onload=function(fileReaderEvent){$timeout(function(){$scope.imageURL=fileReaderEvent.target.result},0)}}},$scope.uploader.onSuccessItem=function(fileItem,response){$scope.success=!0,$scope.user=Authentication.user=response,$scope.cancelUpload()},$scope.uploader.onErrorItem=function(fileItem,response){$scope.cancelUpload(),$scope.error=response.message},$scope.uploadProfilePicture=function(){$scope.success=$scope.error=null,$scope.uploader.uploadAll()},$scope.cancelUpload=function(){$scope.uploader.clearQueue(),$scope.imageURL=$scope.user.profileImageURL}}]); +"use strict";angular.module("users").controller("EditProfileController",["$scope","$http","$location","Users","Authentication",function($scope,$http,$location,Users,Authentication){$scope.user=Authentication.user,$scope.updateUserProfile=function(isValid){if(isValid){$scope.success=$scope.error=null;var user=new Users($scope.user);user.$update(function(response){$scope.success=!0,Authentication.user=response},function(response){$scope.error=response.data.message})}else $scope.submitted=!0}}]); +"use strict";angular.module("users").controller("SocialAccountsController",["$scope","$http","$location","Users","Authentication",function($scope,$http,$location,Users,Authentication){$scope.user=Authentication.user,$scope.hasConnectedAdditionalSocialAccounts=function(){for(var i in $scope.user.additionalProvidersData)return!0;return!1},$scope.isConnectedSocialAccount=function(provider){return $scope.user.provider===provider||$scope.user.additionalProvidersData&&$scope.user.additionalProvidersData[provider]},$scope.removeUserSocialAccount=function(provider){$scope.success=$scope.error=null,$http.delete("/api/users/accounts",{params:{provider:provider}}).success(function(response){$scope.success=!0,$scope.user=Authentication.user=response}).error(function(response){$scope.error=response.message})}}]); +"use strict";angular.module("users").controller("SettingsController",["$scope","$http","$location","Users","Authentication",function($scope,$http,$location,Users,Authentication){$scope.user=Authentication.user,$scope.user||$location.path("/")}]); \ No newline at end of file diff --git a/public/modules/articles/config/articles.client.config.js b/public/modules/articles/config/articles.client.config.js deleted file mode 100644 index 7e1b0ffd27..0000000000 --- a/public/modules/articles/config/articles.client.config.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -// Configuring the Articles module -angular.module('articles').run(['Menus', - function(Menus) { - // Set top bar menu items - Menus.addMenuItem('topbar', 'Articles', 'articles', 'dropdown', '/articles(/create)?'); - Menus.addSubMenuItem('topbar', 'articles', 'List Articles', 'articles'); - Menus.addSubMenuItem('topbar', 'articles', 'New Article', 'articles/create'); - } -]); \ No newline at end of file diff --git a/public/modules/core/css/core.css b/public/modules/core/css/core.css deleted file mode 100644 index c510891448..0000000000 --- a/public/modules/core/css/core.css +++ /dev/null @@ -1,15 +0,0 @@ -.content { - margin-top: 50px; -} -.undecorated-link:hover { - text-decoration: none; -} -[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { - display: none !important; -} -.ng-invalid.ng-dirty { - border-color: #FA787E; -} -.ng-valid.ng-dirty { - border-color: #78FA89; -} \ No newline at end of file diff --git a/public/modules/core/services/menus.client.service.js b/public/modules/core/services/menus.client.service.js deleted file mode 100644 index d2366d1de3..0000000000 --- a/public/modules/core/services/menus.client.service.js +++ /dev/null @@ -1,166 +0,0 @@ -'use strict'; - -//Menu service used for managing menus -angular.module('core').service('Menus', [ - - function() { - // Define a set of default roles - this.defaultRoles = ['*']; - - // Define the menus object - this.menus = {}; - - // A private function for rendering decision - var shouldRender = function(user) { - if (user) { - if (!!~this.roles.indexOf('*')) { - return true; - } else { - for (var userRoleIndex in user.roles) { - for (var roleIndex in this.roles) { - if (this.roles[roleIndex] === user.roles[userRoleIndex]) { - return true; - } - } - } - } - } else { - return this.isPublic; - } - - return false; - }; - - // Validate menu existance - this.validateMenuExistance = function(menuId) { - if (menuId && menuId.length) { - if (this.menus[menuId]) { - return true; - } else { - throw new Error('Menu does not exists'); - } - } else { - throw new Error('MenuId was not provided'); - } - - return false; - }; - - // Get the menu object by menu id - this.getMenu = function(menuId) { - // Validate that the menu exists - this.validateMenuExistance(menuId); - - // Return the menu object - return this.menus[menuId]; - }; - - // Add new menu object by menu id - this.addMenu = function(menuId, isPublic, roles) { - // Create the new menu - this.menus[menuId] = { - isPublic: isPublic || false, - roles: roles || this.defaultRoles, - items: [], - shouldRender: shouldRender - }; - - // Return the menu object - return this.menus[menuId]; - }; - - // Remove existing menu object by menu id - this.removeMenu = function(menuId) { - // Validate that the menu exists - this.validateMenuExistance(menuId); - - // Return the menu object - delete this.menus[menuId]; - }; - - // Add menu item object - this.addMenuItem = function(menuId, menuItemTitle, menuItemURL, menuItemType, menuItemUIRoute, isPublic, roles, position) { - // Validate that the menu exists - this.validateMenuExistance(menuId); - - // Push new menu item - this.menus[menuId].items.push({ - title: menuItemTitle, - link: menuItemURL, - menuItemType: menuItemType || 'item', - menuItemClass: menuItemType, - uiRoute: menuItemUIRoute || ('/' + menuItemURL), - isPublic: ((isPublic === null || typeof isPublic === 'undefined') ? this.menus[menuId].isPublic : isPublic), - roles: ((roles === null || typeof roles === 'undefined') ? this.menus[menuId].roles : roles), - position: position || 0, - items: [], - shouldRender: shouldRender - }); - - // Return the menu object - return this.menus[menuId]; - }; - - // Add submenu item object - this.addSubMenuItem = function(menuId, rootMenuItemURL, menuItemTitle, menuItemURL, menuItemUIRoute, isPublic, roles, position) { - // Validate that the menu exists - this.validateMenuExistance(menuId); - - // Search for menu item - for (var itemIndex in this.menus[menuId].items) { - if (this.menus[menuId].items[itemIndex].link === rootMenuItemURL) { - // Push new submenu item - this.menus[menuId].items[itemIndex].items.push({ - title: menuItemTitle, - link: menuItemURL, - uiRoute: menuItemUIRoute || ('/' + menuItemURL), - isPublic: ((isPublic === null || typeof isPublic === 'undefined') ? this.menus[menuId].items[itemIndex].isPublic : isPublic), - roles: ((roles === null || typeof roles === 'undefined') ? this.menus[menuId].items[itemIndex].roles : roles), - position: position || 0, - shouldRender: shouldRender - }); - } - } - - // Return the menu object - return this.menus[menuId]; - }; - - // Remove existing menu object by menu id - this.removeMenuItem = function(menuId, menuItemURL) { - // Validate that the menu exists - this.validateMenuExistance(menuId); - - // Search for menu item to remove - for (var itemIndex in this.menus[menuId].items) { - if (this.menus[menuId].items[itemIndex].link === menuItemURL) { - this.menus[menuId].items.splice(itemIndex, 1); - } - } - - // Return the menu object - return this.menus[menuId]; - }; - - // Remove existing menu object by menu id - this.removeSubMenuItem = function(menuId, submenuItemURL) { - // Validate that the menu exists - this.validateMenuExistance(menuId); - - // Search for menu item to remove - for (var itemIndex in this.menus[menuId].items) { - for (var subitemIndex in this.menus[menuId].items[itemIndex].items) { - if (this.menus[menuId].items[itemIndex].items[subitemIndex].link === submenuItemURL) { - this.menus[menuId].items[itemIndex].items.splice(subitemIndex, 1); - } - } - } - - // Return the menu object - return this.menus[menuId]; - }; - - //Adding the topbar menu - this.addMenu('topbar'); - } -]); \ No newline at end of file diff --git a/public/modules/users/config/users.client.routes.js b/public/modules/users/config/users.client.routes.js deleted file mode 100755 index 879c2c47b8..0000000000 --- a/public/modules/users/config/users.client.routes.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict'; - -// Setting up route -angular.module('users').config(['$stateProvider', - function($stateProvider) { - // Users state routing - $stateProvider. - state('profile', { - url: '/settings/profile', - templateUrl: 'modules/users/views/settings/edit-profile.client.view.html' - }). - state('password', { - url: '/settings/password', - templateUrl: 'modules/users/views/settings/change-password.client.view.html' - }). - state('accounts', { - url: '/settings/accounts', - templateUrl: 'modules/users/views/settings/social-accounts.client.view.html' - }). - state('signup', { - url: '/signup', - templateUrl: 'modules/users/views/authentication/signup.client.view.html' - }). - state('signin', { - url: '/signin', - templateUrl: 'modules/users/views/authentication/signin.client.view.html' - }). - state('forgot', { - url: '/password/forgot', - templateUrl: 'modules/users/views/password/forgot-password.client.view.html' - }). - state('reset-invalid', { - url: '/password/reset/invalid', - templateUrl: 'modules/users/views/password/reset-password-invalid.client.view.html' - }). - state('reset-success', { - url: '/password/reset/success', - templateUrl: 'modules/users/views/password/reset-password-success.client.view.html' - }). - state('reset', { - url: '/password/reset/:token', - templateUrl: 'modules/users/views/password/reset-password.client.view.html' - }); - } -]); \ No newline at end of file diff --git a/public/modules/users/css/users.css b/public/modules/users/css/users.css deleted file mode 100644 index de67bf94f5..0000000000 --- a/public/modules/users/css/users.css +++ /dev/null @@ -1,14 +0,0 @@ -@media (min-width: 992px) { - .nav-users { - position: fixed; - } -} -.remove-account-container { - display: inline-block; - position: relative; -} -.btn-remove-account { - top: 10px; - right: 10px; - position: absolute; -} \ No newline at end of file diff --git a/public/modules/users/views/authentication/signin.client.view.html b/public/modules/users/views/authentication/signin.client.view.html deleted file mode 100644 index 91e256eff2..0000000000 --- a/public/modules/users/views/authentication/signin.client.view.html +++ /dev/null @@ -1,45 +0,0 @@ -
-

Sign in using your social accounts

- -

Or with your account

-
- -
-
\ No newline at end of file diff --git a/public/modules/users/views/authentication/signup.client.view.html b/public/modules/users/views/authentication/signup.client.view.html deleted file mode 100644 index e2051760a0..0000000000 --- a/public/modules/users/views/authentication/signup.client.view.html +++ /dev/null @@ -1,54 +0,0 @@ -
-

Sign up using your social accounts

- -

Or with your email

-
- -
-
\ No newline at end of file diff --git a/public/modules/users/views/password/forgot-password.client.view.html b/public/modules/users/views/password/forgot-password.client.view.html deleted file mode 100644 index e6275f941f..0000000000 --- a/public/modules/users/views/password/forgot-password.client.view.html +++ /dev/null @@ -1,22 +0,0 @@ -
-

Restore your password

-

Enter your account username.

-
- -
-
\ No newline at end of file diff --git a/public/modules/users/views/password/reset-password-invalid.client.view.html b/public/modules/users/views/password/reset-password-invalid.client.view.html deleted file mode 100644 index d5fc23733d..0000000000 --- a/public/modules/users/views/password/reset-password-invalid.client.view.html +++ /dev/null @@ -1,4 +0,0 @@ -
-

Password reset is invalid

- Ask for a new password reset -
\ No newline at end of file diff --git a/public/modules/users/views/password/reset-password.client.view.html b/public/modules/users/views/password/reset-password.client.view.html deleted file mode 100644 index dc8b2ea0c4..0000000000 --- a/public/modules/users/views/password/reset-password.client.view.html +++ /dev/null @@ -1,26 +0,0 @@ -
-

Reset your password

-
- -
-
\ No newline at end of file diff --git a/public/modules/users/views/settings/social-accounts.client.view.html b/public/modules/users/views/settings/social-accounts.client.view.html deleted file mode 100644 index 4712ee093b..0000000000 --- a/public/modules/users/views/settings/social-accounts.client.view.html +++ /dev/null @@ -1,29 +0,0 @@ -
-

Connected social accounts:

-
- -
-

Connect other social accounts:

- -
\ No newline at end of file diff --git a/server.js b/server.js index 98d108bbcb..af09ff324b 100755 --- a/server.js +++ b/server.js @@ -1,36 +1,20 @@ 'use strict'; -/** - * Module dependencies. - */ -var init = require('./config/init')(), - config = require('./config/config'), - mongoose = require('mongoose'), - chalk = require('chalk'); /** - * Main application entry file. - * Please note that the order of loading is important. + * Module dependencies. */ +var config = require('./config/config'), + mongoose = require('./config/lib/mongoose'), + express = require('./config/lib/express'); -// Bootstrap db connection -var db = mongoose.connect(config.db, function(err) { - if (err) { - console.error(chalk.red('Could not connect to MongoDB!')); - console.log(chalk.red(err)); - } -}); +// Initialize mongoose +mongoose.connect(function (db) { + // Initialize express + var app = express.init(db); -// Init the express application -var app = require('./config/express')(db); + // Start the app by listening on + app.listen(config.port); -// Bootstrap passport config -require('./config/passport')(); - -// Start the app by listening on -app.listen(config.port); - -// Expose app -exports = module.exports = app; - -// Logging initialization -console.log('MEAN.JS application started on port ' + config.port); \ No newline at end of file + // Logging initialization + console.log('MEAN.JS application started on port ' + config.port); +}); From 9ef18123b0392d17429aeb189c95e2a7cf0031fc Mon Sep 17 00:00:00 2001 From: Amos Haviv Date: Mon, 10 Nov 2014 23:28:39 +0200 Subject: [PATCH 002/131] Remove Image Uploads --- .../117bf261c0152c79a428db522715cdd7.png | Bin 3835 -> 0 bytes .../5323300de0498510f42b6bbb57800b77.png | Bin 3835 -> 0 bytes .../97ddbe2719b2f5da90c24b8194cca2c0.png | Bin 3835 -> 0 bytes .../edb5fdc047848ce1089c0e78693f7652.png | Bin 23176 -> 0 bytes 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 modules/users/client/img/profile/uploads/117bf261c0152c79a428db522715cdd7.png delete mode 100644 modules/users/client/img/profile/uploads/5323300de0498510f42b6bbb57800b77.png delete mode 100644 modules/users/client/img/profile/uploads/97ddbe2719b2f5da90c24b8194cca2c0.png delete mode 100644 modules/users/client/img/profile/uploads/edb5fdc047848ce1089c0e78693f7652.png diff --git a/modules/users/client/img/profile/uploads/117bf261c0152c79a428db522715cdd7.png b/modules/users/client/img/profile/uploads/117bf261c0152c79a428db522715cdd7.png deleted file mode 100644 index 65c6f11e89128853b86ae1cdad4f653601c75779..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3835 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?O3?zSk_}l@c*aCb)Tu)n=|NsAADcb2PP(;Pk z#WBR=cyfXSYqRK|_U1sw9Sk$%L?ysb!8bvK$Bu!)%{q3!l-1G2K=llsu6{1-oD!N8 pM!{$ZjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb8|b1OPT~@1Otx diff --git a/modules/users/client/img/profile/uploads/5323300de0498510f42b6bbb57800b77.png b/modules/users/client/img/profile/uploads/5323300de0498510f42b6bbb57800b77.png deleted file mode 100644 index 65c6f11e89128853b86ae1cdad4f653601c75779..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3835 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?O3?zSk_}l@c*aCb)Tu)n=|NsAADcb2PP(;Pk z#WBR=cyfXSYqRK|_U1sw9Sk$%L?ysb!8bvK$Bu!)%{q3!l-1G2K=llsu6{1-oD!N8 pM!{$ZjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb8|b1OPT~@1Otx diff --git a/modules/users/client/img/profile/uploads/97ddbe2719b2f5da90c24b8194cca2c0.png b/modules/users/client/img/profile/uploads/97ddbe2719b2f5da90c24b8194cca2c0.png deleted file mode 100644 index 65c6f11e89128853b86ae1cdad4f653601c75779..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3835 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?O3?zSk_}l@c*aCb)Tu)n=|NsAADcb2PP(;Pk z#WBR=cyfXSYqRK|_U1sw9Sk$%L?ysb!8bvK$Bu!)%{q3!l-1G2K=llsu6{1-oD!N8 pM!{$ZjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb8|b1OPT~@1Otx diff --git a/modules/users/client/img/profile/uploads/edb5fdc047848ce1089c0e78693f7652.png b/modules/users/client/img/profile/uploads/edb5fdc047848ce1089c0e78693f7652.png deleted file mode 100644 index 2ed647ce9d074b185efb50605a50cac0c5d78b6d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23176 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%0D1RU@} ztVS`Tb?{*F#ltsFkRklvzXxP?aQ|rH)xq*~w=_A3SKjAAL^?0qQchmT7)7ZJhneg{ z1y7cfEV-}T^168*VK83;9vjv_ZIwFud|a~Fc0L${&;ng>De)=@RX<(gZfioVkQu2Y zyJg>eT(3A$8@gc=X>!!1uj@oWMJo57;hZP#;3%|-T?<;ExO>p^%6bXyTMO7OMZZ{hhO4aEK9}JDX62rOo0!wKzae0) zRh!El1H>F^hM97WnmCxJy56F|V=hSb4%v4`K^J6fp74lW`mh6^m>}LK9OkCR0Ro`U zO+Z>VJ>ubjIZ?ck6TKFbi@a(FqYI3}yoCUXt_vbIseOvj(DugNwH2IUdhP{7+Y(6rtBC$I%OuTwpde6!) z^5q^S8%eg@TJFtEWwMOfE<>K75iBy7`-uzr{Mb32vRm_&yCQ z)_OEtnpq|QvG=OtsJ^{~R8JJ?8CAO^R^X8gK;uwB%@(@bdBuFR4LLnE!oXBn$YT(X%DRKDy>w z$>sGzv=U`)IeryNX)XR#1)_u9#_MMa4b-hB$n|XO-+uw@1Grb)&uF4R=0k7|0T?Zi zeHTUC^FzwP%@C39hjQYg-#id@_}3~4bl!=$YyPkEDf#Nrr=wGL{F?J7-0eozNs6nG z^YYMwMT}1I-`E>aJ0z%o@FG^^+gHIt2b>wc`)xsFZUAJy*wp}aa*9z86#>s2{KQix`xM1k+5g;V zI;v3QmvSOvM3%>U!R~83YCtLr8G_zrrhF)E@%H*7w({Xx(Rbp<{llwN5vSCs6Exzz&`DF&S7jtQ zj+@ZvIg|o6`NCj?5oq`1ZYNP|R7naE=*lQWc^ei&!VB--w2Xv0BO8 z>Inq15(hNLM8vbu@PIEXpDzjU0lrm^;y4U5PGo;ssXO>$=h8y{8xIK}p&{d8s`^U@ z>Cp=uPwAuEkII=D3}JWnmd9U^LaxhqMSraRndPS`sMf)GLo-?076<8SeOC-T}$>k_=NHe97g+cG}`rS zLJ|);22kULe9vJ(@K4Bc{MCl`$5S_oF#(VF`#mm0(RlV-jKy?_3j3+7^~uwtA~O?v zHK4dE24$%_0#T&L!EJ2#y(A>5B76zb1U|oemuFXd8Vc;o1J5~!@HR@LYsG7Xv7fni z`KuiuZs4!lr!UR1NZrdFU|Ix?XXgMdPEUH)Rf4ck0&Br1<6|4DQ)Ev|azm!Knokd& zB*XwL+S7b_k^gsWOa7HdmfT<_A>+YvS)Brl-XJ)3%ddgXNpjV$G(yCTk_n zNVvim^fR=fGuxwfQ29BDVtJ+ac0mU}4LBkS*dV<2R~VwAal@9>io=n}!-n~>ZA%gU zWyZ{HhSps5p!9d(KTr39QBL4)1lDxnK(5iTpAr!mk9PF;VU|yFH3U?zuKm7UcFOju z`N>b=8GJ=>rxJEx@=yx#0;O5}Wd_WJIGB-5pUCQ`3BEf=50&Aq_Ql(m543I9$E=a3l_~vWz@79~ z0idNsp+h8NIG-u_fD1O2w>!A9a@B_k${2$MM-(zmMZy)oH30n#Llx*NoCVdW*8g3C z6Zqt}z5IKOFK86rLnon7oB412twFGYz(`9;*YDn-KaaMqIL-)u#DlMMcTqJ^QFFQ@ zst@FWTLNF(7sGkeA`x^*H$=ikbAFk71(=&o=xQX=n7FB>R8&;Fcd?kk*x9lWww~?=nU%SZ!%E1#8*z37@ymaiDP?}iDSuo zaDu0dbSeB)r>EAm_m4XU{#h>fw6CmEi&_s+JN$Z&JldUke(f@SfY`8$>WeCgLO47g z6^H}UYj=bL9~TfPRK@r!95%0>efa{lRIYykUEKibbAFQG$UfAlJ6VQq-0@=XKWSwD z2V12e16go~?^FzNj#bh5SCiY#K09q67s0%tA}YBhB| zYWMx{vyQIs#P_Lw={ek+UT%QN5`~K(I(2NVR^`HfA^MU0156PuZ5a?2hn}@FbGP`G zqE~bqIia2bdr^`8Hnm3ntwjyxZU`Acs~x?#T)lKszbREO_MdH9&}jlIn9u`homTM> zxugoE;+NczsjBK2icJWp-UZTq2#uTRW zTgNYsr#pvA-P#*Rk%*}|LP&laL9kHkOyhRYm9u8TG7cQ)Z=`T6Rpo#od5=#yfU$OP zpLBup>(ai9=bZ3^2|aJ|)|kCBC3&@*WQCkD5|Mayx*V_+`_~u#1REE-{bbzW+!sd+ zZ1c}MEn8DVs~dw%y)WzpUGIEr4aI43(X`0Km3|gAQm*tJ;7OV-q%&VYmAr=8J+@;Sea8=X1*W z-{EBuO!tt-7eZ?~hqR%x!v&(Xv))!i9gSOqOjgk#0qe`$JW=&}L3*0&9WkFtz~d$0 z7cPeVQtaPwN?s@|ykHyw*R!jo$tuIG?RU5k61%$3$XQlZPOZu>Wk{yOfO{+&mMrJA3p}})$KZp(CYdu$Y zHmQ|aW=o^X;zn-mXL#aS?rg+CnoVTr>a33Ps~CVcpr~qxI;mJ)Wl1VDjKtK9-QoB4 zljR`o9T?XHA(e)Ry(1`aw9;0W^3uS(1672;7sx|6=+$Fj7f5rZK&k3Poz*tl5&rOC z&Emgmp9BpT4WIQ~ejb>^2>33b1V9fFM9OuW(YawiX|}QM%{wrt9#8zJ4Y#f$l!QJkotVHYos4C_QztCJS_|`YdV4k6 zTWdx9HJ=P!40lK5(q?RfE&sw-vt3PT39P{=s_y^#TxtQ-dAvWp?QvXUQt<)m6TByv zE~}j~b5KP4b}m?l5SRG}P+-&GOEWis73iSmZ*S((_uDM7_lGA3Hy1U)uUz~~|D8-( zCI?6XO_ShQ6I(lYYP7Ijj~U(>qiH<};G*y6Tac|fcN7BbAFBl+OyE~I|9A+yiCvK6{Qf2A`Ka@8i$#;y(K6}S zv^KEp?n!Z$jOJ~asAmjPW((W>Mm84evi;<0x|V{I`qP+QxJ^8oTFX%(XSrQUMs~q> zMk!eqkO}3!aO-?2W~mR;t;i$GH^fCF;;wV|?xziG(SqFss`xG`G}vH81M1RGq?Yd_ zWuMFC`Y7ef9q=-y?BR<=A91@=z|M110 zDUkDy;wcG5oQU{{`P+9{i;I@?wUrJlzJGIhvHx^D9;zwSe=5~-CkIC%|WhbyU|Ka`q8xv-K^om>r&}-GM8| z0?&npCOR+AW)28--58aXfdp(!x`&dvCy!SzP($~A3(uy_sydr(x*;H6`YAbJD~yyk zj4iW^ZfIVc`So=~tLek?p1;(xB~0`Nj(T&dzSl-&OQRoa+wpPd^I0j~{ZXsrw00Zs zSp%IvEy+_9DYf6X%cHQLqo67-4vbU`SCQx~VOw5OOet>5M=CS)5XB2)`ydZsz+t0b z^^mYjwXRU*V_BEKE#B}EvlQY@v2aEJ(~5hEBM0P|Jow~!$b6H8t<>TKxZsYt2VY*6 znCW?+r>&kg;X!Pp9MAMGXQPL#oLawq%GvHa@qxK-@xI%2HFnTCqf8ow4XqeE=4GtQw6ZbdeyDwb7R+ylD#(X%!~oSdSPUs5qPpjGSgTdGG~SMH)xthsZyR4a zqoaq&6fxii=3Ejl35i@kp$?)-m|H8Q@Mq<3O|Ri~KMQIwnnbz2b`tb3Ix>j?U2DtV z4g&uYpy#RCXO(a+zE|9T{a=r8$rRRfzr8HXKh8Y%pZa2#h5{* ztDddIMZ+c;glVT050$VQ6MA^3R{K@ z=pht8%0j<=<&nAoptp=5ZW|2W1E~6VM|CCd^zQ0?$5~PCePE;bqukES9o>On4}?Sr zc&8f813+M*a`^S>!FbH;I!6kR?gD-eA6)fq19FX%>TuDcFIZo}6$g|^dTXYDq>a7s zV3+Yv;6bB!E(fg{9+AxaOI9Qu1Q z3syvr1_1Q^T`Dd#`0#{5dht$(RMT}n-4y$R7ZCA#UoQc2kHa^M-tEY^06lc5(}bWq zoIqiOCi(=1zDrsmW9~1}ctc=4Y*?KTK^)wHr2V4r8&r*ch^O`j;H0jX$k_@^E_5y^ zC87>ba+!6xbJUkinR5K{wH*Bse*r;0AV5nEuu3R&>!sF7d8EZ}pA@+q!Cb9&>!@&F zp}Qg_%u^*+?~DLoUf7)!V1JZv+weQ;0j(u}j=5Ooeh$qaM(W2eB|W0Aexl&7~&EgRL>0o%p_z9jkw)vB9mgoz191(6SH}Wnf&tWo=~^_ zQZ@S4KBoZ|039g4WH~1SRsK>}kk4cAN*n-E^lbK$_hLCpZ#fRd%Jh5(3TQb*!@N@S zrLpH$jL;TR@lf~;1ff-V?LCn5rs~c>fI6%-zq#a%3R?=7`-#9Y4P&7dr{sFE{ID|& zO}*Ge%#$TJuAQ%DgJ65G?<=XIYC~!4Ful6k@c$K;{Rb`;`Na;qAYkV!XfuP9p>%JWt z%t5Q=*+c8ADWZ^8u2z5_m7hF2rCzporp6;P9vpGAy{4R3apR&H4~rH`Pb=^kcg$XW z=g~kBjN6ydOvt9tfwh%xZDp<+zD{N6cgerE=M!m`XHJllvp(v6aU~G*NQ9fdcZ`JT zRnH>E_vaoPibT5XGXQE-I#F^xpTMn>#ZA2(hxt<-wIcTL^KF(|C)O~SfyeruI_8y2 zZ11B!v><@aDeep_%ET!wVZ04viCH!SAJ3MT2%m?tiRcRH?=th7E4@!2^?2S_aD#(~ z0}xZ9A+o1vE+3hWu3;Yd5pT2Wd?*cYor|uOU?9>Dn6Q%$q0#YW`op*AK>}O9 zeHzf_Xh6$Ig4rlBo``m&HppJ-!0X83bc+^s4b{&PplupVb>HpZ8*;@cx>bwE( zI00Z%bYr-M9~H{;TnVjcHe+|UFFML`ghL?i8bIolR9S}saW7>O$chvO8cl8iv%oA- zWymg78GRQ9#8;`x=m+j$;AwawYCjsHM)#$l6y3lWLY>wHOXe30&;?8P$p#YP{+MVR zq=QvbL_GVi?RZHCzU`$!!HKM>5C{xFbfvV7yX4ClQVg6?=N!miUt!aB+Y!SS5+}nn z47qwYF9|2g%Tkp~4+qnc+;Y_r%Eqg(3M5Ex-@LR#Ga1MCmKLJIV({LjuNLZ9l&A3D zNCxa#VI{19&e?yS<<&t;79+pglU-OId;e%+h`@+3K^IEs)lF}yntfnH^aVe9_M7$8 z^!B>6ZQl3;Jt}hYAdmgkp5!u5($hT6xk;SPE;C!1TxqsSaokzcCJiPr1Pl=UATv${ zKqj0Ty)>XGlemD9&$3zXG$a6*$5eR~r+re>$qlu=LBjx`d5V}oe_>r-@6Sk?Wz;z7 zdVp)_>97*$B$e;U*mrV4ACT5w*viz55c?LrVsazM+HJm7}7F2lfO% z3Q?oNMEK8Ty3jHj)cRX-sDkjtkbf^`{&L9(?&d62SFtObm1Y5&U6OPhRSR}cP{P3! zhXu-&k4VQrV`_^^nz`?9(wyQSe4*(YxRtO~=(6bV9A_dwe$iY0c3`7kh5gjj%wZvVniqld% z3B--j0B>mq((w5omlNY@(rZT&y~_*kfe%LqsdTKNZ&>92*z&>rN*wNRKBTmLS@dOc;YuTiLMKz7ou|nExNfz%2ANC+uyXiBe`Ll4<6M$ z4lFo$c$?w(sdsN+OKQVNWfpqnsE)yw8=$>(8~rf#O9r(?L*&Wj{Ex@851nJFh;GL4 zq(8$>R5+VJA9RWJc+m1PJVQ48(kl&uxBIh}I=+CzDZY`XeVGSr>Az2Dx^hs8yxo6S z?gw3VbTW=eWki>XgaGD_^|6m$pH zTfZRvsT7tYtRr}g=xlht*XBh>iC={nHQx|G+%elC%!Lcr5LW&v0c^4$L zWUKopZ6!?r!2K-mk*)?$bG?7ZI_yMG#boqO4j(|C*F;2NIkxX zzx_xNXJ9I76Zb++mSTac3O@|Vyr|>6jg>uEIclOe65VeWZ+{o-@H^6<*JAqN;?pml zRR1dEqbx(>McUm}nb+K3W5penPn1d4Aw=25n_k$M;i~hWqEQspAAJi-?IQA_%U;2g zQyr#qCjD$VOWd|$xKypFK5VajiBidGwhCOX{*JeiDA`~hBre(3tmns@Ut{*#D=?=; zj7rBA3s>m$+H>)LMYA;H{+Xsty-v*-&S=9l)zogp%~ih(w@B?n1w7RkM5Q%I_`e$} z`E@Gq%^ls=Ys?U%THw|e6_4(GGYUqWrwIVRTP$ zA;(Mqg$-M=b@=4k>_?k=m(oNRw>VhK*)UMDrs zTnbUO#%}LB{Gv0>f~J^8Q#)@KghHERc&F^b=QMVdj|sshpUgXJE!VMZX~wbjSY=sT+;g2T5xZ{35Z5$zvD9KnL@tMe8r_=#EK?H+ zL0&L)rtWir;L59FFR<$@;n-?sfkWewKDt?rUFG9Pu{$&g8QHhDq-A|Z`A%{fT12h1 z_1(n0-!jh)@G^Tu+RFqPBqqz+jLGFRl;#)?a``|1-g%J{;s$H=8>lOrg8#h&S%+iK z9y^!%{rh~HJ9EmBK+V}BnXz$}=Pfxu4G6`*TR$BX_k^`=N*;WbW_g`yz+mvfs>i@+ z;kDCBSp?&P)94s|oGLBVYAm)_eT?03zc+7bI~-`>mlnV*#V0H!7R|9PD6A)>o`11u zzT{lr5!2ic`?pPA?ZdqC=3Id8KwYsjWXS&)GY#6kFa@j_oyqntp5rl?abj@2`7UJl zoqh5A85LVLw|Iy1-xHXe++QmTx!k7aH-^TXW!Qx}Ik?b$wCGyh(RW347u>9YMM+6f9B2yE>YD6 zK~8{Ncf2^{9W2HJg&IX5_bi7+qB<2!Hj@DuC#^v(EZhlFBznh(2{>Z@-2C8W8v6$h z4k||Z-^zh$@l{DNCN~b?Tv(TZ+`MD)e>2=8m7T;Y2JadTZ|U;l)S_kLOaPwyVbvSJ zHyuS_!nMod8&Ute%oaGh)SEaj&eVPesG1)o*Ax}?=XOcN>>wj3|=?&PfF>vs|gRi~Cfg4{)PB%xT1fQO}#2mph&dnp31jOmx zE#yWhaxhRg*J~&SDm2|L{zJBGVdF~v+MpQfc++{fDI@J{=25IgdOhh5L6_YBg$Sx zJ>_Nb`Hob977R}A)Jd7gba~OkyxLDxmZedo$dJRD&{oZlAR`DMRdNthDedW82L5iT6ln6YgfSw5@Re2`rNePujgK?_4G_u)rY=~ION zVlnfMlHCUiAHBa6RK6kKm6)VrzV6i>-|z;eAYwR~s&Rp*~tL$0RlWG7l~%Q`0jx2#*Znik-FuP$Oa!U_oua-Hq+PF z-<$?9H)KPFIBhfUZMgXC>wN+jomQfs#_-in5&c!6qBitHcYTxp(V?4y$s<*T^&%S`XBV=Ub}_ct&?Upl2b_6{wdH%i z65-NZ^ly~N4?nVmsnJpKV%eNP=zS&t&RV^%Bjtp6DmKoZRHQ{gYF=?&A1eA9kpYx; zx`XN5V|I?GRf3hQEsyfIbv`I3tC@>?{lNI*+trI>(8 zL}yqHT&qu9Z|K_|NV0A)lzP1QxCr2;YA=iCLXg@;m%J_UU(N?9{eEuO_jSJdEdq@T zsiVU~&gDuHSRPVNxycC`+HZA`;ur0Y$sg16O0PPn;6{>mb!X#5Gj?IhtjVm*Koy(I z(6KcqEx-a_X1zrOd$aGZe(HXRZvDI$l<#Ok$uny8$u{k}2AI)gO;)iMv+De*iuYQFI!)~l=TXq zZeOObLYfmQULC1%>_59!-_;;Ot!x~PERJh>mH?|v0;Hp)!&6Id<}7k&?YMmKWbnVQ zVLwsFdDjnob1T?6vL5Fl2X0y3HN!%)Sc-$KRVjy1=5!-Cxw%(Gr&^h!~)h3cR?@DSk?;eauj!&1o#^(J`GvED4)gS-= zx&znh-kTMPi;R>lGIB2wm6?!DcDxmpP~3}#ok~%zkv+=J-Yp{6mQ7|^*NTjLzsKkM z`6E8}$8%oip7R>d*XubRkH>c7&f=T%)j^$qNzufg?`T~NByo7de`oF^!Yvh|t5+M1 z?%0b9W7FzBt`8btobvoZel}cu3B+e0V`Yy^({0qfZy1cVCdue3M0+Gz(0I}KR9Q=z z1Lrf__yyjBWD~vvAHy3>6P4pfBn%szIz(!}bu3qwo1OLhZk=EllYHt%d&aRi$Cup} zS%EeR^R|@Xt$a#Q>n=2UA-~p2amF)~`r7Z9-{CHdtB&j~b(TJt(bf1q4=CeD*1ajW z3??*)0hsAjSLq=$s7xs1pR~KZ{rhYyNX^s+HRObZjGx_Y>7YLjW!zvDiYxdMCjVEy zinBm7J~uQ-$1N(rUN>;%w83D;AB}n&s(zczrh9%vqgg{jD)PcD9pEoKsWR_k$*EQg zrK_s%nN3TD90lHMZ!KD152V}=C=VXDQt>iR2&=05{qa`xn`NGlS;9xDk25xhbKV5d zt>jl}gMAT|Un*F4T5!>))PhWyNw&372FxJfTW`6o8SRcsVc-&VD6}Gzch8qr!{6iD zcjhDmZxGkFyvN=n`!kV%nQL}`ZNK+Y(gykUAbvh4Z=Y79uQHPw087jN!fvv(^t|$N z_G(`G2p2|T$wb6rc|K2+?NMq*h1t}~@q4^`uMK?%;{^JYi;8Qso9u0WzOA}Tl3Z(~ zn)}#H;4_)b_U!9U_cIV}J$|+suMFpA!y^NDDiVumF1lXg?_VFI>U`qeQZ1U{&hD4r z1s=K_Tl=9+GIm{ae<|Y4i$vQPwG9SC~9p0qNxU+WuqOD`v7r%52oUf(KDcApEIAJ=s#V%@*yrAUjZW6`&hGWd_ zbf3&8;y7WBjcCv3Ctsdu8}9+uN~}lnI~flBOLoEs7cVX>Rd?};rG!KV9r9Q17o$~Z zlXv+Q=IZH-?*sW7a?c>2aI4@cXnTyF7kTyA{s#K>D5X=P01EnWYr;5v*zH^KCZvn= za6s7UQ?oTH+d3G64gZ0XkWPN0@`|q8U$CESf>`yH9B;kvO*D}`J6c5qP`ySvo?;Oz*weoGi5yl;o;+v1b>$JD_JHRy52;k?bO3yMy!gNi%@6x{ z#ifH=`n^X+sys!W!IuOjYMuiS{&d9kW0g)&oYpk)r*riQnadT~ZF`lX!0)pUj_*ij zC)C6UN!abe383`t`7Y9}c)fDRO{*Sg%t@WXSRxrrejBHoSZzu zAjoqEr`_}o#_%wlR0!rg-#`Y}Hm-QhepaOc1!P#d7LyZOt5=&%q%V;YyZAud5h(7) zzx;QmwEJ|hk*Lz@!1bFjPMGmY0Md1(LIlsf9Ewk=$n&7_^Gu!1NmED(;?3A?E>pMke`~45G zd#CN-U!P7J-tfn|{ibnu#}31wJJ>xxP2V`fAKm}zEV=Zh>mk`gzmCz)#@JPoEvipN@FhcoKP=ouh zk*b|9u}v|HS@Ol;On#MomAj7^nSJ z%5aEfqX<-hbWYkRl`cfomDcuq4R>{lvSH*FX-k_`Q#Rj%A5kTg8V>ebwjb z%S5iRET7|!9X>(hE8cSAJiH3!tqr)iZaguKJe|{m=Tv%-LU-UsOS8a=I|2fv2bj8t zmF_I&D@nVMPysv7AX8f_sLKQ%~c4s^!FMrejzn$QrBmSG@I3$Vd6^@|jom%I&JH2Odp-AKUAYRy(bJ z-2heB6HxX3`ejcXfsy zb2<{-RgsbT7Sxo_WGv}0{ z+zd>q{L>sLyi*r^YsBq)`_|tLuFof-@xyptNkeY|fUUAFde!09sCZ+x{vf@2-Sgk7 zVJ*X5DGs&+%V*;<2i0aoW$z+;PrUmIAMYEjBY&jl*XjOg(6+{^XJhO?n-;H zC!mc4kM0}wpPYq_+!_8?^A-*UuEfw`pu1^Q-{?foWOb~BhnP|5hL7L6x=k4ILKEPC2peog;yTSIc zL-CnD9oXmd+t0dqA=RW=`p2_P^}dW`w-!Fim6i5LxlMJt59R#oD!1Fhxu7EZf`t#? zM?zImBnK0c_PdrWh%_hR0C#^OmhNu*)mmKJu2fl8Y*~vSKz)0OKTYdmtmzW9j%HwU z@LTwA$j&8R^utaL56!5d@^_zX)u5o=Enk!*K1J>-seIDvR_FG4G@cx4=Qi?FZ>v^W z)J!;6T*v-i>ipKWPrnFw%LQth?G1mmX(EAQXANF$%nSKpLQbq*JiuHJIqLfT9^z8w zBrh%7llLmlf8XVN_I}YZM4rlV+^u>6iS=m1QWTh_H=+LZT}&Z{bDaI~LBOKA zZt2<+e9Xbpcv<;r&u7bx0BSloZaTGvgM-n%wB{jtOmXMwVFZa^8$>I6r9^uO zk3wj3*blf?oN_tU2wC9v*WZZ*!=d3#NgDkLuQ~Eq+|99%>ei@*CAk`rp#&1Y4){E$ zn5-Y5briWGKA#kE?!z>w(yx8pO2br z?S2tJ?=deV=rOGCHu~FpJtlrM)|@=(yyKX73x(Umg6G4bO_ygbof2*8!s#Kt?}<~b zvY?Ryc}qstPza|3{M@Ek4=Z~RSX=pYI3q#JWWheK09oe0*UQB6&=kaUzy6wRj;3<4 z&Ur+1ZD$>@Qs*J|$L-nK53W{BJfbI=9@FqdO8A3hvBc_51xN7-Zh}+h(DF6SNeAZ3hsM$mzN*A}kPQ@fiaqw0wqE@@@jCT8vdvSk?N9y0GbfBC7G9)gQ6z zh3niH1WC0uH0e#=HPSQuIXx!}Sh}`HZhUMJ1kFl=4fh0taW9g77X0Bg|9-kwTb-=2 z{2{^GQXztoWIOumOl54%?_AEFS#?%BpA_bUhcAKYOWvp#{nrShg0&xij0RshZXxwk zf%aQQTgr>L+jOJBfRxvEHOo@W6!z+n`)t6PD{~y@#rc;);JM7h$^h%rbscRxlLMDD zaW{W8)WJ?Fj-kJ#xVpwfx^!+hoSu+unsJuSBR_>cI2$z{>%Kx2p???R zUo6ayFytC=C;b>qjLv~Sj4xXrGqi#-wq|Ki0h>jBUNsOAYIiqyU)!!h8!u(-U+*lv znr;nzZ|#4vQH29B@v_%94&h4o?38BKt}ItK^uMzpvE zPanNE+Xl0fQWun$KSOKga9ZIHDP-VeY4M*fGH>Kre=aY~0^+`*NIVQ(ks*}Xf2`)5 zlLJY^9a06u;iwsYVvBtzH~G&5q!>8+a1W?(Kwg9~-{g25u^ScgFhjWe!HVFePv3); z9AW@Todp;nAC3fA{uqqGw2D~u8(qls)lU@x=02ie#uRj%oxMijv4_gNGBde9iv+mU zgwvWlKuq|=0OW2r}Z`i%ly$i<& zR=Dq`NI#912|`Of^4Qp2{WqLDw5Kjam%OVw5>*aCuN2|NSZ4ntSYyGd1V!H(y}}!GW-6XyWPm%+bP>j zC?$FZa#IK<4li>r60`_KcQK#CS`OBCx6zoDTNral-li7nZ7tgEq=tpj{c3pv-US5tRDu|C(LvR&!7`UEM9oQ$~gAuUE8k)YagB?D zhdPIP(6qU^qxHQq9R~ELv(a<*4o79!Af9Xjy@lkoiGD3HcL4w#z`!uvg%XL~z8CpB zYSZ}TBy^9a*M4?caG|;^+4}_#q^?mpn6DrZIFQW=J5Ek`pu+L{o6+fL4t9lt@Q5qc z9UX7_Z$LZXdncG#vg9Dr~xa}L3aeQ4Gg_T^c9;2a9ifkYRf z(~EHUa)536bj-I&zz_!D0E@w^roR7i#H5FE3c8Jh|H`5)ei?7hBS{pg{cj5yw11JB z3K!BsgHZqEPOU5%bZCKpQs5 z3ythuZ1Gp)4s)3xv$^8wH=3M-wjqZaktl0qYuWY3sKgh|lH-k?sgh-Ros3CdbI>UQ zydnT^@$9kEOGD=(crJ1RuRwYbnGqmK23TW=Xf?w}$r_Z|FW6co3qw#u0?4#PvZ*HB zgjd{F>P*XR{hHT#yoLEpF~h1LfMGsj;+iwg6+hfB__NgLDh3`&sjtysH=9ZmUt2yH zd*RX<^_tV}wbS&(MAN@rC}@zjIHDc241=@)i$!38AWu3if$6qw93zOv11zaS2Vwr8 zoAmxcl>weqAKrGyMyl&%&D=oO1{#|GbX43Ly`pT33JL>a?fmR&s;ts9yQ}-FSCo7t z!JjZr!e@V5_+@4<#i7%fr44m@+BlB_RHE}V{FKG*v=4*a5-Gm4_@+N>{GD^f0!^I( z&$sFF7ASq;%m6=VCCdvT1J#APUbT!RR5ofX*?G>Bmz0JKUP3d8^Mf?OrX90QvZ4&C z-Bb)bVC-sE-X2{n9w1jo@NfadofRr>3%C@9=|Wturp4Slvg7gO#1o;*#>xM)KH_w+ zvWX5tX-q{e2L)cwEL;?2^;Vd| zj7NKxrqxOE?j?`AiNd%L<3B3V0@f-FkRLhuKB2CI1AfIIp!;1Qx4;Vsm;2xMyuac; zeyR#EhnBOoKW|K(f_f}3!WNjne~&mUkk)|&&wl9dAyDY6fLE+@G^iDVpEm6&&sK8) z%+Yd~7N3I{@H)8(%@K**tPhY5eNwpqs*BDLw(e`3N?gB51Q>ZvnAS}PF~W-RjGNif zT2l}X0EPiPa5(H8kgg|(j{4I)WogiJfT!pfq)r($-6*N~QsVG7^AyZb)AhIVqXU?5 zm@q-_1Si7vDEq%5{USW!t}HXACp;3iI;-d$8d~;&($#mF)JI?eJ$f=ZxUJUSi6yUg zIT~1lE?sRKUVQC!$8Nw!?5f9(*<{XbVn{CxBbW(rAG~xxCJXNauG`*16f^;X{0Dl;NHoZYPmz#xK-0sXm zHp!cMmn^N*f;`wAi*vNp-515np}_&+S3&E!<-cq^ik*Bd8{pp~C4%1Bl)>un8_w_K zl%N#~b=49Bn;|de4658pI>!$FJ>~??j6E=RUi#LdjRL~JBk(xn7!le|2!_ypxsmP> zVoQ#JkUGIKIgniJSrB(g(SCf0J8AtrNrDCGay=o*SMXtv@qeFvArph_u1*JW?Korw zZu(S5Wsc`nKd*b(BvY*Cf(B|5(YAXPuWsO>KLCLxBhcJt0p5k$%?kNWntO!Y)-ag!yoR6 ze|V6#DL#c_jJuEy#r2kfC^kLR=t7X45iqjf%POf;yKct=ZG7N27mj8g73%m`1N Date: Mon, 10 Nov 2014 23:29:19 +0200 Subject: [PATCH 003/131] Remove Image Uploads --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 721705ea02..303af01ca8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ node_modules/ public/lib .bower-*/ .idea/ +uploads +modules/users/client/img/profie/uploads From 87217da9d22ccefecce41a521a4b16105d0c18ed Mon Sep 17 00:00:00 2001 From: Matt Raby Date: Mon, 24 Nov 2014 14:10:22 -0700 Subject: [PATCH 004/131] Because html5Mode is enabled, the links the social buttons point to will not work, the request to /api/auth/{provider} will be captured by ui-router and directed back to /. Adding target="_self" to each link will fix this issue. Maybe creating a new directive for links to hit server side endpoints would be more appropriate but this should do for now. --- .../authentication/authentication.client.view.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/users/client/views/authentication/authentication.client.view.html b/modules/users/client/views/authentication/authentication.client.view.html index 44675189b7..3f67964d3c 100644 --- a/modules/users/client/views/authentication/authentication.client.view.html +++ b/modules/users/client/views/authentication/authentication.client.view.html @@ -1,19 +1,19 @@

Sign in using your social accounts

From acccc03814f35a9578c519f6412be09435befe3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Bj=C3=B8rn=20Hassing=20Nielsen?= Date: Tue, 23 Dec 2014 12:56:29 +0100 Subject: [PATCH 005/131] Fix #283 glob path issue for Windows This fixes https://groups.google.com/forum/#!msg/meanjs/qOPHzMtWrPY/da9puKbyI5UJ as suggested by Silla Tan - also mentioned in #283 which was closed without a fix. --- config/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.js b/config/config.js index a966dde2f1..90e7ca0afa 100644 --- a/config/config.js +++ b/config/config.js @@ -89,7 +89,7 @@ var initGlobalConfigFolders = function(config, assets) { }; // Setting globbed client paths - config.folders.client = getGlobbedPaths(path.join(process.cwd(), 'modules/*/client/'), process.cwd()); + config.folders.client = getGlobbedPaths(path.join(process.cwd(), 'modules/*/client/'), process.cwd().replace(new RegExp(/\\/g),'/')); }; /** From ab18e14aa5b9b124228c70d37a4d6981fb9f7ad9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Bj=C3=B8rn=20Hassing=20Nielsen?= Date: Tue, 23 Dec 2014 13:32:27 +0100 Subject: [PATCH 006/131] Fix #321 image paths in social meta tags [v0.4] --- modules/core/server/views/layout.server.view.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/server/views/layout.server.view.html b/modules/core/server/views/layout.server.view.html index 4a55542ab5..576f25a4ac 100644 --- a/modules/core/server/views/layout.server.view.html +++ b/modules/core/server/views/layout.server.view.html @@ -26,14 +26,14 @@ - + - + From 9929f1b5f150a24987cb82b4da4d4779008802be Mon Sep 17 00:00:00 2001 From: Igor Freire Date: Fri, 30 Jan 2015 10:33:11 -0300 Subject: [PATCH 007/131] Remove username from facebook strategy Username is now deprecated on Facebook API 2.0 --- modules/users/server/config/strategies/facebook.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/users/server/config/strategies/facebook.js b/modules/users/server/config/strategies/facebook.js index 4329c4d03b..7acdd217cd 100644 --- a/modules/users/server/config/strategies/facebook.js +++ b/modules/users/server/config/strategies/facebook.js @@ -14,7 +14,7 @@ module.exports = function(config) { clientID: config.facebook.clientID, clientSecret: config.facebook.clientSecret, callbackURL: config.facebook.callbackURL, - profileFields: ['id', 'name', 'displayName', 'email', 'username', 'photos'], + profileFields: ['id', 'name', 'displayName', 'emails', 'photos'], passReqToCallback: true }, function(req, accessToken, refreshToken, profile, done) { @@ -29,7 +29,6 @@ module.exports = function(config) { lastName: profile.name.familyName, displayName: profile.displayName, email: profile.emails[0].value, - username: profile.username, profileImageURL: (profile.photos && profile.photos.length) ? profile.photos[0].value : undefined, provider: 'facebook', providerIdentifierField: 'id', From 9c501dbf9f4f39f41428fb72289783124aaf7c28 Mon Sep 17 00:00:00 2001 From: sylvainlap Date: Mon, 2 Feb 2015 16:54:09 +0100 Subject: [PATCH 008/131] Auth service --- .../services/authentication.client.service.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/modules/users/client/services/authentication.client.service.js b/modules/users/client/services/authentication.client.service.js index 2cbe0d8716..8eadf2c06f 100644 --- a/modules/users/client/services/authentication.client.service.js +++ b/modules/users/client/services/authentication.client.service.js @@ -1,15 +1,12 @@ 'use strict'; // Authentication service for user variables -angular.module('users').factory('Authentication', [ - - function() { - var _this = this; - - _this._data = { - user: window.user +angular.module('users').factory('Authentication', ['$window', + function($window) { + var auth = { + user: $window.user }; - return _this._data; + return auth; } -]); \ No newline at end of file +]); From 12766c1f3e9563034d33a9c8478e2e352beec39a Mon Sep 17 00:00:00 2001 From: Igor Freire Date: Sun, 1 Feb 2015 13:10:41 -0300 Subject: [PATCH 009/131] Adjust profile image URLs on Fb and Twitter strategies For Fb, use the Graph API. For twitter, use the 'bigger' profile image. Larger profile images (like the one provided by Google) could provide more flexibility. --- modules/users/server/config/strategies/facebook.js | 2 +- modules/users/server/config/strategies/twitter.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/users/server/config/strategies/facebook.js b/modules/users/server/config/strategies/facebook.js index 7acdd217cd..dfec829c79 100644 --- a/modules/users/server/config/strategies/facebook.js +++ b/modules/users/server/config/strategies/facebook.js @@ -29,7 +29,7 @@ module.exports = function(config) { lastName: profile.name.familyName, displayName: profile.displayName, email: profile.emails[0].value, - profileImageURL: (profile.photos && profile.photos.length) ? profile.photos[0].value : undefined, + profileImageURL: (profile.id) ? '//graph.facebook.com/' + profile.id + '/picture?type=large' : undefined, provider: 'facebook', providerIdentifierField: 'id', providerData: providerData diff --git a/modules/users/server/config/strategies/twitter.js b/modules/users/server/config/strategies/twitter.js index f58de392c2..b7d46435b7 100644 --- a/modules/users/server/config/strategies/twitter.js +++ b/modules/users/server/config/strategies/twitter.js @@ -26,7 +26,7 @@ module.exports = function(config) { var providerUserProfile = { displayName: profile.displayName, username: profile.username, - profileImageURL: (profile.photos && profile.photos.length) ? profile.photos[0].value : undefined, + profileImageURL: profile.photos[0].value.replace('normal', 'bigger'), provider: 'twitter', providerIdentifierField: 'id_str', providerData: providerData From eced93ff6f030e2d8d619e3a3c0fb2c47870320c Mon Sep 17 00:00:00 2001 From: sylvainlap Date: Sat, 7 Feb 2015 18:40:09 +0100 Subject: [PATCH 010/131] Remove dist files 0.4.0 --- .gitignore | 3 ++- public/dist/application.min.css | 5 ----- public/dist/application.min.js | 25 ------------------------- 3 files changed, 2 insertions(+), 31 deletions(-) delete mode 100644 public/dist/application.min.css delete mode 100644 public/dist/application.min.js diff --git a/.gitignore b/.gitignore index 303af01ca8..a28e051738 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,8 @@ .sass-cache/ npm-debug.log node_modules/ -public/lib +public/lib/ +public/dist/ .bower-*/ .idea/ uploads diff --git a/public/dist/application.min.css b/public/dist/application.min.css deleted file mode 100644 index 034f618cc5..0000000000 --- a/public/dist/application.min.css +++ /dev/null @@ -1,5 +0,0 @@ -.chat-message{margin-top:10px;padding-top:10px}.chat-message:not(:first-child){border-top:1px solid #e7e7e7}.chat-message-details{margin-left:10px}.chat-profile-image{height:28px;width:28px;border-radius:50%} - -.content-navigation{border-color:#fff;color:#e8e8e8}.border{padding:8px;margin:8px;border-color:#fff} -.content{margin-top:50px}.undecorated-link:hover{text-decoration:none}.ng-cloak,.x-ng-cloak,[data-ng-cloak],[ng-cloak],[ng\:cloak],[x-ng-cloak]{display:none!important}.ng-invalid.ng-dirty{border-color:#FA787E}.ng-valid.ng-dirty{border-color:#78FA89}.header-profile-image{opacity:.8;height:28px;width:28px;border-radius:50%;margin-right:5px}.open .header-profile-image,a:hover .header-profile-image{opacity:1}.user-header-dropdown-toggle{padding-top:11px!important;padding-bottom:11px!important} -@media (min-width:992px){.nav-users{position:fixed}}.social-account-container{display:inline-block;position:relative}.btn-remove-account{top:10px;right:10px;position:absolute}.btn-file{position:relative;overflow:hidden}.btn-file input[type=file]{position:absolute;top:0;right:0;min-width:100%;min-height:100%;font-size:100px;text-align:right;filter:alpha(opacity=0);opacity:0;background:#fff;cursor:inherit;display:block}.user-profile-picture{min-height:150px;max-height:150px} \ No newline at end of file diff --git a/public/dist/application.min.js b/public/dist/application.min.js deleted file mode 100644 index b841413081..0000000000 --- a/public/dist/application.min.js +++ /dev/null @@ -1,25 +0,0 @@ -"use strict";var ApplicationConfiguration=function(){var applicationModuleName="mean",applicationModuleVendorDependencies=["ngResource","ngAnimate","ui.router","ui.bootstrap","ui.utils","angularFileUpload"],registerModule=function(moduleName,dependencies){angular.module(moduleName,dependencies||[]),angular.module(applicationModuleName).requires.push(moduleName)};return{applicationModuleName:applicationModuleName,applicationModuleVendorDependencies:applicationModuleVendorDependencies,registerModule:registerModule}}(); -"use strict";angular.module(ApplicationConfiguration.applicationModuleName,ApplicationConfiguration.applicationModuleVendorDependencies),angular.module(ApplicationConfiguration.applicationModuleName).config(["$locationProvider",function($locationProvider){$locationProvider.hashPrefix("!")}]),angular.element(document).ready(function(){"#_=_"===window.location.hash&&(window.location.hash="#!"),angular.bootstrap(document,[ApplicationConfiguration.applicationModuleName])}); -"use strict";ApplicationConfiguration.registerModule("chat"); -"use strict";ApplicationConfiguration.registerModule("core"); -"use strict";ApplicationConfiguration.registerModule("users"); -"use strict";angular.module("chat").run(["Menus",function(Menus){Menus.addMenuItem("topbar",{title:"Chat",state:"chat"})}]); -"use strict";angular.module("chat").config(["$stateProvider",function($stateProvider){$stateProvider.state("chat",{url:"/chat",templateUrl:"modules/chat/views/chat.client.view.html"})}]); -"use strict";angular.module("chat").controller("ChatController",["$scope","Socket",function($scope,Socket){$scope.messages=[],Socket.on("chatMessage",function(message){$scope.messages.unshift(message)}),$scope.sendMessage=function(){var message={text:this.messageText};Socket.emit("chatMessage",message),this.messageText=""},$scope.$on("$destroy",function(){Socket.removeListener("chatMessage")})}]); -"use strict";angular.module("core").config(["$stateProvider","$urlRouterProvider",function($stateProvider,$urlRouterProvider){$urlRouterProvider.otherwise("/"),$stateProvider.state("home",{url:"/",templateUrl:"modules/core/views/home.client.view.html"})}]); -"use strict";angular.module("core").controller("HeaderController",["$scope","Authentication","Menus",function($scope,Authentication,Menus){$scope.authentication=Authentication,$scope.isCollapsed=!1,$scope.menu=Menus.getMenu("topbar"),$scope.toggleCollapsibleMenu=function(){$scope.isCollapsed=!$scope.isCollapsed},$scope.$on("$stateChangeSuccess",function(){$scope.isCollapsed=!1})}]); -"use strict";angular.module("core").controller("HomeController",["$scope","Authentication",function($scope,Authentication){$scope.authentication=Authentication}]); -"use strict";angular.module("core").service("Menus",[function(){this.defaultRoles=["*"],this.menus={};var shouldRender=function(user){if(!user)return this.isPublic;if(~this.roles.indexOf("*"))return!0;for(var userRoleIndex in user.roles)for(var roleIndex in this.roles)if(this.roles[roleIndex]===user.roles[userRoleIndex])return!0;return!1};this.validateMenuExistance=function(menuId){if(menuId&&menuId.length){if(this.menus[menuId])return!0;throw new Error("Menu does not exists")}throw new Error("MenuId was not provided")},this.getMenu=function(menuId){return this.validateMenuExistance(menuId),this.menus[menuId]},this.addMenu=function(menuId,options){return options=options||{},this.menus[menuId]={isPublic:null===options.isPublic||"undefined"==typeof options.isPublic?!0:options.isPublic,roles:options.roles||this.defaultRoles,items:options.items||[],shouldRender:shouldRender},this.menus[menuId]},this.removeMenu=function(menuId){this.validateMenuExistance(menuId),delete this.menus[menuId]},this.addMenuItem=function(menuId,options){if(options=options||{},this.validateMenuExistance(menuId),this.menus[menuId].items.push({title:options.title||"",state:options.state||"",type:options.type||"item","class":options.class,isPublic:null===options.isPublic||"undefined"==typeof options.isPublic?this.menus[menuId].isPublic:options.isPublic,roles:null===options.roles||"undefined"==typeof options.roles?this.menus[menuId].roles:options.roles,position:options.position||0,items:[],shouldRender:shouldRender}),options.items)for(var i in options.items)this.addSubMenuItem(menuId,options.link,options.items[i]);return this.menus[menuId]},this.addSubMenuItem=function(menuId,rootMenuItemURL,options){options=options||{},this.validateMenuExistance(menuId);for(var itemIndex in this.menus[menuId].items)this.menus[menuId].items[itemIndex].link===rootMenuItemURL&&this.menus[menuId].items[itemIndex].items.push({title:options.title||"",state:options.state||"",isPublic:null===options.isPublic||"undefined"==typeof options.isPublic?this.menus[menuId].items[itemIndex].isPublic:options.isPublic,roles:null===options.roles||"undefined"==typeof options.roles?this.menus[menuId].items[itemIndex].roles:options.roles,position:options.position||0,shouldRender:shouldRender});return this.menus[menuId]},this.removeMenuItem=function(menuId,menuItemURL){this.validateMenuExistance(menuId);for(var itemIndex in this.menus[menuId].items)this.menus[menuId].items[itemIndex].link===menuItemURL&&this.menus[menuId].items.splice(itemIndex,1);return this.menus[menuId]},this.removeSubMenuItem=function(menuId,submenuItemURL){this.validateMenuExistance(menuId);for(var itemIndex in this.menus[menuId].items)for(var subitemIndex in this.menus[menuId].items[itemIndex].items)this.menus[menuId].items[itemIndex].items[subitemIndex].link===submenuItemURL&&this.menus[menuId].items[itemIndex].items.splice(subitemIndex,1);return this.menus[menuId]},this.addMenu("topbar",{isPublic:!1})}]); -"use strict";angular.module("core").service("Socket",["Authentication","$state","$timeout",function(Authentication,$state,$timeout){Authentication.user?this.socket=io():$state.go("home"),this.on=function(eventName,callback){this.socket&&this.socket.on(eventName,function(data){$timeout(function(){callback(data)})})},this.emit=function(eventName,data){this.socket&&this.socket.emit(eventName,data)},this.removeListener=function(eventName){this.socket&&this.socket.removeListener(eventName)}}]); -"use strict";angular.module("users").config(["$httpProvider",function($httpProvider){$httpProvider.interceptors.push(["$q","$location","Authentication",function($q,$location,Authentication){return{responseError:function(rejection){switch(rejection.status){case 401:Authentication.user=null,$location.path("signin");break;case 403:}return $q.reject(rejection)}}}])}]); -"use strict";angular.module("users").config(["$stateProvider",function($stateProvider){$stateProvider.state("settings",{"abstract":!0,url:"/settings",templateUrl:"modules/users/views/settings/settings.client.view.html"}).state("settings.profile",{url:"/profile",templateUrl:"modules/users/views/settings/edit-profile.client.view.html"}).state("settings.password",{url:"/password",templateUrl:"modules/users/views/settings/change-password.client.view.html"}).state("settings.accounts",{url:"/accounts",templateUrl:"modules/users/views/settings/manage-social-accounts.client.view.html"}).state("settings.picture",{url:"/picture",templateUrl:"modules/users/views/settings/change-profile-picture.client.view.html"}).state("authentication",{"abstract":!0,url:"/authentication",templateUrl:"modules/users/views/authentication/authentication.client.view.html"}).state("authentication.signup",{url:"/signup",templateUrl:"modules/users/views/authentication/signup.client.view.html"}).state("authentication.signin",{url:"/signin",templateUrl:"modules/users/views/authentication/signin.client.view.html"}).state("password",{"abstract":!0,url:"/password",template:""}).state("password.forgot",{url:"/forgot",templateUrl:"modules/users/views/password/forgot-password.client.view.html"}).state("password.reset",{"abstract":!0,url:"/reset",template:""}).state("password.reset.invalid",{url:"/invalid",templateUrl:"modules/users/views/password/reset-password-invalid.client.view.html"}).state("password.reset.success",{url:"/success",templateUrl:"modules/users/views/password/reset-password-success.client.view.html"}).state("password.reset.form",{url:"/:token",templateUrl:"modules/users/views/password/reset-password.client.view.html"})}]); -"use strict";angular.module("users").controller("AuthenticationController",["$scope","$http","$location","Authentication",function($scope,$http,$location,Authentication){$scope.authentication=Authentication,$scope.authentication.user&&$location.path("/"),$scope.signup=function(){$http.post("/api/auth/signup",$scope.credentials).success(function(response){$scope.authentication.user=response,$location.path("/")}).error(function(response){$scope.error=response.message})},$scope.signin=function(){$http.post("/api/auth/signin",$scope.credentials).success(function(response){$scope.authentication.user=response,$location.path("/")}).error(function(response){$scope.error=response.message})}}]); -"use strict";angular.module("users").controller("PasswordController",["$scope","$stateParams","$http","$location","Authentication",function($scope,$stateParams,$http,$location,Authentication){$scope.authentication=Authentication,$scope.authentication.user&&$location.path("/"),$scope.askForPasswordReset=function(){$scope.success=$scope.error=null,$http.post("/api/auth/forgot",$scope.credentials).success(function(response){$scope.credentials=null,$scope.success=response.message}).error(function(response){$scope.credentials=null,$scope.error=response.message})},$scope.resetUserPassword=function(){$scope.success=$scope.error=null,$http.post("/api/auth/reset/"+$stateParams.token,$scope.passwordDetails).success(function(response){$scope.passwordDetails=null,Authentication.user=response,$location.path("/password/reset/success")}).error(function(response){$scope.error=response.message})}}]); -"use strict";angular.module("users").controller("SettingsController",["$scope","$http","$location","Users","Authentication",function($scope,$http,$location,Users,Authentication){$scope.user=Authentication.user,$scope.user||$location.path("/"),$scope.hasConnectedAdditionalSocialAccounts=function(){for(var i in $scope.user.additionalProvidersData)return!0;return!1},$scope.isConnectedSocialAccount=function(provider){return $scope.user.provider===provider||$scope.user.additionalProvidersData&&$scope.user.additionalProvidersData[provider]},$scope.removeUserSocialAccount=function(provider){$scope.success=$scope.error=null,$http.delete("/api/users/accounts",{params:{provider:provider}}).success(function(response){$scope.success=!0,$scope.user=Authentication.user=response}).error(function(response){$scope.error=response.message})},$scope.updateUserProfile=function(isValid){if(isValid){$scope.success=$scope.error=null;var user=new Users($scope.user);user.$update(function(response){$scope.success=!0,Authentication.user=response},function(response){$scope.error=response.data.message})}else $scope.submitted=!0},$scope.changeUserPassword=function(){$scope.success=$scope.error=null,$http.post("/api/users/password",$scope.passwordDetails).success(function(){$scope.success=!0,$scope.passwordDetails=null}).error(function(response){$scope.error=response.message})}}]); -"use strict";angular.module("users").factory("Authentication",[function(){var _this=this;return _this._data={user:window.user},_this._data}]); -"use strict";angular.module("users").factory("Users",["$resource",function($resource){return $resource("api/users",{},{update:{method:"PUT"}})}]); -"use strict";angular.module("users").controller("ChangePasswordController",["$scope","$http","$location","Users","Authentication",function($scope,$http,$location,Users,Authentication){$scope.user=Authentication.user,$scope.changeUserPassword=function(){$scope.success=$scope.error=null,$http.post("/api/users/password",$scope.passwordDetails).success(function(){$scope.success=!0,$scope.passwordDetails=null}).error(function(response){$scope.error=response.message})}}]); -"use strict";angular.module("users").controller("ChangeProfilePictureController",["$scope","$timeout","$window","Authentication","FileUploader",function($scope,$timeout,$window,Authentication,FileUploader){$scope.user=Authentication.user,$scope.imageURL=$scope.user.profileImageURL,$scope.uploader=new FileUploader({url:"api/users/picture"}),$scope.uploader.filters.push({name:"imageFilter",fn:function(item){var type="|"+item.type.slice(item.type.lastIndexOf("/")+1)+"|";return-1!=="|jpg|png|jpeg|bmp|gif|".indexOf(type)}}),$scope.uploader.onAfterAddingFile=function(fileItem){if($window.FileReader){var fileReader=new FileReader;fileReader.readAsDataURL(fileItem._file),fileReader.onload=function(fileReaderEvent){$timeout(function(){$scope.imageURL=fileReaderEvent.target.result},0)}}},$scope.uploader.onSuccessItem=function(fileItem,response){$scope.success=!0,$scope.user=Authentication.user=response,$scope.cancelUpload()},$scope.uploader.onErrorItem=function(fileItem,response){$scope.cancelUpload(),$scope.error=response.message},$scope.uploadProfilePicture=function(){$scope.success=$scope.error=null,$scope.uploader.uploadAll()},$scope.cancelUpload=function(){$scope.uploader.clearQueue(),$scope.imageURL=$scope.user.profileImageURL}}]); -"use strict";angular.module("users").controller("EditProfileController",["$scope","$http","$location","Users","Authentication",function($scope,$http,$location,Users,Authentication){$scope.user=Authentication.user,$scope.updateUserProfile=function(isValid){if(isValid){$scope.success=$scope.error=null;var user=new Users($scope.user);user.$update(function(response){$scope.success=!0,Authentication.user=response},function(response){$scope.error=response.data.message})}else $scope.submitted=!0}}]); -"use strict";angular.module("users").controller("SocialAccountsController",["$scope","$http","$location","Users","Authentication",function($scope,$http,$location,Users,Authentication){$scope.user=Authentication.user,$scope.hasConnectedAdditionalSocialAccounts=function(){for(var i in $scope.user.additionalProvidersData)return!0;return!1},$scope.isConnectedSocialAccount=function(provider){return $scope.user.provider===provider||$scope.user.additionalProvidersData&&$scope.user.additionalProvidersData[provider]},$scope.removeUserSocialAccount=function(provider){$scope.success=$scope.error=null,$http.delete("/api/users/accounts",{params:{provider:provider}}).success(function(response){$scope.success=!0,$scope.user=Authentication.user=response}).error(function(response){$scope.error=response.message})}}]); -"use strict";angular.module("users").controller("SettingsController",["$scope","$http","$location","Users","Authentication",function($scope,$http,$location,Users,Authentication){$scope.user=Authentication.user,$scope.user||$location.path("/")}]); \ No newline at end of file From 4879a8ea2a5efa4a73439ea502c67ab649c2fb98 Mon Sep 17 00:00:00 2001 From: Christian Berendt Date: Mon, 16 Feb 2015 14:19:56 +0100 Subject: [PATCH 011/131] Remove executable bit when not necessary The executable bit is set for a lot of files where it is not necessary to have the executable bit set. This PR removes the executable bit from those files. --- Procfile | 0 config/lib/express.js | 0 modules/articles/client/articles.client.module.js | 0 modules/articles/client/config/articles.client.routes.js | 0 modules/core/client/config/core.client.routes.js | 0 modules/core/client/core.client.module.js | 0 modules/users/client/config/users.client.routes.js | 0 modules/users/client/users.client.module.js | 0 modules/users/server/controllers/users.server.controller.js | 0 modules/users/server/models/user.server.model.js | 0 package.json | 0 public/humans.txt | 0 public/robots.txt | 0 server.js | 0 14 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 Procfile mode change 100755 => 100644 config/lib/express.js mode change 100755 => 100644 modules/articles/client/articles.client.module.js mode change 100755 => 100644 modules/articles/client/config/articles.client.routes.js mode change 100755 => 100644 modules/core/client/config/core.client.routes.js mode change 100755 => 100644 modules/core/client/core.client.module.js mode change 100755 => 100644 modules/users/client/config/users.client.routes.js mode change 100755 => 100644 modules/users/client/users.client.module.js mode change 100755 => 100644 modules/users/server/controllers/users.server.controller.js mode change 100755 => 100644 modules/users/server/models/user.server.model.js mode change 100755 => 100644 package.json mode change 100755 => 100644 public/humans.txt mode change 100755 => 100644 public/robots.txt mode change 100755 => 100644 server.js diff --git a/Procfile b/Procfile old mode 100755 new mode 100644 diff --git a/config/lib/express.js b/config/lib/express.js old mode 100755 new mode 100644 diff --git a/modules/articles/client/articles.client.module.js b/modules/articles/client/articles.client.module.js old mode 100755 new mode 100644 diff --git a/modules/articles/client/config/articles.client.routes.js b/modules/articles/client/config/articles.client.routes.js old mode 100755 new mode 100644 diff --git a/modules/core/client/config/core.client.routes.js b/modules/core/client/config/core.client.routes.js old mode 100755 new mode 100644 diff --git a/modules/core/client/core.client.module.js b/modules/core/client/core.client.module.js old mode 100755 new mode 100644 diff --git a/modules/users/client/config/users.client.routes.js b/modules/users/client/config/users.client.routes.js old mode 100755 new mode 100644 diff --git a/modules/users/client/users.client.module.js b/modules/users/client/users.client.module.js old mode 100755 new mode 100644 diff --git a/modules/users/server/controllers/users.server.controller.js b/modules/users/server/controllers/users.server.controller.js old mode 100755 new mode 100644 diff --git a/modules/users/server/models/user.server.model.js b/modules/users/server/models/user.server.model.js old mode 100755 new mode 100644 diff --git a/package.json b/package.json old mode 100755 new mode 100644 diff --git a/public/humans.txt b/public/humans.txt old mode 100755 new mode 100644 diff --git a/public/robots.txt b/public/robots.txt old mode 100755 new mode 100644 diff --git a/server.js b/server.js old mode 100755 new mode 100644 From e027f4025b5a7af924f2f6c970ed4dfe42fe0c73 Mon Sep 17 00:00:00 2001 From: Christian Berendt Date: Mon, 16 Feb 2015 21:35:33 +0100 Subject: [PATCH 012/131] Add missing newline at the end of text files On Unix it is common to have a newline at the end of text files. --- .slugignore | 2 +- .travis.yml | 2 +- Dockerfile | 2 +- config/assets/development.js | 2 +- config/assets/test.js | 2 +- config/env/production.js | 2 +- config/env/test.js | 2 +- config/lib/socket.io.js | 2 +- fig.yml | 2 +- modules/articles/client/articles.client.module.js | 2 +- .../articles/client/controllers/articles.client.controller.js | 2 +- modules/articles/server/models/article.server.model.js | 2 +- .../articles/tests/client/articles.client.controller.tests.js | 2 +- modules/chat/client/config/chat.client.routes.js | 2 +- modules/chat/tests/client/chat.client.controller.tests.js | 2 +- modules/chat/tests/e2e/chat.e2e.tests.js | 2 +- modules/chat/tests/server/chat.socket.tests.js | 2 +- modules/core/client/config/core.client.routes.js | 2 +- modules/core/client/controllers/home.client.controller.js | 2 +- modules/core/client/core.client.module.js | 2 +- modules/core/server/controllers/errors.server.controller.js | 2 +- modules/core/server/views/404.server.view.html | 2 +- modules/core/server/views/500.server.view.html | 2 +- modules/core/tests/client/header.client.controller.tests.js | 2 +- modules/core/tests/client/home.client.controller.tests.js | 2 +- .../client/controllers/authentication.client.controller.js | 2 +- modules/users/client/controllers/password.client.controller.js | 2 +- modules/users/client/services/users.client.service.js | 2 +- modules/users/server/config/strategies/local.js | 2 +- modules/users/server/controllers/users.server.controller.js | 2 +- .../controllers/users/users.authorization.server.controller.js | 2 +- .../templates/reset-password-confirm-email.server.view.html | 2 +- .../server/templates/reset-password-email.server.view.html | 2 +- .../tests/client/authentication.client.controller.tests.js | 2 +- 34 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.slugignore b/.slugignore index e4e50baab8..4611d35f44 100644 --- a/.slugignore +++ b/.slugignore @@ -1 +1 @@ -/app/tests \ No newline at end of file +/app/tests diff --git a/.travis.yml b/.travis.yml index 0abd7e4bb2..32edf2495e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,4 +4,4 @@ node_js: env: - NODE_ENV=travis services: - - mongodb \ No newline at end of file + - mongodb diff --git a/Dockerfile b/Dockerfile index 96dc98b13a..de61a99205 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,4 +26,4 @@ ENV NODE_ENV development # Port 3000 for server # Port 35729 for livereload EXPOSE 3000 35729 -CMD ["grunt"] \ No newline at end of file +CMD ["grunt"] diff --git a/config/assets/development.js b/config/assets/development.js index 47a2c6d20a..b521b6577d 100644 --- a/config/assets/development.js +++ b/config/assets/development.js @@ -2,4 +2,4 @@ module.exports = { // Development assets -}; \ No newline at end of file +}; diff --git a/config/assets/test.js b/config/assets/test.js index ecc4cac91d..29ffa89c9e 100644 --- a/config/assets/test.js +++ b/config/assets/test.js @@ -6,4 +6,4 @@ module.exports = { server: ['modules/*/tests/server/**/*.js'], e2e: ['modules/*/tests/e2e/**/*.js'] } -}; \ No newline at end of file +}; diff --git a/config/env/production.js b/config/env/production.js index 3e87554779..01cc6beb87 100644 --- a/config/env/production.js +++ b/config/env/production.js @@ -37,4 +37,4 @@ module.exports = { } } } -}; \ No newline at end of file +}; diff --git a/config/env/test.js b/config/env/test.js index 6f95fdd3f5..dd0c6184cb 100644 --- a/config/env/test.js +++ b/config/env/test.js @@ -41,4 +41,4 @@ module.exports = { } } } -}; \ No newline at end of file +}; diff --git a/config/lib/socket.io.js b/config/lib/socket.io.js index 4e89bdee4b..cddbb34f22 100644 --- a/config/lib/socket.io.js +++ b/config/lib/socket.io.js @@ -58,4 +58,4 @@ module.exports = function(app, db) { }); return server; -}; \ No newline at end of file +}; diff --git a/fig.yml b/fig.yml index 4726a15100..967ac7d1d1 100644 --- a/fig.yml +++ b/fig.yml @@ -9,4 +9,4 @@ web: db: image: mongo ports: - - "27017:27017" \ No newline at end of file + - "27017:27017" diff --git a/modules/articles/client/articles.client.module.js b/modules/articles/client/articles.client.module.js index 7435fc78a8..3c94d0cb57 100644 --- a/modules/articles/client/articles.client.module.js +++ b/modules/articles/client/articles.client.module.js @@ -1,4 +1,4 @@ 'use strict'; // Use Applicaion configuration module to register a new module -ApplicationConfiguration.registerModule('articles'); \ No newline at end of file +ApplicationConfiguration.registerModule('articles'); diff --git a/modules/articles/client/controllers/articles.client.controller.js b/modules/articles/client/controllers/articles.client.controller.js index 364987eb9e..2ea4e2a856 100644 --- a/modules/articles/client/controllers/articles.client.controller.js +++ b/modules/articles/client/controllers/articles.client.controller.js @@ -55,4 +55,4 @@ angular.module('articles').controller('ArticlesController', ['$scope', '$statePa }); }; } -]); \ No newline at end of file +]); diff --git a/modules/articles/server/models/article.server.model.js b/modules/articles/server/models/article.server.model.js index 3f6fd0df1f..f2b89db815 100644 --- a/modules/articles/server/models/article.server.model.js +++ b/modules/articles/server/models/article.server.model.js @@ -31,4 +31,4 @@ var ArticleSchema = new Schema({ } }); -mongoose.model('Article', ArticleSchema); \ No newline at end of file +mongoose.model('Article', ArticleSchema); diff --git a/modules/articles/tests/client/articles.client.controller.tests.js b/modules/articles/tests/client/articles.client.controller.tests.js index 859a967e47..326a9b45c8 100644 --- a/modules/articles/tests/client/articles.client.controller.tests.js +++ b/modules/articles/tests/client/articles.client.controller.tests.js @@ -167,4 +167,4 @@ expect(scope.articles.length).toBe(0); })); }); -}()); \ No newline at end of file +}()); diff --git a/modules/chat/client/config/chat.client.routes.js b/modules/chat/client/config/chat.client.routes.js index a86defc19d..c688ac096f 100644 --- a/modules/chat/client/config/chat.client.routes.js +++ b/modules/chat/client/config/chat.client.routes.js @@ -9,4 +9,4 @@ angular.module('chat').config(['$stateProvider', templateUrl: 'modules/chat/views/chat.client.view.html' }); } -]); \ No newline at end of file +]); diff --git a/modules/chat/tests/client/chat.client.controller.tests.js b/modules/chat/tests/client/chat.client.controller.tests.js index e842322891..8a31f1930f 100644 --- a/modules/chat/tests/client/chat.client.controller.tests.js +++ b/modules/chat/tests/client/chat.client.controller.tests.js @@ -7,4 +7,4 @@ describe('ChatController', function() { // TODO: Add chat client controller tests }); -}()); \ No newline at end of file +}()); diff --git a/modules/chat/tests/e2e/chat.e2e.tests.js b/modules/chat/tests/e2e/chat.e2e.tests.js index 06f5fc6226..d3fd973c29 100644 --- a/modules/chat/tests/e2e/chat.e2e.tests.js +++ b/modules/chat/tests/e2e/chat.e2e.tests.js @@ -5,4 +5,4 @@ */ describe('Chat E2E Tests:', function() { // TODO: Add chat e2e tests -}); \ No newline at end of file +}); diff --git a/modules/chat/tests/server/chat.socket.tests.js b/modules/chat/tests/server/chat.socket.tests.js index 8a82c74ea2..f900e2f626 100644 --- a/modules/chat/tests/server/chat.socket.tests.js +++ b/modules/chat/tests/server/chat.socket.tests.js @@ -5,4 +5,4 @@ */ describe('Chat Socket Tests:', function() { // TODO: Add chat socket tests -}); \ No newline at end of file +}); diff --git a/modules/core/client/config/core.client.routes.js b/modules/core/client/config/core.client.routes.js index 894e3a6caf..7328d0d213 100644 --- a/modules/core/client/config/core.client.routes.js +++ b/modules/core/client/config/core.client.routes.js @@ -13,4 +13,4 @@ angular.module('core').config(['$stateProvider', '$urlRouterProvider', templateUrl: 'modules/core/views/home.client.view.html' }); } -]); \ No newline at end of file +]); diff --git a/modules/core/client/controllers/home.client.controller.js b/modules/core/client/controllers/home.client.controller.js index 086632edd3..ea80b59e60 100644 --- a/modules/core/client/controllers/home.client.controller.js +++ b/modules/core/client/controllers/home.client.controller.js @@ -5,4 +5,4 @@ angular.module('core').controller('HomeController', ['$scope', 'Authentication', // This provides Authentication context. $scope.authentication = Authentication; } -]); \ No newline at end of file +]); diff --git a/modules/core/client/core.client.module.js b/modules/core/client/core.client.module.js index 0edda0458d..b2658634cb 100644 --- a/modules/core/client/core.client.module.js +++ b/modules/core/client/core.client.module.js @@ -1,4 +1,4 @@ 'use strict'; // Use Applicaion configuration module to register a new module -ApplicationConfiguration.registerModule('core'); \ No newline at end of file +ApplicationConfiguration.registerModule('core'); diff --git a/modules/core/server/controllers/errors.server.controller.js b/modules/core/server/controllers/errors.server.controller.js index e4604f8c9c..db81a8ec86 100644 --- a/modules/core/server/controllers/errors.server.controller.js +++ b/modules/core/server/controllers/errors.server.controller.js @@ -39,4 +39,4 @@ exports.getErrorMessage = function(err) { } return message; -}; \ No newline at end of file +}; diff --git a/modules/core/server/views/404.server.view.html b/modules/core/server/views/404.server.view.html index 0074fa456d..404076174c 100644 --- a/modules/core/server/views/404.server.view.html +++ b/modules/core/server/views/404.server.view.html @@ -5,4 +5,4 @@

Page Not Found

 	{{url}} is not a valid path.
 
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/modules/core/server/views/500.server.view.html b/modules/core/server/views/500.server.view.html index 8e6711b72a..cc3b14785c 100644 --- a/modules/core/server/views/500.server.view.html +++ b/modules/core/server/views/500.server.view.html @@ -5,4 +5,4 @@

Server Error

 	{{error}}
 
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/modules/core/tests/client/header.client.controller.tests.js b/modules/core/tests/client/header.client.controller.tests.js index 76ee4fb4e7..b45cc26b37 100644 --- a/modules/core/tests/client/header.client.controller.tests.js +++ b/modules/core/tests/client/header.client.controller.tests.js @@ -21,4 +21,4 @@ expect(scope.authentication).toBeTruthy(); }); }); -})(); \ No newline at end of file +})(); diff --git a/modules/core/tests/client/home.client.controller.tests.js b/modules/core/tests/client/home.client.controller.tests.js index a5b1a566d5..b83b12d736 100644 --- a/modules/core/tests/client/home.client.controller.tests.js +++ b/modules/core/tests/client/home.client.controller.tests.js @@ -21,4 +21,4 @@ expect(scope.authentication).toBeTruthy(); }); }); -})(); \ No newline at end of file +})(); diff --git a/modules/users/client/controllers/authentication.client.controller.js b/modules/users/client/controllers/authentication.client.controller.js index dbc1cc0ea2..a1b349f4f1 100644 --- a/modules/users/client/controllers/authentication.client.controller.js +++ b/modules/users/client/controllers/authentication.client.controller.js @@ -31,4 +31,4 @@ angular.module('users').controller('AuthenticationController', ['$scope', '$http }); }; } -]); \ No newline at end of file +]); diff --git a/modules/users/client/controllers/password.client.controller.js b/modules/users/client/controllers/password.client.controller.js index f5bc915c82..6c95125750 100644 --- a/modules/users/client/controllers/password.client.controller.js +++ b/modules/users/client/controllers/password.client.controller.js @@ -41,4 +41,4 @@ angular.module('users').controller('PasswordController', ['$scope', '$stateParam }); }; } -]); \ No newline at end of file +]); diff --git a/modules/users/client/services/users.client.service.js b/modules/users/client/services/users.client.service.js index eaa6909afe..f8a68eeac2 100644 --- a/modules/users/client/services/users.client.service.js +++ b/modules/users/client/services/users.client.service.js @@ -9,4 +9,4 @@ angular.module('users').factory('Users', ['$resource', } }); } -]); \ No newline at end of file +]); diff --git a/modules/users/server/config/strategies/local.js b/modules/users/server/config/strategies/local.js index 5471473df2..89e655e805 100644 --- a/modules/users/server/config/strategies/local.js +++ b/modules/users/server/config/strategies/local.js @@ -35,4 +35,4 @@ module.exports = function() { }); } )); -}; \ No newline at end of file +}; diff --git a/modules/users/server/controllers/users.server.controller.js b/modules/users/server/controllers/users.server.controller.js index 64e772e9a7..06ef00ea31 100644 --- a/modules/users/server/controllers/users.server.controller.js +++ b/modules/users/server/controllers/users.server.controller.js @@ -13,4 +13,4 @@ module.exports = _.extend( require('./users/users.authorization.server.controller'), require('./users/users.password.server.controller'), require('./users/users.profile.server.controller') -); \ No newline at end of file +); diff --git a/modules/users/server/controllers/users/users.authorization.server.controller.js b/modules/users/server/controllers/users/users.authorization.server.controller.js index efa3b69524..f0d1b9ca5e 100644 --- a/modules/users/server/controllers/users/users.authorization.server.controller.js +++ b/modules/users/server/controllers/users/users.authorization.server.controller.js @@ -51,4 +51,4 @@ exports.hasAuthorization = function(roles) { } }); }; -}; \ No newline at end of file +}; diff --git a/modules/users/server/templates/reset-password-confirm-email.server.view.html b/modules/users/server/templates/reset-password-confirm-email.server.view.html index eec61a672c..baeddbf957 100644 --- a/modules/users/server/templates/reset-password-confirm-email.server.view.html +++ b/modules/users/server/templates/reset-password-confirm-email.server.view.html @@ -13,4 +13,4 @@

The {{appName}} Support Team

- \ No newline at end of file + diff --git a/modules/users/server/templates/reset-password-email.server.view.html b/modules/users/server/templates/reset-password-email.server.view.html index eb73cb83e3..e5934a95af 100644 --- a/modules/users/server/templates/reset-password-email.server.view.html +++ b/modules/users/server/templates/reset-password-email.server.view.html @@ -19,4 +19,4 @@

The {{appName}} Support Team

- \ No newline at end of file + diff --git a/modules/users/tests/client/authentication.client.controller.tests.js b/modules/users/tests/client/authentication.client.controller.tests.js index 5f2d662624..348ec474c6 100644 --- a/modules/users/tests/client/authentication.client.controller.tests.js +++ b/modules/users/tests/client/authentication.client.controller.tests.js @@ -115,4 +115,4 @@ expect(scope.error).toBe('Username already exists'); }); }); -}()); \ No newline at end of file +}()); From 659c8de8017876ca92313a9c42df2a2d936090ec Mon Sep 17 00:00:00 2001 From: Veikko Karsikko Date: Thu, 19 Feb 2015 16:08:48 +0200 Subject: [PATCH 013/131] Change file ignore pattern to match word 'core' instead of chars --- config/assets/default.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/assets/default.js b/config/assets/default.js index 8d614fd4d3..42f1b1062a 100644 --- a/config/assets/default.js +++ b/config/assets/default.js @@ -38,7 +38,7 @@ module.exports = { server: { allJS: ['gruntfile.js', 'server.js', 'config/**/*.js', 'modules/*/server/**/*.js'], models: 'modules/*/server/models/**/*.js', - routes: ['modules/*[!core]/server/routes/**/*.js', 'modules/core/server/routes/**/*.js'], + routes: ['modules/!(core)/server/routes/**/*.js', 'modules/core/server/routes/**/*.js'], sockets: 'modules/*/server/sockets/**/*.js', config: 'modules/*/server/config/*.js', policies: 'modules/*/server/policies/*.js', From 1897ef985b84c741ee166acfa3b4e7c61d55f668 Mon Sep 17 00:00:00 2001 From: Veikko Karsikko Date: Wed, 18 Feb 2015 22:06:00 +0200 Subject: [PATCH 014/131] Wait for async saving and removing --- .../tests/server/article.server.model.tests.js | 6 +++--- .../tests/server/article.server.routes.tests.js | 6 +++--- .../users/tests/server/user.server.model.tests.js | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/modules/articles/tests/server/article.server.model.tests.js b/modules/articles/tests/server/article.server.model.tests.js index ee5fd067b5..b4b76b8a93 100644 --- a/modules/articles/tests/server/article.server.model.tests.js +++ b/modules/articles/tests/server/article.server.model.tests.js @@ -57,8 +57,8 @@ describe('Article Model Unit Tests:', function() { }); afterEach(function(done) { - Article.remove().exec(); - User.remove().exec(); - done(); + Article.remove().exec(function() { + User.remove().exec(done); + }); }); }); diff --git a/modules/articles/tests/server/article.server.routes.tests.js b/modules/articles/tests/server/article.server.routes.tests.js index 04874135ef..c8c35c8718 100644 --- a/modules/articles/tests/server/article.server.routes.tests.js +++ b/modules/articles/tests/server/article.server.routes.tests.js @@ -270,8 +270,8 @@ describe('Article CRUD tests', function() { }); afterEach(function(done) { - User.remove().exec(); - Article.remove().exec(); - done(); + User.remove().exec(function() { + Article.remove().exec(done); + }); }); }); diff --git a/modules/users/tests/server/user.server.model.tests.js b/modules/users/tests/server/user.server.model.tests.js index 63983d1a15..e43848764d 100644 --- a/modules/users/tests/server/user.server.model.tests.js +++ b/modules/users/tests/server/user.server.model.tests.js @@ -52,10 +52,11 @@ describe('User Model Unit Tests:', function() { }); it('should fail to save an existing user again', function(done) { - user.save(); - return user2.save(function(err) { - should.exist(err); - done(); + user.save(function() { + user2.save(function(err) { + should.exist(err); + done(); + }); }); }); @@ -69,7 +70,6 @@ describe('User Model Unit Tests:', function() { }); after(function(done) { - User.remove().exec(); - done(); + User.remove().exec(done); }); }); From 75bb6f2c2c6957fb9b86df69f48a9a61d9e8e8c7 Mon Sep 17 00:00:00 2001 From: dotch Date: Tue, 3 Mar 2015 23:21:47 +0100 Subject: [PATCH 015/131] update gulp-sass to ensure node-0.12 compatibility --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b114c2829b..ab0fd9017d 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "gulp-mocha": "~1.1.1", "gulp-karma": "~0.0.4", "gulp-protractor": "~0.0.11", - "gulp-sass": "~1.2.2", + "gulp-sass": "~1.3.3", "gulp-less": "~1.3.6", "gulp-load-plugins": "~0.7.0", "karma": "~0.12.0", From e954a20e2f59a140828d67f94a70aaaa942b32d7 Mon Sep 17 00:00:00 2001 From: Ilan Biala Date: Thu, 5 Mar 2015 22:01:36 -0500 Subject: [PATCH 016/131] Disconnect method to close DB connection --- config/lib/mongoose.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config/lib/mongoose.js b/config/lib/mongoose.js index 9bb0b74a0e..76cd2e9557 100644 --- a/config/lib/mongoose.js +++ b/config/lib/mongoose.js @@ -34,3 +34,8 @@ module.exports.connect = function(cb) { } }); }; + +module.exports.disconnect = function() { + mongoose.disconnect(); + console.info(chalk.yellow('Disconnected from MongoDB.')); +}; From 6cce3d0952a6531d67429d86d03723cfac872f74 Mon Sep 17 00:00:00 2001 From: Ilan Biala Date: Thu, 5 Mar 2015 22:04:43 -0500 Subject: [PATCH 017/131] Gulp now closes the mongoose connection Fixes #450. --- gulpfile.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index b0a7ceda7f..bcdab553a4 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -112,7 +112,7 @@ gulp.task('less', function () { }); // Connect to MongoDB using the mongoose module -gulp.task('mongoose', function (done) { +gulp.task('openMongoose', function (done) { var mongoose = require('./config/lib/mongoose.js'); mongoose.connect(function(db) { @@ -120,6 +120,12 @@ gulp.task('mongoose', function (done) { }); }); +gulp.task('closeMongoose', function (done) { + var mongoose = require('./config/lib/mongoose.js'); + + mongoose.disconnect(); +}); + // Mocha tests task gulp.task('mocha', function () { return gulp.src(testAssets.tests.server) @@ -168,7 +174,7 @@ gulp.task('build', function(done) { // Run the project tests gulp.task('test', function(done) { - runSequence('env:test', 'mongoose', ['karma', 'mocha'], done); + runSequence('env:test', 'openMongoose', ['karma', 'mocha'], 'closeMongoose', done); }); // Run the project in development mode From 81c2847571108e1c22b34efb233b55c9e2444330 Mon Sep 17 00:00:00 2001 From: Ilan Biala Date: Thu, 5 Mar 2015 22:30:35 -0500 Subject: [PATCH 018/131] Properly track DB disconnect --- config/lib/mongoose.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/config/lib/mongoose.js b/config/lib/mongoose.js index 76cd2e9557..51eb37f76a 100644 --- a/config/lib/mongoose.js +++ b/config/lib/mongoose.js @@ -35,7 +35,9 @@ module.exports.connect = function(cb) { }); }; -module.exports.disconnect = function() { - mongoose.disconnect(); - console.info(chalk.yellow('Disconnected from MongoDB.')); +module.exports.disconnect = function(cb) { + mongoose.disconnect(function(err) { + console.info(chalk.yellow('Disconnected from MongoDB.')); + cb(err); + }); }; From 71c4d4b5557f283ed45a561003fcd7e3e0ab8e77 Mon Sep 17 00:00:00 2001 From: Ilan Biala Date: Thu, 5 Mar 2015 22:32:00 -0500 Subject: [PATCH 019/131] Cleanly track mongoose connection in test task sequence --- gulpfile.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index bcdab553a4..f8fab60612 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -115,15 +115,13 @@ gulp.task('less', function () { gulp.task('openMongoose', function (done) { var mongoose = require('./config/lib/mongoose.js'); - mongoose.connect(function(db) { - done(); - }); + mongoose.connect(done); }); gulp.task('closeMongoose', function (done) { var mongoose = require('./config/lib/mongoose.js'); - mongoose.disconnect(); + mongoose.disconnect(done); }); // Mocha tests task From 6f09033617f444939272e54c0fe07c51239a5bce Mon Sep 17 00:00:00 2001 From: Ilan Biala Date: Thu, 5 Mar 2015 22:57:42 -0500 Subject: [PATCH 020/131] Fix Gulp throwing errors --- gulpfile.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index f8fab60612..3abc1995f3 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -115,13 +115,17 @@ gulp.task('less', function () { gulp.task('openMongoose', function (done) { var mongoose = require('./config/lib/mongoose.js'); - mongoose.connect(done); + mongoose.connect(function() { + done(); + }); }); gulp.task('closeMongoose', function (done) { var mongoose = require('./config/lib/mongoose.js'); - mongoose.disconnect(done); + mongoose.disconnect(function(err) { + done(err); + }); }); // Mocha tests task From 5f57f9d6f29c1d040d1e7b1bb35af0d326ce8505 Mon Sep 17 00:00:00 2001 From: reblace Date: Fri, 6 Mar 2015 14:18:33 -0500 Subject: [PATCH 021/131] #450 Now the mocha task synchronously calls mongoose connect and disconnect. --- gulpfile.js | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 3abc1995f3..8fb826c57f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -111,29 +111,26 @@ gulp.task('less', function () { .pipe(gulp.dest('./modules/')); }); -// Connect to MongoDB using the mongoose module -gulp.task('openMongoose', function (done) { +// Mocha tests task +gulp.task('mocha', function (done) { + // Open mongoose connections var mongoose = require('./config/lib/mongoose.js'); + var error; mongoose.connect(function() { - done(); + gulp.src(testAssets.tests.server) + .pipe(plugins.mocha({ + reporter: 'spec' + })) + .on('error', function (err) { + error = new Error('Mocha tests failed'); + }).on('end', function() { + mongoose.disconnect(function(){ + done(error); + }); + }); }); -}); - -gulp.task('closeMongoose', function (done) { - var mongoose = require('./config/lib/mongoose.js'); - mongoose.disconnect(function(err) { - done(err); - }); -}); - -// Mocha tests task -gulp.task('mocha', function () { - return gulp.src(testAssets.tests.server) - .pipe(plugins.mocha({ - reporter: 'spec' - })); }); // Karma test runner task @@ -143,11 +140,7 @@ gulp.task('karma', function (done) { configFile: 'karma.conf.js', action: 'run', singleRun: true - })) - .on('error', function (err) { - // Make sure failed tests cause gulp to exit non-zero - throw err; - }); + })); }); // Selenium standalone WebDriver update task @@ -176,7 +169,7 @@ gulp.task('build', function(done) { // Run the project tests gulp.task('test', function(done) { - runSequence('env:test', 'openMongoose', ['karma', 'mocha'], 'closeMongoose', done); + runSequence('env:test', ['karma', 'mocha'], done); }); // Run the project in development mode From ffde5e8067aa1513af2b258a85fb41689b575de3 Mon Sep 17 00:00:00 2001 From: reblace Date: Fri, 6 Mar 2015 15:17:58 -0500 Subject: [PATCH 022/131] #450 Use the error reported by mocha. Added some comments explaining what's going on in the mocha task. --- gulpfile.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 8fb826c57f..1821796752 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -115,16 +115,21 @@ gulp.task('less', function () { gulp.task('mocha', function (done) { // Open mongoose connections var mongoose = require('./config/lib/mongoose.js'); - var error; + + // Connect mongoose mongoose.connect(function() { + + // Run the tests gulp.src(testAssets.tests.server) .pipe(plugins.mocha({ reporter: 'spec' })) .on('error', function (err) { - error = new Error('Mocha tests failed'); + // If an error occurs, save it + error = err; }).on('end', function() { + // When the tests are done, disconnect mongoose and pass the error state back to gulp mongoose.disconnect(function(){ done(error); }); From 7876fad50e3e421d347f693aada1f023b3abd9e4 Mon Sep 17 00:00:00 2001 From: dotch Date: Sat, 7 Mar 2015 01:26:49 +0100 Subject: [PATCH 023/131] removed unused gulp-watch dependency --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index ab0fd9017d..43f2674761 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,6 @@ "gulp-rename": "~1.2.0", "gulp-concat": "~2.4.1", "gulp-nodemon": "~1.0.4", - "gulp-watch": "~1.1.0", "gulp-livereload": "~2.1.1", "gulp-jshint": "~1.8.6", "gulp-csslint": "~0.1.5", From b61d6a7c8b508bfc2d175d6d60e7dd460a224700 Mon Sep 17 00:00:00 2001 From: reblace Date: Sat, 7 Mar 2015 11:14:53 -0500 Subject: [PATCH 024/131] #450 Fixing unrelated jshint warnings --- gulpfile.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 1821796752..6e99e0d893 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -130,7 +130,7 @@ gulp.task('mocha', function (done) { error = err; }).on('end', function() { // When the tests are done, disconnect mongoose and pass the error state back to gulp - mongoose.disconnect(function(){ + mongoose.disconnect(function() { done(error); }); }); @@ -155,11 +155,11 @@ gulp.task('webdriver-update', plugins.protractor.webdriver_update); gulp.task('protractor', function () { gulp.src([]) .pipe(plugins.protractor.protractor({ - configFile: "protractor.conf.js" + configFile: 'protractor.conf.js' })) .on('error', function (e) { - throw e - }) + throw e; + }); }); // Lint CSS and JavaScript files. From 9f45e63a20625ccd459256182247f7d669ab4a76 Mon Sep 17 00:00:00 2001 From: reblace Date: Sat, 7 Mar 2015 11:21:47 -0500 Subject: [PATCH 025/131] #450 minor formatting fixes. --- gulpfile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 6e99e0d893..c9b0fe6c12 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -119,7 +119,6 @@ gulp.task('mocha', function (done) { // Connect mongoose mongoose.connect(function() { - // Run the tests gulp.src(testAssets.tests.server) .pipe(plugins.mocha({ @@ -128,7 +127,8 @@ gulp.task('mocha', function (done) { .on('error', function (err) { // If an error occurs, save it error = err; - }).on('end', function() { + }) + .on('end', function() { // When the tests are done, disconnect mongoose and pass the error state back to gulp mongoose.disconnect(function() { done(error); From 08f1750d9450df834a0cf19d303e3c2f9e5da565 Mon Sep 17 00:00:00 2001 From: Rupert Muchembled Date: Wed, 5 Nov 2014 20:33:01 +0000 Subject: [PATCH 026/131] Correctly encode and decode password salt The user password salt should be encoded with Base64 before being saved to the database. The current code adds an unecessary step of converting the result of crypto.randomBytes() (which already returns a SlowBuffer) to a Base64 string and back again to a Buffer, and misses the final step of converting the Buffer's bytes back to a Base64 string. Because of this, the salt stored in the database is garbled. This is inconvenient when manipulating the data in a terminal or text editor. When generating the password hash, the crypto.pbkdf2Sync() method creates a new Buffer directly from the data supplied. Due to the incorrect encoding of the salt, entropy is lost at this step, weakening the security of stored passwords against brute force attacks. --- modules/users/server/models/user.server.model.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/users/server/models/user.server.model.js b/modules/users/server/models/user.server.model.js index d92d29aff9..f4d06c6df2 100644 --- a/modules/users/server/models/user.server.model.js +++ b/modules/users/server/models/user.server.model.js @@ -100,7 +100,7 @@ var UserSchema = new Schema({ */ UserSchema.pre('save', function(next) { if (this.password && this.password.length > 6) { - this.salt = new Buffer(crypto.randomBytes(16).toString('base64'), 'base64'); + this.salt = crypto.randomBytes(16).toString('base64'); this.password = this.hashPassword(this.password); } @@ -112,7 +112,7 @@ UserSchema.pre('save', function(next) { */ UserSchema.methods.hashPassword = function(password) { if (this.salt && password) { - return crypto.pbkdf2Sync(password, this.salt, 10000, 64).toString('base64'); + return crypto.pbkdf2Sync(password, new Buffer(this.salt, 'base64'), 10000, 64).toString('base64'); } else { return password; } From dd0cdfcc40477277f32c7aa28e00048c9663fb41 Mon Sep 17 00:00:00 2001 From: Adam Walz Date: Mon, 9 Mar 2015 15:46:35 -0700 Subject: [PATCH 027/131] Bump glob to version 5.0 --- config/config.js | 63 ++++++++++++++++++++---------------------------- package.json | 2 +- 2 files changed, 27 insertions(+), 38 deletions(-) diff --git a/config/config.js b/config/config.js index 90e7ca0afa..670cd3f341 100644 --- a/config/config.js +++ b/config/config.js @@ -18,7 +18,7 @@ var getGlobbedPaths = function(globPatterns, excludes) { // The output array var output = []; - // If glob pattern is array so we use each pattern in a recursive way, otherwise we use glob + // If glob pattern is array so we use each pattern in a recursive way, otherwise we use glob if (_.isArray(globPatterns)) { globPatterns.forEach(function(globPattern) { output = _.union(output, getGlobbedPaths(globPattern, excludes)); @@ -27,25 +27,20 @@ var getGlobbedPaths = function(globPatterns, excludes) { if (urlRegex.test(globPatterns)) { output.push(globPatterns); } else { - glob(globPatterns, { - sync: true - }, function(err, files) { - if (excludes) { - files = files.map(function(file) { - if (_.isArray(excludes)) { - for (var i in excludes) { - file = file.replace(excludes[i], ''); - } - } else { - file = file.replace(excludes, ''); + var files = glob.sync(globPatterns); + if (excludes) { + files = files.map(function(file) { + if (_.isArray(excludes)) { + for (var i in excludes) { + file = file.replace(excludes[i], ''); } - - return file; - }); - } - - output = _.union(output, files); - }); + } else { + file = file.replace(excludes, ''); + } + return file; + }); + } + output = _.union(output, files); } } @@ -56,26 +51,20 @@ var getGlobbedPaths = function(globPatterns, excludes) { * Validate NODE_ENV existance */ var validateEnvironmentVariable = function() { - glob('./config/env/' + process.env.NODE_ENV + '.js', { - sync: true - }, function(err, environmentFiles) { - console.log(); - - if (!environmentFiles.length) { - if (process.env.NODE_ENV) { - console.error(chalk.red('No configuration file found for "' + process.env.NODE_ENV + '" environment using development instead')); - } else { - console.error(chalk.red('NODE_ENV is not defined! Using default development environment')); - } - - process.env.NODE_ENV = 'development'; + var environmentFiles = glob.sync('./config/env/' + process.env.NODE_ENV + '.js'); + console.log(); + if (!environmentFiles.length) { + if (process.env.NODE_ENV) { + console.error(chalk.red('No configuration file found for "' + process.env.NODE_ENV + '" environment using development instead')); } else { - console.log(chalk.bold('Application loaded using the "' + process.env.NODE_ENV + '" environment configuration')); + console.error(chalk.red('NODE_ENV is not defined! Using default development environment')); } - - // Reset console color - console.log(chalk.white('')); - }); + process.env.NODE_ENV = 'development'; + } else { + console.log(chalk.bold('Application loaded using the "' + process.env.NODE_ENV + '" environment configuration')); + } + // Reset console color + console.log(chalk.white('')); }; /** diff --git a/package.json b/package.json index 43f2674761..76f6944b0b 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "bower": "~1.3.8", "grunt-cli": "~0.1.13", "chalk": "~0.5.1", - "glob": "~4.0.5", + "glob": "~5.0.0", "async": "~0.9.0", "nodemailer": "~1.3.0" }, From e1496dc01cd25f9e77f79db96cc8357087f1bd90 Mon Sep 17 00:00:00 2001 From: Ilan Biala Date: Sun, 8 Mar 2015 11:54:55 -0400 Subject: [PATCH 028/131] Reset password script --- scripts/reset-password.js | 50 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 scripts/reset-password.js diff --git a/scripts/reset-password.js b/scripts/reset-password.js new file mode 100644 index 0000000000..8a268567fa --- /dev/null +++ b/scripts/reset-password.js @@ -0,0 +1,50 @@ +var nodemailer = require('nodemailer'), + mongoose = require('mongoose'), + config = require('../config/config'), + mg = require('../config/lib/mongoose'); + +var transporter = nodemailer.createTransport(config.mailer.options); +var link = 'reset link here'; // PUT reset link here + +mg.connect(function(db) { + var User = mongoose.model('User'); + + User.find().exec(function(err, users) { + if (err) { + throw err; + } + + var email = { + from: 'noreply@xyz.com', + subject: 'Security update' + }; + + for (var i = 0; i < users.length; i++) { + var text = [ + 'Dear ' + users[i].displayName, + '\n', + 'We have updated our password storage systems to be more secure and more efficient, please click the link below to reset your password so you can login in the future.', + link, + '\n', + 'Thanks,', + 'The Team' + ].join('\n'); + + email.to = users[i].email; + email.text = text; + email.html = text; + + transporter.sendMail(email, function(err, info) { + if (err) { + console.log('Error: ', err); + console.log('Could not send email for ', users[i].displayName); + } else { + console.log('Sent reset password email for ', users[i].displayName); + } + }); + } + + console.log('Sent all emails'); + process.exit(0); + }); +}); From 1fa147e372bee3aab891dce7f6e935304371410a Mon Sep 17 00:00:00 2001 From: Ilan Biala Date: Fri, 27 Mar 2015 22:26:03 -0400 Subject: [PATCH 029/131] Update dependencies --- package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 76f6944b0b..06e227cd5b 100644 --- a/package.json +++ b/package.json @@ -18,19 +18,19 @@ "postinstall": "bower install --config.interactive=false" }, "dependencies": { - "express": "~4.10.0", - "express-session": "~1.9.1", + "express": "~4.12.3", + "express-session": "~1.10.4", "serve-favicon": "~2.1.6", - "body-parser": "~1.9.0", + "body-parser": "~1.12.2", "cookie-parser": "~1.3.2", - "compression": "~1.2.0", + "compression": "~1.4.3", "method-override": "~2.3.0", - "morgan": "~1.4.1", + "morgan": "~1.5.2", "multer": "0.1.6", "connect-mongo": "~0.4.1", "connect-flash": "~0.1.1", - "helmet": "~0.4.0", - "consolidate": "~0.10.0", + "helmet": "~0.7.1", + "consolidate": "~0.11.0", "swig": "~1.4.1", "mongoose": "~3.8.8", "passport": "~0.2.0", From dc6f74dec55d3fbfc1c55f43571be679798fa2cd Mon Sep 17 00:00:00 2001 From: Ilan Biala Date: Sat, 28 Mar 2015 19:35:54 -0400 Subject: [PATCH 030/131] Update connect-mongo to support Mongoose 4.0.0 --- config/lib/express.js | 2 +- config/lib/socket.io.js | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/lib/express.js b/config/lib/express.js index 2ad68145b2..b01eb9abf1 100644 --- a/config/lib/express.js +++ b/config/lib/express.js @@ -113,7 +113,7 @@ module.exports.initSession = function (app, db) { resave: true, secret: config.sessionSecret, store: new MongoStore({ - db: db.connection.db, + mongooseConnection: db.connection, collection: config.sessionCollection }) })); diff --git a/config/lib/socket.io.js b/config/lib/socket.io.js index cddbb34f22..3312bb4ad4 100644 --- a/config/lib/socket.io.js +++ b/config/lib/socket.io.js @@ -20,7 +20,7 @@ module.exports = function(app, db) { // Create a MongoDB storage object var mongoStore = new MongoStore({ - db: db.connection.db, + mongooseConnection: db.connection, collection: config.sessionCollection }); diff --git a/package.json b/package.json index 06e227cd5b..aabab5ab59 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "method-override": "~2.3.0", "morgan": "~1.5.2", "multer": "0.1.6", - "connect-mongo": "~0.4.1", + "connect-mongo": "~0.8.0", "connect-flash": "~0.1.1", "helmet": "~0.7.1", "consolidate": "~0.11.0", From 74273dabb3a2971777b4dc143ded848de8a3c441 Mon Sep 17 00:00:00 2001 From: dotch Date: Thu, 2 Apr 2015 03:30:55 +0200 Subject: [PATCH 031/131] return a 404 for not found api, module and lib routes --- config/lib/express.js | 7 ------- modules/core/server/routes/core.server.routes.js | 4 +++- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/config/lib/express.js b/config/lib/express.js index 2ad68145b2..c3f6b953d2 100644 --- a/config/lib/express.js +++ b/config/lib/express.js @@ -177,7 +177,6 @@ module.exports.initModulesServerRoutes = function (app) { * Configure error handling */ module.exports.initErrorRoutes = function (app) { - // Assume 'not found' in the error msgs is a 404. this is somewhat silly, but valid, you can do whatever you like, set properties, use instanceof etc. app.use(function (err, req, res, next) { // If the error object doesn't exists if (!err) return next(); @@ -188,12 +187,6 @@ module.exports.initErrorRoutes = function (app) { // Redirect to error page res.redirect('/server-error'); }); - - // Assume 404 since no middleware responded - app.use(function (req, res) { - // Redirect to not found page - res.redirect('/not-found'); - }); }; /** diff --git a/modules/core/server/routes/core.server.routes.js b/modules/core/server/routes/core.server.routes.js index 33a436641c..d58861fc91 100644 --- a/modules/core/server/routes/core.server.routes.js +++ b/modules/core/server/routes/core.server.routes.js @@ -6,7 +6,9 @@ module.exports = function(app) { // Define error pages app.route('/server-error').get(core.renderServerError); - app.route('/not-found').get(core.renderNotFound); + + // Return a 404 for all undefined api, module or lib routes + app.route('/:url(api|modules|lib)/*').get(core.renderNotFound); // Define application route app.route('/*').get(core.renderIndex); From 238bbe1ad88da908eb5d31da89fd8db468852d4c Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Wed, 8 Apr 2015 11:26:27 +0200 Subject: [PATCH 032/131] Fix headers to HTML5 format Headers were a weird mixture of html5 and xhtml. Now it's pure html5. Use this as doctype if you really want it to be xhtml, but I don't see why would you want that with Angular: ```html ``` --- modules/core/server/views/layout.server.view.html | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/core/server/views/layout.server.view.html b/modules/core/server/views/layout.server.view.html index 576f25a4ac..8df12bdb3f 100644 --- a/modules/core/server/views/layout.server.view.html +++ b/modules/core/server/views/layout.server.view.html @@ -1,15 +1,14 @@ - + {{title}} - - - + + From 0dbab180a90761fd279b106c95c0def0a3306041 Mon Sep 17 00:00:00 2001 From: Edward Sun Date: Mon, 13 Apr 2015 12:08:30 -0400 Subject: [PATCH 033/131] Removed duplicate include for crypto --- .../server/controllers/users/users.password.server.controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/users/server/controllers/users/users.password.server.controller.js b/modules/users/server/controllers/users/users.password.server.controller.js index 9b4ee83812..93fdda0632 100644 --- a/modules/users/server/controllers/users/users.password.server.controller.js +++ b/modules/users/server/controllers/users/users.password.server.controller.js @@ -11,7 +11,6 @@ var _ = require('lodash'), passport = require('passport'), User = mongoose.model('User'), nodemailer = require('nodemailer'), - crypto = require('crypto'), async = require('async'), crypto = require('crypto'); From b7f24c655c123ad28b088d26eb441b9a18d77b1d Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Fri, 17 Apr 2015 17:17:36 +0300 Subject: [PATCH 034/131] Fix typo at gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a28e051738..3ec34150c9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,4 @@ public/dist/ .bower-*/ .idea/ uploads -modules/users/client/img/profie/uploads +modules/users/client/img/profile/uploads From 7f1b46b0d74543854d6172da61a905b859a1f2e0 Mon Sep 17 00:00:00 2001 From: Pedro Rodrigues Date: Thu, 23 Apr 2015 11:09:45 +0100 Subject: [PATCH 035/131] Remove unused passport require --- modules/users/server/routes/users.server.routes.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/users/server/routes/users.server.routes.js b/modules/users/server/routes/users.server.routes.js index 80e528324b..d74a428190 100644 --- a/modules/users/server/routes/users.server.routes.js +++ b/modules/users/server/routes/users.server.routes.js @@ -1,10 +1,5 @@ 'use strict'; -/** - * Module dependencies. - */ -var passport = require('passport'); - module.exports = function(app) { // User Routes var users = require('../controllers/users.server.controller'); From b2d76b82e5843ba30194d45b7bd710ebfe338259 Mon Sep 17 00:00:00 2001 From: Pedro Rodrigues Date: Thu, 23 Apr 2015 17:51:58 +0100 Subject: [PATCH 036/131] Remove more unused requires --- .../articles/server/controllers/articles.server.controller.js | 3 +-- modules/users/server/config/strategies/facebook.js | 1 - modules/users/server/config/strategies/github.js | 1 - modules/users/server/config/strategies/google.js | 1 - modules/users/server/config/strategies/linkedin.js | 1 - modules/users/server/config/strategies/twitter.js | 1 - .../users/users.authentication.server.controller.js | 3 +-- .../controllers/users/users.password.server.controller.js | 4 +--- .../controllers/users/users.profile.server.controller.js | 1 - 9 files changed, 3 insertions(+), 13 deletions(-) diff --git a/modules/articles/server/controllers/articles.server.controller.js b/modules/articles/server/controllers/articles.server.controller.js index d9c9b45e53..a50b1edf75 100644 --- a/modules/articles/server/controllers/articles.server.controller.js +++ b/modules/articles/server/controllers/articles.server.controller.js @@ -3,8 +3,7 @@ /** * Module dependencies. */ -var _ = require('lodash'), - path = require('path'), +var path = require('path'), mongoose = require('mongoose'), Article = mongoose.model('Article'), errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller')); diff --git a/modules/users/server/config/strategies/facebook.js b/modules/users/server/config/strategies/facebook.js index dfec829c79..ed03ce267a 100644 --- a/modules/users/server/config/strategies/facebook.js +++ b/modules/users/server/config/strategies/facebook.js @@ -4,7 +4,6 @@ * Module dependencies. */ var passport = require('passport'), - url = require('url'), FacebookStrategy = require('passport-facebook').Strategy, users = require('../../controllers/users.server.controller'); diff --git a/modules/users/server/config/strategies/github.js b/modules/users/server/config/strategies/github.js index 228fdd6915..e4fa84e99a 100644 --- a/modules/users/server/config/strategies/github.js +++ b/modules/users/server/config/strategies/github.js @@ -4,7 +4,6 @@ * Module dependencies. */ var passport = require('passport'), - url = require('url'), GithubStrategy = require('passport-github').Strategy, users = require('../../controllers/users.server.controller'); diff --git a/modules/users/server/config/strategies/google.js b/modules/users/server/config/strategies/google.js index 1c095763cc..bd4df63f87 100644 --- a/modules/users/server/config/strategies/google.js +++ b/modules/users/server/config/strategies/google.js @@ -4,7 +4,6 @@ * Module dependencies. */ var passport = require('passport'), - url = require('url'), GoogleStrategy = require('passport-google-oauth').OAuth2Strategy, users = require('../../controllers/users.server.controller'); diff --git a/modules/users/server/config/strategies/linkedin.js b/modules/users/server/config/strategies/linkedin.js index a3c97d2440..220269cf48 100644 --- a/modules/users/server/config/strategies/linkedin.js +++ b/modules/users/server/config/strategies/linkedin.js @@ -4,7 +4,6 @@ * Module dependencies. */ var passport = require('passport'), - url = require('url'), LinkedInStrategy = require('passport-linkedin').Strategy, users = require('../../controllers/users.server.controller'); diff --git a/modules/users/server/config/strategies/twitter.js b/modules/users/server/config/strategies/twitter.js index b7d46435b7..6a668ac17d 100644 --- a/modules/users/server/config/strategies/twitter.js +++ b/modules/users/server/config/strategies/twitter.js @@ -4,7 +4,6 @@ * Module dependencies. */ var passport = require('passport'), - url = require('url'), TwitterStrategy = require('passport-twitter').Strategy, users = require('../../controllers/users.server.controller'); diff --git a/modules/users/server/controllers/users/users.authentication.server.controller.js b/modules/users/server/controllers/users/users.authentication.server.controller.js index 2998a836a2..fee8ed79ce 100644 --- a/modules/users/server/controllers/users/users.authentication.server.controller.js +++ b/modules/users/server/controllers/users/users.authentication.server.controller.js @@ -3,8 +3,7 @@ /** * Module dependencies. */ -var _ = require('lodash'), - path = require('path'), +var path = require('path'), errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller')), mongoose = require('mongoose'), passport = require('passport'), diff --git a/modules/users/server/controllers/users/users.password.server.controller.js b/modules/users/server/controllers/users/users.password.server.controller.js index 93fdda0632..653dbfe372 100644 --- a/modules/users/server/controllers/users/users.password.server.controller.js +++ b/modules/users/server/controllers/users/users.password.server.controller.js @@ -3,12 +3,10 @@ /** * Module dependencies. */ -var _ = require('lodash'), - path = require('path'), +var path = require('path'), config = require(path.resolve('./config/config')), errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller')), mongoose = require('mongoose'), - passport = require('passport'), User = mongoose.model('User'), nodemailer = require('nodemailer'), async = require('async'), diff --git a/modules/users/server/controllers/users/users.profile.server.controller.js b/modules/users/server/controllers/users/users.profile.server.controller.js index ec2ad9c999..63e18e8705 100644 --- a/modules/users/server/controllers/users/users.profile.server.controller.js +++ b/modules/users/server/controllers/users/users.profile.server.controller.js @@ -8,7 +8,6 @@ var _ = require('lodash'), path = require('path'), errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller')), mongoose = require('mongoose'), - passport = require('passport'), User = mongoose.model('User'); /** From c800c0a18f44c6b863167ddb8e439549687d06a7 Mon Sep 17 00:00:00 2001 From: Veikko Karsikko Date: Wed, 6 May 2015 13:27:04 +0300 Subject: [PATCH 037/131] Hide email address and remove trailing whitespaces Email address should not be shown to client --- .../controllers/users/users.password.server.controller.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/users/server/controllers/users/users.password.server.controller.js b/modules/users/server/controllers/users/users.password.server.controller.js index 653dbfe372..060bdca28b 100644 --- a/modules/users/server/controllers/users/users.password.server.controller.js +++ b/modules/users/server/controllers/users/users.password.server.controller.js @@ -74,7 +74,7 @@ exports.forgot = function(req, res, next) { smtpTransport.sendMail(mailOptions, function(err) { if (!err) { res.send({ - message: 'An email has been sent to ' + user.email + ' with further instructions.' + message: 'An email has been sent to the provided email with further instructions.' }); } @@ -137,7 +137,7 @@ exports.reset = function(req, res, next) { if (err) { res.status(400).send(err); } else { - // Return authenticated user + // Return authenticated user res.json(user); done(err, user); @@ -174,7 +174,7 @@ exports.reset = function(req, res, next) { subject: 'Your password has been changed', html: emailHTML }; - + smtpTransport.sendMail(mailOptions, function(err) { done(err, 'done'); }); From 3e1d0b5f84ce9556e30eec6fde07e3d0df964b9e Mon Sep 17 00:00:00 2001 From: jloveland Date: Wed, 6 May 2015 00:18:33 -0700 Subject: [PATCH 038/131] adding ssl back in --- .gitignore | 1 + config/env/production.js | 2 + config/lib/express.js | 1 + config/lib/socket.io.js | 99 ++++++++++++++++++++--------------- generate-ssl-certs.sh | 7 --- scripts/generate-ssl-certs.sh | 11 ++++ 6 files changed, 72 insertions(+), 49 deletions(-) delete mode 100644 generate-ssl-certs.sh create mode 100755 scripts/generate-ssl-certs.sh diff --git a/.gitignore b/.gitignore index 3ec34150c9..34f79a3566 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ public/dist/ .idea/ uploads modules/users/client/img/profile/uploads +*.pem diff --git a/config/env/production.js b/config/env/production.js index 01cc6beb87..6d4681547e 100644 --- a/config/env/production.js +++ b/config/env/production.js @@ -1,6 +1,8 @@ 'use strict'; module.exports = { + secure: true, + port: process.env.PORT || 8443, db: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean', facebook: { clientID: process.env.FACEBOOK_ID || 'APP_ID', diff --git a/config/lib/express.js b/config/lib/express.js index b01eb9abf1..f51b718386 100644 --- a/config/lib/express.js +++ b/config/lib/express.js @@ -27,6 +27,7 @@ module.exports.initLocalVariables = function (app) { // Setting application local variables app.locals.title = config.app.title; app.locals.description = config.app.description; + app.locals.secure = config.secure; app.locals.keywords = config.app.keywords; app.locals.googleAnalyticsTrackingID = config.app.googleAnalyticsTrackingID; app.locals.facebookAppId = config.facebook.clientID; diff --git a/config/lib/socket.io.js b/config/lib/socket.io.js index 3312bb4ad4..18adffd0d0 100644 --- a/config/lib/socket.io.js +++ b/config/lib/socket.io.js @@ -2,60 +2,75 @@ // Load the module dependencies var config = require('../config'), - path = require('path'), + path = require('path'), + fs = require('fs'), + http = require('http'), + https = require('https'), cookieParser = require('cookie-parser'), passport = require('passport'), socketio = require('socket.io'), - session = require('express-session'), - MongoStore = require('connect-mongo')(session), - http = require('http'); + session = require('express-session'), + MongoStore = require('connect-mongo')(session); // Define the Socket.io configuration method module.exports = function(app, db) { - // Create a new HTTP server - var server = http.createServer(app); + var server; + if (config.secure === true) { + // Load SSL key and certificate + var privateKey = fs.readFileSync('./config/sslcerts/key.pem', 'utf8'); + var certificate = fs.readFileSync('./config/sslcerts/cert.pem', 'utf8'); + var options = { + key: privateKey, + cert: certificate + }; - // Create a new Socket.io server - var io = socketio.listen(server); + // Create new HTTPS Server + server = https.createServer(options, app); + } else { + // Create a new HTTP server + server = http.createServer(app); + } + // Create a new Socket.io server + var io = socketio.listen(server); - // Create a MongoDB storage object - var mongoStore = new MongoStore({ - mongooseConnection: db.connection, - collection: config.sessionCollection - }); + // Create a MongoDB storage object + var mongoStore = new MongoStore({ + mongooseConnection: db.connection, + collection: config.sessionCollection + }); + + // Intercept Socket.io's handshake request + io.use(function(socket, next) { + // Use the 'cookie-parser' module to parse the request cookies + cookieParser(config.sessionSecret)(socket.request, {}, function(err) { + // Get the session id from the request cookies + var sessionId = socket.request.signedCookies['connect.sid']; - // Intercept Socket.io's handshake request - io.use(function(socket, next) { - // Use the 'cookie-parser' module to parse the request cookies - cookieParser(config.sessionSecret)(socket.request, {}, function(err) { - // Get the session id from the request cookies - var sessionId = socket.request.signedCookies['connect.sid']; - - // Use the mongoStorage instance to get the Express session information - mongoStore.get(sessionId, function(err, session) { - // Set the Socket.io session information - socket.request.session = session; - - // Use Passport to populate the user details - passport.initialize()(socket.request, {}, function() { - passport.session()(socket.request, {}, function() { - if (socket.request.user) { - next(null, true); - } else { - next(new Error('User is not authenticated'), false); - } - }); - }); - }); + // Use the mongoStorage instance to get the Express session information + mongoStore.get(sessionId, function(err, session) { + // Set the Socket.io session information + socket.request.session = session; + + // Use Passport to populate the user details + passport.initialize()(socket.request, {}, function() { + passport.session()(socket.request, {}, function() { + if (socket.request.user) { + next(null, true); + } else { + next(new Error('User is not authenticated'), false); + } + }); }); + }); }); + }); - // Add an event listener to the 'connection' event - io.on('connection', function(socket) { - config.files.server.sockets.forEach(function(socketConfiguration) { - require(path.resolve(socketConfiguration))(io, socket); - }); + // Add an event listener to the 'connection' event + io.on('connection', function(socket) { + config.files.server.sockets.forEach(function(socketConfiguration) { + require(path.resolve(socketConfiguration))(io, socket); }); + }); - return server; + return server; }; diff --git a/generate-ssl-certs.sh b/generate-ssl-certs.sh deleted file mode 100644 index fda44bb0c4..0000000000 --- a/generate-ssl-certs.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -echo "Generating self-signed certificates..." -openssl genrsa -out ./config/sslcerts/key.pem -aes256 1024 -openssl req -new -key ./config/sslcerts/key.pem -out ./config/sslcerts/csr.pem -openssl x509 -req -days 9999 -in ./config/sslcerts/csr.pem -signkey ./config/sslcerts/key.pem -out ./config/sslcerts/cert.pem -rm ./config/sslcerts/csr.pem -chmod 600 ./config/sslcerts/key.pem ./config/sslcerts/cert.pem diff --git a/scripts/generate-ssl-certs.sh b/scripts/generate-ssl-certs.sh new file mode 100755 index 0000000000..02e5d2d504 --- /dev/null +++ b/scripts/generate-ssl-certs.sh @@ -0,0 +1,11 @@ +#!/bin/bash +echo "Generating self-signed certificates..." +openssl genrsa -out ./config/sslcerts/key.pem -aes256 1024 +openssl req -new -key ./config/sslcerts/key.pem -out ./config/sslcerts/csr.pem +openssl x509 -req -days 9999 -in ./config/sslcerts/csr.pem -signkey ./config/sslcerts/key.pem -out ./config/sslcerts/cert.pem +rm ./config/sslcerts/csr.pem +# resolve issue with bad password... +# Error: error:0906A068:PEM routines:PEM_do_header:bad password read +# reference: http://blog.mgechev.com/2014/02/19/create-https-tls-ssl-application-with-express-nodejs/ +openssl rsa -in ./config/sslcerts/key.pem -out ./config/sslcerts/newkey.pem && mv ./config/sslcerts/newkey.pem ./config/sslcerts/key.pem +chmod 0400 ./config/sslcerts/key.pem ./config/sslcerts/cert.pem From fea436f815d72483c3ef29873773f92067a0177c Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Sat, 16 May 2015 14:10:14 +0300 Subject: [PATCH 039/131] Add fonts and svgs to gzip compression pattern I think it's good to have these here "just in case", even the seed project wouldn't have any fonts/svgs. These are just so common filetypes at any project. Thoughts? --- config/lib/express.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/lib/express.js b/config/lib/express.js index b01eb9abf1..129e0fd42b 100644 --- a/config/lib/express.js +++ b/config/lib/express.js @@ -54,7 +54,7 @@ module.exports.initMiddleware = function (app) { // Should be placed before express.static app.use(compress({ filter: function (req, res) { - return (/json|text|javascript|css/).test(res.getHeader('Content-Type')); + return (/json|text|javascript|css|font|svg/).test(res.getHeader('Content-Type')); }, level: 9 })); From f24ce6550ed6c793bf3a2cf6868b921863cc7e67 Mon Sep 17 00:00:00 2001 From: Ilan Biala Date: Sun, 17 May 2015 23:19:05 -0400 Subject: [PATCH 040/131] Ignore ALL .log files at gitignore Closes #523 --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3ec34150c9..90fc5a6fdf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ .DS_Store .nodemonignore .sass-cache/ -npm-debug.log +*.log node_modules/ public/lib/ public/dist/ From ba1a4475e9613f2f9c34c98214fcf6fc32bbb159 Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Mon, 18 May 2015 17:38:30 +0300 Subject: [PATCH 041/131] #501 Handle 404 errors at Express backend at at Angular frontend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `/{api|modules|lib}/*` returns error page when path doesn’t exist (from Express). - `/*` always returns index (from Express), but if `$state` doesn’t exist, Angular redirects to `/not-found` (no 404 status in that case though!) - If `Accept: application/json` header is present without `Accept: text/html`, return error as json. Hence looking at non existing /api/* paths with browser would show html error, but querying them with script would return json. - Slightly prettier 404 error Test: ```bash curl http://localhost:3000/api/notfound -4 -H "Accept: application/json" ``` => json error. ```bash curl http://localhost:3000/api/notfound -4 -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0 .8" ``` => html error (imitates Chrome’s Accept header). Starting point was @dotch’s PL: https://github.com/meanjs/mean/pull/503 And `req.accepts()` idea came from http://stackoverflow.com/a/9802006 --- .../core/client/config/core.client.routes.js | 17 ++++++++----- .../core/client/views/404.client.view.html | 6 +++++ .../controllers/core.server.controller.js | 25 +++++++++++++++---- .../core/server/views/404.server.view.html | 6 +++-- 4 files changed, 41 insertions(+), 13 deletions(-) create mode 100644 modules/core/client/views/404.client.view.html diff --git a/modules/core/client/config/core.client.routes.js b/modules/core/client/config/core.client.routes.js index 7328d0d213..fe5692bd35 100644 --- a/modules/core/client/config/core.client.routes.js +++ b/modules/core/client/config/core.client.routes.js @@ -3,14 +3,19 @@ // Setting up route angular.module('core').config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) { - // Redirect to home view when route not found - $urlRouterProvider.otherwise('/'); + + // Redirect to 404 when route not found + $urlRouterProvider.otherwise('not-found'); // Home state routing $stateProvider. - state('home', { - url: '/', - templateUrl: 'modules/core/views/home.client.view.html' - }); + state('home', { + url: '/', + templateUrl: 'modules/core/views/home.client.view.html' + }). + state('not-found', { + url: '/not-found', + templateUrl: 'modules/core/views/404.client.view.html' + }); } ]); diff --git a/modules/core/client/views/404.client.view.html b/modules/core/client/views/404.client.view.html new file mode 100644 index 0000000000..6e79d29f44 --- /dev/null +++ b/modules/core/client/views/404.client.view.html @@ -0,0 +1,6 @@ +

Page Not Found

+ diff --git a/modules/core/server/controllers/core.server.controller.js b/modules/core/server/controllers/core.server.controller.js index 3b427c38f7..efda2451db 100644 --- a/modules/core/server/controllers/core.server.controller.js +++ b/modules/core/server/controllers/core.server.controller.js @@ -1,7 +1,7 @@ 'use strict'; /** - * Render the main applicaion page + * Render the main application page */ exports.renderIndex = function(req, res) { res.render('modules/core/server/views/index', { @@ -19,10 +19,25 @@ exports.renderServerError = function(req, res) { }; /** - * Render the server not found page + * Render the server not found responses */ exports.renderNotFound = function(req, res) { - res.status(404).render('modules/core/server/views/404', { - url: req.originalUrl - }); + res.status(404); + + // Respond with html page + if (req.accepts('html')) { + res.render('modules/core/server/views/404', { + url: req.originalUrl + }); + return; + } + + // Respond with json to API calls + if (req.accepts('json')) { + res.json({ error: 'Path not found' }); + return; + } + + // Default to plain-text + res.type('txt').send('Path not found'); }; diff --git a/modules/core/server/views/404.server.view.html b/modules/core/server/views/404.server.view.html index 404076174c..94e371926a 100644 --- a/modules/core/server/views/404.server.view.html +++ b/modules/core/server/views/404.server.view.html @@ -2,7 +2,9 @@ {% block content %}

Page Not Found

-
+
+ {% endblock %} From fd170261ec9699c99bbceb3a5c5db1e0fab7da48 Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Mon, 18 May 2015 19:22:56 +0300 Subject: [PATCH 042/131] #501 Use req.format() to content-negotiate correct response --- .../controllers/core.server.controller.js | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/modules/core/server/controllers/core.server.controller.js b/modules/core/server/controllers/core.server.controller.js index efda2451db..5fe58e65df 100644 --- a/modules/core/server/controllers/core.server.controller.js +++ b/modules/core/server/controllers/core.server.controller.js @@ -20,24 +20,23 @@ exports.renderServerError = function(req, res) { /** * Render the server not found responses + * Performs content-negotiation on the Accept HTTP header */ exports.renderNotFound = function(req, res) { - res.status(404); - // Respond with html page - if (req.accepts('html')) { - res.render('modules/core/server/views/404', { - url: req.originalUrl + res + .status(404) + .format({ + 'text/html': function(){ + res.render('modules/core/server/views/404', { + url: req.originalUrl + }); + }, + 'application/json': function(){ + res.json({ error: 'Path not found' }); + }, + 'default': function(){ + res.send('Path not found'); + } }); - return; - } - - // Respond with json to API calls - if (req.accepts('json')) { - res.json({ error: 'Path not found' }); - return; - } - - // Default to plain-text - res.type('txt').send('Path not found'); }; From 7070796c53affb5372d151c2c7699eef28137c72 Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Mon, 18 May 2015 19:25:02 +0300 Subject: [PATCH 043/131] Prettier res.status().format() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (due tabs — my editor has tab-spacing set to 2 so I don’t notice when stuff like this looks crappy) --- modules/core/server/controllers/core.server.controller.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/core/server/controllers/core.server.controller.js b/modules/core/server/controllers/core.server.controller.js index 5fe58e65df..45d5d76468 100644 --- a/modules/core/server/controllers/core.server.controller.js +++ b/modules/core/server/controllers/core.server.controller.js @@ -24,9 +24,7 @@ exports.renderServerError = function(req, res) { */ exports.renderNotFound = function(req, res) { - res - .status(404) - .format({ + res.status(404).format({ 'text/html': function(){ res.render('modules/core/server/views/404', { url: req.originalUrl From e11ffda6e5b5a8156dade30503c04d87233011a2 Mon Sep 17 00:00:00 2001 From: Pedro Rodrigues Date: Thu, 28 May 2015 16:56:49 +0100 Subject: [PATCH 044/131] Add some abstraction to local strategy login error --- modules/users/server/config/strategies/local.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/modules/users/server/config/strategies/local.js b/modules/users/server/config/strategies/local.js index 89e655e805..50ad14f142 100644 --- a/modules/users/server/config/strategies/local.js +++ b/modules/users/server/config/strategies/local.js @@ -20,14 +20,9 @@ module.exports = function() { if (err) { return done(err); } - if (!user) { + if (!user || !user.authenticate(password)) { return done(null, false, { - message: 'Unknown user' - }); - } - if (!user.authenticate(password)) { - return done(null, false, { - message: 'Invalid password' + message: 'Invalid username or password' }); } From fcb6f902932e95861a96331ed651bc69c7482c66 Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Fri, 29 May 2015 00:36:07 +0300 Subject: [PATCH 045/131] Remove un-used hasAuthorization and requiresLogin Looks like these aren't needed now that we have ACL. --- .../users.authorization.server.controller.js | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/modules/users/server/controllers/users/users.authorization.server.controller.js b/modules/users/server/controllers/users/users.authorization.server.controller.js index f0d1b9ca5e..9ad24fc3ef 100644 --- a/modules/users/server/controllers/users/users.authorization.server.controller.js +++ b/modules/users/server/controllers/users/users.authorization.server.controller.js @@ -20,35 +20,3 @@ exports.userByID = function(req, res, next, id) { next(); }); }; - -/** - * Require login routing middleware - */ -exports.requiresLogin = function(req, res, next) { - if (!req.isAuthenticated()) { - return res.status(401).send({ - message: 'User is not logged in' - }); - } - - next(); -}; - -/** - * User authorizations routing middleware - */ -exports.hasAuthorization = function(roles) { - var _this = this; - - return function(req, res, next) { - _this.requiresLogin(req, res, function() { - if (_.intersection(req.user.roles, roles).length) { - return next(); - } else { - return res.status(403).send({ - message: 'User is not authorized' - }); - } - }); - }; -}; From 1ea9f5560055a3b440fc63059f6c1b2c1fd01ece Mon Sep 17 00:00:00 2001 From: Liran Tal Date: Sun, 31 May 2015 11:54:17 +0300 Subject: [PATCH 046/131] porting pull request from master to 0.4.0 branch: Local environment variables to address issue #553 #557 --- .gitignore | 1 + config/config.js | 5 ++++- config/env/local.example.js | 23 +++++++++++++++++++++++ gruntfile.js | 20 +++++++++++++++----- package.json | 1 + 5 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 config/env/local.example.js diff --git a/.gitignore b/.gitignore index 13bbaa740f..43107cc37b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.log node_modules/ public/lib/ +config/env/local.js public/dist/ .bower-*/ .idea/ diff --git a/config/config.js b/config/config.js index 670cd3f341..85458853b9 100644 --- a/config/config.js +++ b/config/config.js @@ -6,6 +6,7 @@ var _ = require('lodash'), chalk = require('chalk'), glob = require('glob'), + fs = require('fs'), path = require('path'); /** @@ -139,7 +140,9 @@ var initGlobalConfig = function() { var environmentConfig = require(path.join(process.cwd(), 'config/env/', process.env.NODE_ENV)) || {}; // Merge config files - var config = _.extend(defaultConfig, environmentConfig); + var envConf = _.extend(defaultConfig, environmentConfig); + + var config = _.merge(envConf, (fs.existsSync(path.join(process.cwd(), 'config/env/local.js')) && require(path.join(process.cwd(), 'config/env/local.js'))) || {}); // Initialize global globbed files initGlobalConfigFiles(config, assets); diff --git a/config/env/local.example.js b/config/env/local.example.js new file mode 100644 index 0000000000..824a29930f --- /dev/null +++ b/config/env/local.example.js @@ -0,0 +1,23 @@ +'use strict'; + +// Rename this file to local.js for having a local configuration variables that +// will not get commited and pushed to remote repositories. +// Use it for your API keys, passwords, etc. + +/* For example: + +module.exports = { + db: { + uri: 'mongodb://localhost/local-dev', + options: { + user: '', + pass: '' + } + }, + facebook: { + clientID: process.env.FACEBOOK_ID || 'APP_ID', + clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', + callbackURL: '/auth/facebook/callback' + } +}; +*/ \ No newline at end of file diff --git a/gruntfile.js b/gruntfile.js index ea4eb0d88e..e8abfe71ab 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -5,7 +5,8 @@ */ var _ = require('lodash'), defaultAssets = require('./config/assets/default'), - testAssets = require('./config/assets/test'); + testAssets = require('./config/assets/test'), + fs = require('fs'); module.exports = function (grunt) { // Project Configuration @@ -190,6 +191,15 @@ module.exports = function (grunt) { args: {} // Target-specific arguments } } + }, + copy: { + localConfig: { + src: 'config/env/local.example.js', + dest: 'config/env/local.js', + filter: function() { + return !fs.existsSync('config/env/local.js'); + } + } } }); @@ -220,14 +230,14 @@ module.exports = function (grunt) { grunt.registerTask('build', ['env:dev', 'lint', 'ngAnnotate', 'uglify', 'cssmin']); // Run the project tests - grunt.registerTask('test', ['env:test', 'mongoose', 'mochaTest', 'karma:unit']); + grunt.registerTask('test', ['env:test', 'copy:localConfig', 'mongoose', 'mochaTest', 'karma:unit']); // Run the project in development mode - grunt.registerTask('default', ['env:dev', 'lint', 'concurrent:default']); + grunt.registerTask('default', ['env:dev', 'lint', 'copy:localConfig', 'concurrent:default']); // Run the project in debug mode - grunt.registerTask('debug', ['env:dev', 'lint', 'concurrent:debug']); + grunt.registerTask('debug', ['env:dev', 'lint', 'copy:localConfig', 'concurrent:debug']); // Run the project in production mode - grunt.registerTask('prod', ['build', 'env:prod', 'concurrent:default']); + grunt.registerTask('prod', ['build', 'env:prod', 'copy:localConfig', 'concurrent:default']); }; diff --git a/package.json b/package.json index aabab5ab59..873e02e09e 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "grunt-contrib-uglify": "~0.6.0", "grunt-contrib-cssmin": "~0.10.0", "grunt-nodemon": "~0.3.0", + "grunt-contrib-copy": "0.8", "grunt-concurrent": "~1.0.0", "grunt-mocha-test": "~0.12.1", "grunt-karma": "~0.9.0", From 10d35d1df97acf3d952dc5739c38da24645b100d Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Tue, 2 Jun 2015 15:36:56 +0300 Subject: [PATCH 047/131] Fix deprecated ExpressJS req.param('provider') >"Deprecated. Use either req.params, req.body or req.query, as applicable." http://expressjs.com/api.html#req.param --- .../controllers/users/users.authentication.server.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/users/server/controllers/users/users.authentication.server.controller.js b/modules/users/server/controllers/users/users.authentication.server.controller.js index fee8ed79ce..c8920bd3a8 100644 --- a/modules/users/server/controllers/users/users.authentication.server.controller.js +++ b/modules/users/server/controllers/users/users.authentication.server.controller.js @@ -177,7 +177,7 @@ exports.saveOAuthUserProfile = function(req, providerUserProfile, done) { */ exports.removeOAuthProvider = function(req, res, next) { var user = req.user; - var provider = req.param('provider'); + var provider = req.params.provider; if (user && provider) { // Delete the additional provider From 2a3516e2a612d9b163062f9ed6af0fe2fc1408b8 Mon Sep 17 00:00:00 2001 From: cdriscol Date: Wed, 3 Jun 2015 21:44:40 -0600 Subject: [PATCH 048/131] Removing target on signin and signup anchors to prevent a complete page reload when changing to those states. --- modules/core/client/views/header.client.view.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/client/views/header.client.view.html b/modules/core/client/views/header.client.view.html index 754e9bc241..dec84ff9a5 100644 --- a/modules/core/client/views/header.client.view.html +++ b/modules/core/client/views/header.client.view.html @@ -25,11 +25,11 @@
diff --git a/modules/users/client/views/settings/manage-social-accounts.client.view.html b/modules/users/client/views/settings/manage-social-accounts.client.view.html index b40b203e6c..6a0b47300e 100644 --- a/modules/users/client/views/settings/manage-social-accounts.client.view.html +++ b/modules/users/client/views/settings/manage-social-accounts.client.view.html @@ -40,5 +40,11 @@

+ diff --git a/modules/users/server/config/strategies/paypal.js b/modules/users/server/config/strategies/paypal.js new file mode 100644 index 0000000000..21271bf975 --- /dev/null +++ b/modules/users/server/config/strategies/paypal.js @@ -0,0 +1,42 @@ +'use strict'; + +/** + * Module dependencies. + */ +var passport = require('passport'), + PayPalStrategy = require('passport-paypal-openidconnect').Strategy, + users = require('../../controllers/users.server.controller'); + +module.exports = function (config) { + passport.use(new PayPalStrategy({ + clientID: config.paypal.clientID, + clientSecret: config.paypal.clientSecret, + callbackURL: config.paypal.callbackURL, + scope: 'openid profile email', + sandbox: config.paypal.sandbox, + passReqToCallback: true + + }, + function(req, accessToken, refreshToken, profile, done) { + // Set the provider data and include tokens + var providerData = profile._json; + providerData.accessToken = accessToken; + providerData.refreshToken = refreshToken; + + // Create the user OAuth profile + var providerUserProfile = { + firstName: profile.name.givenName, + lastName: profile.name.familyName, + displayName: profile.displayName, + email: profile._json.email, + username: profile.username, + provider: 'paypal', + providerIdentifierField: 'user_id', + providerData: providerData + }; + + // Save the user OAuth profile + users.saveOAuthUserProfile(req, providerUserProfile, done); + } + )); +}; diff --git a/modules/users/server/routes/auth.server.routes.js b/modules/users/server/routes/auth.server.routes.js index 6390beed5f..02b6471847 100644 --- a/modules/users/server/routes/auth.server.routes.js +++ b/modules/users/server/routes/auth.server.routes.js @@ -50,4 +50,8 @@ module.exports = function(app) { // Setting the github oauth routes app.route('/api/auth/github').get(passport.authenticate('github')); app.route('/api/auth/github/callback').get(users.oauthCallback('github')); + + // Setting the paypal oauth routes + app.route('/api/auth/paypal').get(passport.authenticate('paypal')); + app.route('/api/auth/paypal/callback').get(users.oauthCallback('paypal')); }; diff --git a/package.json b/package.json index aabab5ab59..9af09003c3 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "passport-linkedin": "~0.1.3", "passport-google-oauth": "~0.1.5", "passport-github": "~0.1.5", + "passport-paypal-openidconnect": "^0.1.1", "acl": "~0.4.4", "socket.io": "~1.1.0", "lodash": "~2.4.1", From 08b2f746d621bb16d487f5e0fe138d269e0c3551 Mon Sep 17 00:00:00 2001 From: Roberto Date: Thu, 2 Jul 2015 08:03:46 -0500 Subject: [PATCH 052/131] Fig changed to Compose --- Dockerfile | 4 ++-- README.md | 25 ++++++++++++------------- config/env/development.js | 2 +- config/env/test.js | 4 ++-- fig.yml => docker-compose.yml | 0 5 files changed, 17 insertions(+), 18 deletions(-) rename fig.yml => docker-compose.yml (100%) diff --git a/Dockerfile b/Dockerfile index de61a99205..d6a654b74b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM dockerfile/nodejs +FROM node:0.10 MAINTAINER Matthias Luebken, matthias@catalyst-zero.com @@ -20,7 +20,7 @@ RUN bower install --config.interactive=false --allow-root # Make everything available for start ADD . /home/mean -# currently only works for development +# Set development environment as default ENV NODE_ENV development # Port 3000 for server diff --git a/README.md b/README.md index dec285d123..7c24689840 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,10 @@ [![Build Status](https://travis-ci.org/meanjs/mean.svg?branch=master)](https://travis-ci.org/meanjs/mean) [![Dependencies Status](https://david-dm.org/meanjs/mean.svg)](https://david-dm.org/meanjs/mean) -MEAN.JS is a full-stack JavaScript open-source solution, which provides a solid starting point for [MongoDB](http://www.mongodb.org/), [Node.js](http://www.nodejs.org/), [Express](http://expressjs.com/), and [AngularJS](http://angularjs.org/) based applications. The idea is to solve the common issues with connecting those frameworks, build a robust framework to support daily development needs, and help developers use better practices while working with popular JavaScript components. +MEAN.JS is a full-stack JavaScript open-source solution, which provides a solid starting point for [MongoDB](http://www.mongodb.org/), [Node.js](http://www.nodejs.org/), [Express](http://expressjs.com/), and [AngularJS](http://angularjs.org/) based applications. The idea is to solve the common issues with connecting those frameworks, build a robust framework to support daily development needs, and help developers use better practices while working with popular JavaScript components. -## Before You Begin -Before you begin we recommend you read about the basic building blocks that assemble a MEAN.JS application: +## Before You Begin +Before you begin we recommend you read about the basic building blocks that assemble a MEAN.JS application: * MongoDB - Go through [MongoDB Official Website](http://mongodb.org/) and proceed to their [Official Manual](http://docs.mongodb.org/manual/), which should help you understand NoSQL and MongoDB better. * Express - The best way to understand express is through its [Official Website](http://expressjs.com/), particularly [The Express Guide](http://expressjs.com/guide.html); you can also go through this [StackOverflow Thread](http://stackoverflow.com/questions/8144214/learning-express-for-node-js) for more resources. * AngularJS - Angular's [Official Website](http://angularjs.org/) is a great starting point. You can also use [Thinkster Popular Guide](http://www.thinkster.io/), and the [Egghead Videos](https://egghead.io/). @@ -30,9 +30,9 @@ $ sudo npm install -g grunt-cli ``` ## Downloading MEAN.JS -There are several ways you can get the MEAN.JS boilerplate: +There are several ways you can get the MEAN.JS boilerplate: -### Yo Generator +### Yo Generator The recommended way would be to use the [Official Yo Generator](http://meanjs.org/generator.html) which will generate the latest stable copy of the MEAN.JS boilerplate and supplies multiple sub-generators to ease your daily development cycles. ### Cloning The GitHub Repository @@ -73,18 +73,18 @@ $ grunt ``` Your application should run on the 3000 port so in your browser just go to [http://localhost:3000](http://localhost:3000) - -That's it! your application should be running by now, to proceed with your development check the other sections in this documentation. + +That's it! your application should be running by now, to proceed with your development check the other sections in this documentation. If you encounter any problem try the Troubleshooting section. ## Development and deployment With Docker -* Install [Docker](http://www.docker.com/) -* Install [Fig](https://github.com/orchardup/fig) +* Install [Docker](https://docs.docker.com/installation/#installation) +* Install [Compose](https://docs.docker.com/compose/install/) -* Local development and testing with fig: +* Local development and testing with compose: ```bash -$ fig up +$ docker-compose up ``` * Local development and testing with just Docker: @@ -92,7 +92,6 @@ $ fig up $ docker build -t mean . $ docker run -p 27017:27017 -d --name db mongo $ docker run -p 3000:3000 --link db:db_1 mean -$ ``` * To enable live reload forward 35729 port and mount /app and /public as volumes: @@ -101,7 +100,7 @@ $ docker run -p 3000:3000 -p 35729:35729 -v /Users/mdl/workspace/mean-stack/mean ``` ## Getting Started With MEAN.JS -You have your application running but there are a lot of stuff to understand, we recommend you'll go over the [Offical Documentation](http://meanjs.org/docs.html). +You have your application running but there are a lot of stuff to understand, we recommend you'll go over the [Offical Documentation](http://meanjs.org/docs.html). In the docs we'll try to explain both general concepts of MEAN components and give you some guidelines to help you improve your development procees. We tried covering as many aspects as possible, and will keep update it by your request, you can also help us develop the documentation better by checking out the *gh-pages* branch of this repository. ## Community diff --git a/config/env/development.js b/config/env/development.js index 310189454b..c7929f5cf2 100644 --- a/config/env/development.js +++ b/config/env/development.js @@ -1,7 +1,7 @@ 'use strict'; module.exports = { - db: 'mongodb://localhost/mean-dev', + db: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean-dev', app: { title: 'MEAN.JS - Development Environment' }, diff --git a/config/env/test.js b/config/env/test.js index dd0c6184cb..d0a4ea97dd 100644 --- a/config/env/test.js +++ b/config/env/test.js @@ -1,8 +1,8 @@ 'use strict'; module.exports = { - db: 'mongodb://localhost/mean-test', - port: 3001, + db: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean-test', + port: process.env.PORT || 3001, app: { title: 'MEAN.JS - Test Environment' }, diff --git a/fig.yml b/docker-compose.yml similarity index 100% rename from fig.yml rename to docker-compose.yml From 834bfd6cf8e67b3adfae0aa0c7a1e7ebcc9287b3 Mon Sep 17 00:00:00 2001 From: Liran Tal Date: Fri, 3 Jul 2015 07:51:08 +0300 Subject: [PATCH 053/131] removing left-overs of merge diff from 0.4 to master --- modules/core/client/views/home.client.view.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/core/client/views/home.client.view.html b/modules/core/client/views/home.client.view.html index 55e6eb5a55..0da70d5525 100644 --- a/modules/core/client/views/home.client.view.html +++ b/modules/core/client/views/home.client.view.html @@ -54,11 +54,7 @@

Express

-<<<<<<< HEAD:public/modules/core/views/home.client.view.html

Express is an app server. Check out The ExpressJS API reference for more information or StackOverflow for more info.

-======= -

Express is an app server. Check out The Express Guide or StackOverflow for more info.

->>>>>>> 0.4.0:modules/core/client/views/home.client.view.html

From 52fe4434290aebc152a561287966701ef9b61410 Mon Sep 17 00:00:00 2001 From: Liran Tal Date: Fri, 3 Jul 2015 10:48:06 +0300 Subject: [PATCH 054/131] fixing err object which isnt present in this check, replacing it with a text message --- .../articles/server/controllers/articles.server.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/articles/server/controllers/articles.server.controller.js b/modules/articles/server/controllers/articles.server.controller.js index d720196d45..8f063226f2 100644 --- a/modules/articles/server/controllers/articles.server.controller.js +++ b/modules/articles/server/controllers/articles.server.controller.js @@ -92,7 +92,7 @@ exports.articleByID = function(req, res, next, id) { if (!mongoose.Types.ObjectId.isValid(id)) { return res.status(400).send({ - message: errorHandler.getErrorMessage(err) + message: 'Article is invalid' }); } From 2be8f71199bdb62885e4f97be1b8b3f5c97819d4 Mon Sep 17 00:00:00 2001 From: Liran Tal Date: Sat, 4 Jul 2015 01:42:11 +0300 Subject: [PATCH 055/131] support for test:server and test:client grunt tasks which were removed from the merge of 0.4.0 into master --- gruntfile.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gruntfile.js b/gruntfile.js index e8abfe71ab..132e209dc3 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -231,6 +231,8 @@ module.exports = function (grunt) { // Run the project tests grunt.registerTask('test', ['env:test', 'copy:localConfig', 'mongoose', 'mochaTest', 'karma:unit']); + grunt.registerTask('test:server', ['env:test', 'mongoose', 'mochaTest']); + grunt.registerTask('test:client', ['env:test', 'mongoose', 'karma:unit']); // Run the project in development mode grunt.registerTask('default', ['env:dev', 'lint', 'copy:localConfig', 'concurrent:default']); From d5b22e35c4418a47a69d387075f6e0b6b7314c6b Mon Sep 17 00:00:00 2001 From: Liran Tal Date: Sun, 5 Jul 2015 01:12:54 +0300 Subject: [PATCH 056/131] addressing missing newlines and node 0.12 version for travis-ci --- .editorconfig | 2 +- .gitignore | 1 - .travis.yml | 1 + modules/core/client/views/header.client.view.html | 2 +- modules/core/server/views/layout.server.view.html | 2 +- modules/users/server/config/users.server.config.js | 2 +- package.json | 2 +- server.js | 2 +- 8 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.editorconfig b/.editorconfig index 5f5f16c70c..c3fe05aacd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -33,4 +33,4 @@ indent_style = tab # Standard at: [Makefile] -indent_style = tab \ No newline at end of file +indent_style = tab diff --git a/.gitignore b/.gitignore index 8eb8d6fd26..142f28da45 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,6 @@ config/env/local.js # ========================== .nodemonignore .sass-cache/ -npm-debug.log node_modules/ public/lib/ app/tests/coverage/ diff --git a/.travis.yml b/.travis.yml index 708607e326..417e3ddb25 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ language: node_js node_js: - "0.10" - "0.11" + - "0.12" env: - NODE_ENV=travis services: diff --git a/modules/core/client/views/header.client.view.html b/modules/core/client/views/header.client.view.html index 1279b2a1a9..dec84ff9a5 100644 --- a/modules/core/client/views/header.client.view.html +++ b/modules/core/client/views/header.client.view.html @@ -59,4 +59,4 @@ -

\ No newline at end of file + diff --git a/modules/core/server/views/layout.server.view.html b/modules/core/server/views/layout.server.view.html index c5b350467c..8fefeb6cc9 100644 --- a/modules/core/server/views/layout.server.view.html +++ b/modules/core/server/views/layout.server.view.html @@ -72,4 +72,4 @@ {% endif %} - \ No newline at end of file + diff --git a/modules/users/server/config/users.server.config.js b/modules/users/server/config/users.server.config.js index 84d20558c9..fa883bd213 100644 --- a/modules/users/server/config/users.server.config.js +++ b/modules/users/server/config/users.server.config.js @@ -34,4 +34,4 @@ module.exports = function(app, db) { // Add passport's middleware app.use(passport.initialize()); app.use(passport.session()); -}; \ No newline at end of file +}; diff --git a/package.json b/package.json index 407d9a5d66..ba7f71c7c9 100644 --- a/package.json +++ b/package.json @@ -95,4 +95,4 @@ "karma-firefox-launcher": "~0.1.3", "karma-phantomjs-launcher": "~0.1.2" } -} \ No newline at end of file +} diff --git a/server.js b/server.js index 4e697a2b8e..fd0a06d30c 100644 --- a/server.js +++ b/server.js @@ -31,4 +31,4 @@ mongoose.connect(function (db) { console.log(chalk.green('HTTPs:\t\t\t\ton')); } console.log('--'); -}); \ No newline at end of file +}); From 75aad2e0ba9d9be2ca3a7de847bf5bafa8f2bfac Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Sun, 5 Jul 2015 23:09:53 +0300 Subject: [PATCH 057/131] Sorting out .gitignore - remove access.log (*.log is enough) - Move local.js config under MEAN.JS - Rename iOS/Apple => OS (this is a mix of windows/osx stuff anyways) --- .gitignore | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 142f28da45..0d3109886d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,9 @@ -# iOS / Apple +# OS # =========== .DS_Store ehthumbs.db Icon? Thumbs.db -config/env/local.js # Node and related ecosystem # ========================== @@ -18,11 +17,10 @@ app/tests/coverage/ # MEAN.js app and assets # ====================== -config/sslcerts/*.pem -access.log public/dist/ uploads modules/users/client/img/profile/uploads +config/env/local.js *.pem # Sublime editor From d4c880b89ce8fba3a85676b45da044b49028fe6d Mon Sep 17 00:00:00 2001 From: Ryan Hutchison Date: Sun, 5 Jul 2015 16:15:06 -0400 Subject: [PATCH 058/131] update dependencies --- package.json | 138 +++++++++++++++++++++++++-------------------------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/package.json b/package.json index ba7f71c7c9..f1517813f7 100644 --- a/package.json +++ b/package.json @@ -18,81 +18,81 @@ "postinstall": "bower install --config.interactive=false" }, "dependencies": { - "express": "~4.12.3", - "express-session": "~1.10.4", - "serve-favicon": "~2.1.6", - "body-parser": "~1.12.2", - "cookie-parser": "~1.3.2", - "compression": "~1.4.3", - "method-override": "~2.3.0", - "morgan": "~1.5.2", - "multer": "0.1.6", - "connect-mongo": "~0.8.0", - "connect-flash": "~0.1.1", - "helmet": "~0.7.1", - "consolidate": "~0.11.0", - "swig": "~1.4.1", - "mongoose": "~3.8.8", - "passport": "~0.2.0", - "passport-local": "~1.0.0", - "passport-facebook": "~1.0.2", - "passport-twitter": "~1.0.2", - "passport-linkedin": "~0.1.3", - "passport-google-oauth": "~0.1.5", - "passport-github": "~0.1.5", "acl": "~0.4.4", - "socket.io": "~1.1.0", - "lodash": "~2.4.1", - "forever": "~0.11.0", - "bower": "~1.3.8", + "async": "^1.3.0", + "body-parser": "^1.13.1", + "bower": "^1.4.1", + "chalk": "^1.1.0", + "compression": "^1.5.0", + "connect-flash": "~0.1.1", + "connect-mongo": "~0.8.1", + "consolidate": "~0.13.1", + "cookie-parser": "^1.3.2", + "express": "^4.13.0", + "express-session": "^1.11.3", + "forever": "~0.14.2", + "glob": "^5.0.13", "grunt-cli": "~0.1.13", - "chalk": "~0.5.1", - "glob": "~5.0.0", - "async": "~0.9.0", - "nodemailer": "~1.3.0" + "helmet": "~0.9.1", + "lodash": "^3.10.0", + "method-override": "^2.3.3", + "mongoose": "^4.0.6", + "morgan": "^1.6.1", + "multer": "0.1.8", + "nodemailer": "^1.4.0", + "passport": "~0.2.2", + "passport-facebook": "^2.0.0", + "passport-github": "~0.1.5", + "passport-google-oauth": "~0.2.0", + "passport-linkedin": "~0.1.3", + "passport-local": "^1.0.0", + "passport-twitter": "^1.0.2", + "serve-favicon": "^2.3.0", + "socket.io": "^1.3.5", + "swig": "^1.4.2" }, "devDependencies": { - "supertest": "~0.14.0", - "should": "~4.1.0", - "grunt-env": "~0.4.1", - "grunt-node-inspector": "~0.1.3", + "grunt-concurrent": "^2.0.0", + "grunt-contrib-copy": "~0.8.0", + "grunt-contrib-csslint": "~0.4.0", + "grunt-contrib-cssmin": "~0.12.3", + "grunt-contrib-jshint": "~0.11.2", + "grunt-contrib-less": "^1.0.1", + "grunt-contrib-sass": "~0.9.2", + "grunt-contrib-uglify": "~0.9.1", "grunt-contrib-watch": "~0.6.1", - "grunt-contrib-jshint": "~0.10.0", - "grunt-contrib-csslint": "^0.3.1", - "grunt-ng-annotate": "~0.4.0", - "grunt-contrib-uglify": "~0.6.0", - "grunt-contrib-cssmin": "~0.10.0", - "grunt-nodemon": "~0.3.0", - "grunt-contrib-copy": "0.8", - "grunt-concurrent": "~1.0.0", - "grunt-mocha-test": "~0.12.1", - "grunt-karma": "~0.9.0", - "grunt-protractor-runner": "1.1.4", - "grunt-contrib-sass": "~0.8.1", - "grunt-contrib-less": "~0.12.0", - "load-grunt-tasks": "~1.0.0", - "gulp": "~3.8.9", - "run-sequence": "~1.0.1", - "gulp-rename": "~1.2.0", - "gulp-concat": "~2.4.1", - "gulp-nodemon": "~1.0.4", - "gulp-livereload": "~2.1.1", - "gulp-jshint": "~1.8.6", + "grunt-env": "~0.4.4", + "grunt-karma": "~0.11.2", + "grunt-mocha-test": "~0.12.7", + "grunt-ng-annotate": "^1.0.1", + "grunt-node-inspector": "~0.2.0", + "grunt-nodemon": "~0.4.0", + "grunt-protractor-runner": "^2.0.0", + "gulp": "^3.9.0", + "gulp-concat": "^2.6.0", "gulp-csslint": "~0.1.5", - "gulp-ng-annotate": "~0.3.3", - "gulp-uglify": "~1.0.1", - "gulp-cssmin": "~0.1.6", - "gulp-mocha": "~1.1.1", + "gulp-cssmin": "~0.1.7", + "gulp-jshint": "^1.11.2", "gulp-karma": "~0.0.4", - "gulp-protractor": "~0.0.11", - "gulp-sass": "~1.3.3", - "gulp-less": "~1.3.6", - "gulp-load-plugins": "~0.7.0", - "karma": "~0.12.0", - "karma-jasmine": "~0.2.1", - "karma-coverage": "~0.2.0", - "karma-chrome-launcher": "~0.1.2", - "karma-firefox-launcher": "~0.1.3", - "karma-phantomjs-launcher": "~0.1.2" + "gulp-less": "^3.0.3", + "gulp-livereload": "^3.8.0", + "gulp-load-plugins": "^1.0.0-rc.1", + "gulp-mocha": "^2.1.2", + "gulp-ng-annotate": "^1.0.0", + "gulp-nodemon": "^2.0.3", + "gulp-protractor": "^1.0.0", + "gulp-rename": "^1.2.2", + "gulp-sass": "^2.0.3", + "gulp-uglify": "^1.2.0", + "karma": "~0.12.37", + "karma-chrome-launcher": "~0.2.0", + "karma-coverage": "~0.4.2", + "karma-firefox-launcher": "~0.1.6", + "karma-jasmine": "~0.3.6", + "karma-phantomjs-launcher": "~0.2.0", + "load-grunt-tasks": "^3.2.0", + "run-sequence": "^1.1.1", + "should": "^7.0.1", + "supertest": "^1.0.1" } } From 7d8cea159bffd5cfe53d24dcb8d0d7af181cc2b1 Mon Sep 17 00:00:00 2001 From: Ryan Hutchison Date: Sun, 5 Jul 2015 17:50:41 -0400 Subject: [PATCH 059/131] load bootstrap (doh) --- config/assets/default.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/assets/default.js b/config/assets/default.js index 42f1b1062a..cb56bf9955 100644 --- a/config/assets/default.js +++ b/config/assets/default.js @@ -8,6 +8,8 @@ module.exports = { 'public/lib/bootstrap/dist/css/bootstrap-theme.css' ], js: [ + 'public/lib/jquery/dist/jquery.js', + 'public/lib/bootstrap/dist/js/bootstrap.js', 'public/lib/angular/angular.js', 'public/lib/angular-resource/angular-resource.js', 'public/lib/angular-animate/angular-animate.js', From 30c916030eb7e4152ea3e8f58bab2c45ed7a7082 Mon Sep 17 00:00:00 2001 From: Ryan Hutchison Date: Sun, 5 Jul 2015 17:51:04 -0400 Subject: [PATCH 060/131] setup dropdown menu --- modules/core/client/views/header.client.view.html | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/modules/core/client/views/header.client.view.html b/modules/core/client/views/header.client.view.html index dec84ff9a5..d63c378367 100644 --- a/modules/core/client/views/header.client.view.html +++ b/modules/core/client/views/header.client.view.html @@ -10,11 +10,8 @@