├── .bowerrc ├── .cfignore ├── .csslintrc ├── .editorconfig ├── .gitignore ├── .jshintrc ├── .slugignore ├── .travis.yml ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Procfile ├── README.md ├── bower.json ├── config ├── assets │ ├── cloud-foundry.js │ ├── default.js │ ├── development.js │ ├── production.js │ └── test.js ├── config.js ├── env │ ├── default.js │ ├── development.js │ ├── heroku_example.js │ ├── local_example.js │ ├── production.js │ └── test.js └── lib │ ├── app.js │ ├── express.js │ ├── reCaptcha.js │ ├── sequelize-connect.js │ ├── sequelize.js │ ├── socket.io.js │ └── winston.js ├── docker-compose.yml ├── gruntfile.js ├── karma.conf.js ├── modules ├── articles │ ├── client │ │ ├── articles.client.module.js │ │ ├── config │ │ │ ├── articles.client.config.js │ │ │ └── articles.client.routes.js │ │ ├── controllers │ │ │ └── articles.client.controller.js │ │ ├── services │ │ │ └── articles.client.service.js │ │ └── views │ │ │ ├── create-article.client.view.html │ │ │ ├── edit-article.client.view.html │ │ │ ├── list-articles.client.view.html │ │ │ └── view-article.client.view.html │ ├── server │ │ ├── config │ │ │ └── articles.server.config.js │ │ ├── controllers │ │ │ └── articles.server.controller.js │ │ ├── models │ │ │ └── articles.server.model.js │ │ ├── policies │ │ │ └── articles.server.policy.js │ │ └── routes │ │ │ └── articles.server.routes.js │ └── tests │ │ ├── client │ │ └── articles.client.controller.tests.js │ │ ├── e2e │ │ └── articles.e2e.tests.js │ │ └── server │ │ ├── article.server.model.tests.js │ │ └── article.server.routes.tests.js ├── chat │ ├── client │ │ ├── chat.client.module.js │ │ ├── config │ │ │ ├── chat.client.config.js │ │ │ └── chat.client.routes.js │ │ ├── controllers │ │ │ └── chat.client.controller.js │ │ ├── scss │ │ │ └── chat.scss │ │ └── views │ │ │ └── chat.client.view.html │ ├── server │ │ └── sockets │ │ │ └── chat.server.socket.config.js │ └── tests │ │ ├── client │ │ └── chat.client.controller.tests.js │ │ ├── e2e │ │ └── chat.e2e.tests.js │ │ └── server │ │ └── chat.socket.tests.js ├── core │ ├── client │ │ ├── app │ │ │ ├── config.js │ │ │ └── init.js │ │ ├── config │ │ │ ├── core-admin.client.menus.js │ │ │ ├── core-admin.client.routes.js │ │ │ ├── core.client.config.js │ │ │ └── core.client.routes.js │ │ ├── controllers │ │ │ ├── contact.client.controller.js │ │ │ ├── header.client.controller.js │ │ │ └── home.client.controller.js │ │ ├── core.client.module.js │ │ ├── directives │ │ │ └── show-errors.client.directives.js │ │ ├── img │ │ │ ├── brand │ │ │ │ ├── favicon.ico │ │ │ │ ├── favicon.png │ │ │ │ └── logo.png │ │ │ ├── loaders │ │ │ │ └── loader.gif │ │ │ └── sample │ │ │ │ ├── angularjs.png │ │ │ │ ├── express.png │ │ │ │ ├── nodejs.png │ │ │ │ └── sequelize.png │ │ ├── scss │ │ │ └── core.scss │ │ ├── services │ │ │ ├── contact.client.service.js │ │ │ ├── interceptors │ │ │ │ └── auth.interceptor.client.service.js │ │ │ ├── menus.client.service.js │ │ │ └── socket.io.client.service.js │ │ └── views │ │ │ ├── 400.client.view.html │ │ │ ├── 403.client.view.html │ │ │ ├── 404.client.view.html │ │ │ ├── contact.client.view.html │ │ │ ├── footer.client.view.html │ │ │ ├── header.client.view.html │ │ │ └── home.client.view.html │ ├── server │ │ ├── controllers │ │ │ ├── core.server.controller.js │ │ │ └── errors.server.controller.js │ │ ├── routes │ │ │ └── core.server.routes.js │ │ ├── templates │ │ │ └── contact-form-email.server.view.html │ │ └── views │ │ │ ├── 404.server.view.html │ │ │ ├── 500.server.view.html │ │ │ ├── index.server.view.html │ │ │ └── layout.server.view.html │ └── tests │ │ └── client │ │ ├── core.client.tests.js │ │ ├── header.client.controller.tests.js │ │ ├── home.client.controller.tests.js │ │ ├── interceptors │ │ └── auth.interceptor.client.tests.js │ │ ├── menus.client.service.tests.js │ │ └── socket.io.client.service.tests.js └── users │ ├── client │ ├── config │ │ ├── user-admin.client.menus.js │ │ ├── user-admin.client.routes.js │ │ ├── user.client.config.js │ │ └── user.client.routes.js │ ├── controllers │ │ ├── admin │ │ │ ├── list-users.client.controller.js │ │ │ └── user.client.controller.js │ │ ├── authentication.client.controller.js │ │ ├── password.client.controller.js │ │ └── settings │ │ │ ├── change-password.client.controller.js │ │ │ ├── change-profile-picture.client.controller.js │ │ │ ├── edit-profile.client.controller.js │ │ │ ├── manage-social-accounts.client.controller.js │ │ │ └── settings.client.controller.js │ ├── directives │ │ ├── password-validator.client.directive.js │ │ ├── password-verify.client.directive.js │ │ └── user.client.directive.js │ ├── img │ │ └── buttons │ │ │ ├── facebook.png │ │ │ ├── github.png │ │ │ ├── google.png │ │ │ ├── linkedin.png │ │ │ ├── paypal.png │ │ │ └── twitter.png │ ├── scss │ │ └── users.scss │ ├── services │ │ ├── authentication.client.service.js │ │ ├── password-validator.client.service.js │ │ └── user.client.service.js │ ├── user.client.module.js │ └── views │ │ ├── admin │ │ ├── edit-user.client.view.html │ │ ├── list-users.client.view.html │ │ └── view-user.client.view.html │ │ ├── authentication │ │ ├── authentication.client.view.html │ │ ├── signin.client.view.html │ │ └── signup.client.view.html │ │ ├── password │ │ ├── forgot-password.client.view.html │ │ ├── reset-password-invalid.client.view.html │ │ ├── reset-password-success.client.view.html │ │ └── reset-password.client.view.html │ │ └── settings │ │ ├── change-password.client.view.html │ │ ├── change-profile-picture.client.view.html │ │ ├── edit-profile.client.view.html │ │ ├── manage-social-accounts.client.view.html │ │ └── settings.client.view.html │ ├── server │ ├── config │ │ ├── strategies │ │ │ ├── facebook.js │ │ │ ├── github.js │ │ │ ├── google.js │ │ │ ├── linkedin.js │ │ │ ├── local.js │ │ │ ├── paypal.js │ │ │ └── twitter.js │ │ └── user.server.config.js │ ├── controllers │ │ ├── admin.server.controller.js │ │ ├── user.server.controller.js │ │ └── users │ │ │ ├── user.authentication.server.controller.js │ │ │ ├── user.authorization.server.controller.js │ │ │ ├── user.password.server.controller.js │ │ │ └── user.profile.server.controller.js │ ├── models │ │ └── user.server.model.js │ ├── policies │ │ └── admin.server.policy.js │ ├── routes │ │ ├── admin.server.routes.js │ │ ├── auth.server.routes.js │ │ └── user.server.routes.js │ └── templates │ │ ├── reset-password-confirm-email.server.view.html │ │ └── reset-password-email.server.view.html │ └── tests │ ├── client │ ├── authentication.client.controller.tests.js │ └── password.client.controller.tests.js │ ├── e2e │ └── users.e2e.tests.js │ └── server │ ├── user.server.model.tests.js │ └── user.server.routes.tests.js ├── package-lock.json ├── package.json ├── protractor.conf.js ├── public ├── humans.txt ├── robots.txt └── uploads │ └── users │ └── profile │ └── .gitignore ├── scripts └── generate-ssl-certs.sh ├── server.js └── test.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "public/lib" 3 | } 4 | -------------------------------------------------------------------------------- /.cfignore: -------------------------------------------------------------------------------- 1 | # List of files and directories to ignore when deploying to Cloud Foundry 2 | .DS_Store 3 | .nodemonignore 4 | .sass-cache/ 5 | npm-debug.log 6 | node_modules/ 7 | public/lib 8 | app/tests/coverage/ 9 | .bower-*/ 10 | .idea/ 11 | -------------------------------------------------------------------------------- /.csslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "adjoining-classes": false, 3 | "box-model": false, 4 | "box-sizing": false, 5 | "floats": false, 6 | "font-sizes": false, 7 | "important": false, 8 | "known-properties": false, 9 | "overqualified-elements": false, 10 | "qualified-headings": false, 11 | "regex-selectors": false, 12 | "unique-headings": false, 13 | "universal-selector": false, 14 | "unqualified-attributes": false 15 | } 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # Howto with your editor: http://editorconfig.org/#download 4 | # Sublime: https://github.com/sindresorhus/editorconfig-sublime 5 | 6 | # top-most EditorConfig file 7 | root = true 8 | 9 | # Unix-style newlines with a newline ending every file 10 | [**] 11 | end_of_line = lf 12 | insert_final_newline = true 13 | 14 | # Standard at: https://github.com/felixge/node-style-guide 15 | [**.js, **.json] 16 | trim_trailing_whitespace = true 17 | indent_style = space 18 | indent_size = 2 19 | quote_type = single 20 | curly_bracket_next_line = false 21 | spaces_around_operators = true 22 | space_after_control_statements = true 23 | space_after_anonymous_functions = true 24 | spaces_in_brackets = false 25 | 26 | # No Standard. Please document a standard if different from .js 27 | [**.yml, **.css] 28 | trim_trailing_whitespace = true 29 | indent_style = tab 30 | 31 | [**.html] 32 | trim_trailing_whitespace = true 33 | indent_style = space 34 | indent_size = 2 35 | 36 | # No standard. Please document a standard if different from .js 37 | [**.md] 38 | indent_style = tab 39 | 40 | # Standard at: 41 | [Makefile] 42 | indent_style = tab 43 | 44 | # The indentation in package.json will always need to be 2 spaces 45 | # https://github.com/npm/npm/issues/4718 46 | [package.json, bower.json] 47 | indent_style = space 48 | indent_size = 2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS 2 | # =========== 3 | .DS_Store 4 | ehthumbs.db 5 | Icon? 6 | Thumbs.db 7 | 8 | # Node and related ecosystem 9 | # ========================== 10 | .nodemonignore 11 | .sass-cache/ 12 | node_modules/ 13 | public/lib/ 14 | app/tests/coverage/ 15 | .bower-*/ 16 | .idea/ 17 | coverage/ 18 | npm-debug.log 19 | 20 | # SEAN.JS app and assets 21 | # ====================== 22 | public/dist/ 23 | public/uploads/users/profile/* 24 | config/env/local.js 25 | *.pem 26 | 27 | modules/*/client/css 28 | 29 | modules/users/server/controllers/users/local/* 30 | modules/users/server/models/local/* 31 | 32 | # Ignoring SEAN.JS Stack's gh-pages branch for documenation 33 | _site/ 34 | 35 | # General 36 | # ======= 37 | *.log 38 | *.csv 39 | *.dat 40 | *.out 41 | *.pid 42 | *.gz 43 | *.tmp 44 | *.bak 45 | *.swp 46 | logs/ 47 | build/ 48 | 49 | # Sublime editor 50 | # ============== 51 | .sublime-project 52 | *.sublime-project 53 | *.sublime-workspace 54 | 55 | # Eclipse project files 56 | # ===================== 57 | .project 58 | .settings/ 59 | .*.md.html 60 | .metadata 61 | *~.nib 62 | local.properties 63 | 64 | # IntelliJ 65 | # ======== 66 | *.iml 67 | 68 | # Cloud9 IDE 69 | # ========= 70 | .c9/ 71 | 72 | # Visual Studio 73 | # ========= 74 | *.suo 75 | *.ntvs* 76 | *.njsproj 77 | *.sln 78 | /nbproject 79 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, // Enable globals available when code is running inside of the NodeJS runtime environment. 3 | "mocha": true, // Enable globals available when code is running inside of the Mocha tests. 4 | "jasmine": true, // Enable globals available when code is running inside of the Jasmine tests. 5 | "browser": true, // Standard browser globals e.g. `window`, `document`. 6 | "esnext": true, // Allow ES.next specific features such as `const` and `let`. 7 | "bitwise": false, // Prohibit bitwise operators (&, |, ^, etc.). 8 | "curly": false, // Require {} for every new block or scope. 9 | "eqeqeq": true, // Require triple equals i.e. `===`. 10 | "latedef": "nofunc", // Prohibit variable use before definition. 11 | "noarg": true, // Prohibit use of `arguments.caller` and `arguments.callee`. 12 | "undef": true, // Require all non-global variables be declared before they are used. 13 | "unused": false, // Warn unused variables. 14 | "strict": true, // Require `use strict` pragma in every file. 15 | "globals": { // Globals variables. 16 | "angular": true, 17 | "io": true, 18 | "ApplicationConfiguration": true, 19 | "ga": true, 20 | "grecaptcha": true 21 | }, 22 | "predef": [ // Extra globals. 23 | "inject", 24 | "by", 25 | "browser", 26 | "element" 27 | ], 28 | "devel": true // Allow development statements e.g. `console.log();`. 29 | } 30 | -------------------------------------------------------------------------------- /.slugignore: -------------------------------------------------------------------------------- 1 | /app/tests 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - '6.10' 5 | - '7' 6 | - '8' 7 | # NodeJS v4 requires gcc 4.8 8 | env: 9 | - NODE_ENV=travis CXX="g++-4.8" CC="gcc-4.8" 10 | 11 | matrix: 12 | allow_failures: 13 | - node_js: '8' 14 | 15 | services: 16 | - redis-server 17 | - postgresql 18 | # gcc 4.8 requires ubuntu-toolchain-r-test 19 | 20 | dist: trusty 21 | 22 | addons: 23 | postgresql: "9.6" 24 | apt: 25 | sources: 26 | - ubuntu-toolchain-r-test 27 | packages: 28 | - g++-4.8 29 | - gcc-4.8 30 | - clang 31 | before_install: 32 | - "mysql -e 'create database seanjs_dev;'" 33 | - "psql -c 'create database seanjs_dev;' -U postgres" 34 | - "psql seanjs_dev -c 'create extension postgis;' -U postgres" 35 | - gem update --system 36 | - gem install sass --version "=3.3.7" 37 | - npm i nsp -g 38 | - npm install protractor 39 | - "export DISPLAY=:99.0" 40 | - "sh -e /etc/init.d/xvfb start" 41 | - 'node_modules/protractor/bin/webdriver-manager update' 42 | - 'node_modules/protractor/bin/webdriver-manager start 2>&1 &' 43 | - sleep 3 44 | after_script: 45 | - nsp audit-package 46 | - grunt coverage 47 | notifications: 48 | webhooks: 49 | urls: 50 | - $GITTER_IM_URL 51 | on_success: change # options: [always|never|change] default: always 52 | on_failure: always # options: [always|never|change] default: always 53 | on_start: never # options: [always|never|change] default: always 54 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Support and contributions from the open source community are essential for keeping 4 | SEAN.JS up to date and always improving! There are a few guidelines that we need 5 | contributors to follow to keep the project consistent, as well as allow us to keep 6 | maintaining SEAN.JS in a reasonable amount of time. 7 | 8 | ## Creating an Issue 9 | 10 | Before you create a new Issue: 11 | * Check the [Issues](https://github.com/seanjs-stack/seanjs/issues) on Github to ensure one doesn't already exist. 12 | * Clearly describe the issue, including the steps to reproduce the issue. 13 | * If it's a new feature, enhancement, or restructure, Explain your reasoning on why you think it should be added, as well as a particular use case. 14 | 15 | ## Making Changes 16 | 17 | * Create a topic branch from the master branch. 18 | * Check for unnecessary whitespace / changes with `git diff --check` before committing. 19 | * Also check that your code is formatted properly with spaces (hint: Use [.editorconfig](http://editorconfig.org/)) 20 | * **And would strongly suggest to use [atom-beautify ](https://atom.io/packages/atom-beautify)** 21 | * Keep git commit messages clear and appropriate 22 | * If possible, please "squash" your commits to as few commits as possible/reasonable such as one commit for implementation, one for tests, and one for documentation before finally squashing to one commit when getting the LGTM from a collaborator. 23 | * Make Sure you have added any tests necessary to test your code. 24 | * Run __all__ the tests to ensure nothing else was accidently broken. 25 | * Don't rely on the existing tests to see if you've broken code elsewhere; test the changes you made in a browser too! 26 | * Update the Documentation to go along with any changes in functionality / improvements in a separate pull request against the gh-pages branch. 27 | 28 | ## Submitting the Pull Request 29 | 30 | * Push your changes to your topic branch on your fork of the repo. 31 | * Submit a pull request from your topic branch to the master branch on the SEAN.JS repository. 32 | * Be sure to tag any issues your pull request is taking care of / contributing to. 33 | * By adding "Closes #xyz" to a commit message will auto close the issue once the pull request is merged in. 34 | * Small changes are usually accepted and merged in within a week (provided that 2 collaborators give the okay) 35 | * Larger changes usually spark further discussion and possible changes prior to being merged in. 36 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:4.2.3 2 | 3 | # Install gem sass for grunt-contrib-sass 4 | RUN apt-get update -qq && apt-get install -y build-essential 5 | RUN apt-get install -y ruby 6 | RUN apt-get install -y libssl-dev ruby 7 | RUN gem install sass 8 | 9 | WORKDIR /home/seanjs 10 | 11 | # Install SEAN.JS Prerequisites 12 | RUN npm install -g grunt-cli && npm install -g bower 13 | 14 | # Install SEAN.JS packages 15 | ADD package.json /home/seanjs/package.json 16 | #RUN npm install 17 | 18 | # Manually trigger bower 19 | ADD .bowerrc /home/seanjs/.bowerrc 20 | ADD bower.json /home/seanjs/bower.json 21 | RUN bower install --config.interactive=false --allow-root 22 | 23 | # Make everything available for start 24 | ADD . /home/seanjs 25 | 26 | # Set development environment as default 27 | ENV NODE_ENV development 28 | ENV NODE_HOST 192.168.99.100 29 | 30 | # Environment for redis 31 | ENV REDIS_HOST 192.168.99.100 32 | 33 | # Environment for postgres database 34 | ENV DB_HOST 192.168.99.100 35 | ENV DB_PORT 5432 36 | ENV DB_NAME seanjs_dev 37 | ENV DB_USERNAME postgres 38 | ENV DB_PASSWORD postgres 39 | ENV DB_DIALECT postgres 40 | 41 | # Port 5432 for postgres 42 | # Port 3000 for server 43 | # Port 35729 for livereload 44 | 45 | EXPOSE 5432 3000 35729 46 | 47 | CMD ["grunt"] 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 SEAN.JS Stack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: ./node_modules/.bin/forever -m 5 server.js 2 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seanjs", 3 | "description": "Full-Stack Javascript with SequelizeJS, ExpressJS, AngularJS, and NodeJS", 4 | "homepage": "http://seanjs.org", 5 | "license": "MIT", 6 | "dependencies": { 7 | "bootstrap": "~3", 8 | "angular": "~1.3", 9 | "angular-resource": "~1.3", 10 | "angular-animate": "~1.3", 11 | "angular-mocks": "~1.3", 12 | "angular-bootstrap": "~0.13", 13 | "angular-ui-utils": "bower", 14 | "angular-ui-router": "~0.2", 15 | "angular-file-upload": "1.1.5", 16 | "angular-messages": "~1.3.17", 17 | "owasp-password-strength-test": "~1.3.0" 18 | }, 19 | "resolutions": { 20 | "angular": "~1.3" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /config/assets/cloud-foundry.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = {}; -------------------------------------------------------------------------------- /config/assets/default.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | client: { 5 | lib: { 6 | css: [ 7 | 'public/lib/bootstrap/dist/css/bootstrap.css', 8 | 'public/lib/bootstrap/dist/css/bootstrap-theme.css' 9 | ], 10 | js: [ 11 | 'public/lib/angular/angular.js', 12 | 'public/lib/angular-resource/angular-resource.js', 13 | 'public/lib/angular-animate/angular-animate.js', 14 | 'public/lib/angular-messages/angular-messages.js', 15 | 'public/lib/angular-ui-router/release/angular-ui-router.js', 16 | 'public/lib/angular-ui-utils/ui-utils.js', 17 | 'public/lib/angular-bootstrap/ui-bootstrap-tpls.js', 18 | 'public/lib/angular-file-upload/angular-file-upload.js', 19 | 'public/lib/owasp-password-strength-test/owasp-password-strength-test.js', 20 | 'https://www.google.com/recaptcha/api.js' 21 | ], 22 | tests: ['public/lib/angular-mocks/angular-mocks.js'] 23 | }, 24 | css: [ 25 | 'modules/*/client/css/*.css' 26 | ], 27 | less: [ 28 | 'modules/*/client/less/*.less' 29 | ], 30 | sass: [ 31 | 'modules/*/client/scss/*.scss' 32 | ], 33 | js: [ 34 | 'modules/core/client/app/config.js', 35 | 'modules/core/client/app/init.js', 36 | 'modules/*/client/*.js', 37 | 'modules/*/client/**/*.js' 38 | ], 39 | views: ['modules/*/client/views/**/*.html'], 40 | templates: ['build/templates.js'] 41 | }, 42 | server: { 43 | gruntConfig: 'gruntfile.js', 44 | gulpConfig: 'gulpfile.js', 45 | allJS: ['server.js', 'config/**/*.js', 'modules/*/server/**/*.js'], 46 | models: 'modules/*/server/models/**/*.js', 47 | routes: ['modules/!(core)/server/routes/**/*.js', 'modules/core/server/routes/**/*.js'], 48 | sockets: 'modules/*/server/sockets/**/*.js', 49 | config: 'modules/*/server/config/*.js', 50 | policies: 'modules/*/server/policies/*.js', 51 | views: 'modules/*/server/views/*.html' 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /config/assets/development.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | // Development assets 5 | }; -------------------------------------------------------------------------------- /config/assets/production.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | client: { 5 | lib: { 6 | css: [ 7 | 'public/lib/bootstrap/dist/css/bootstrap.min.css', 8 | 'public/lib/bootstrap/dist/css/bootstrap-theme.min.css', 9 | ], 10 | js: [ 11 | 'public/lib/angular/angular.min.js', 12 | 'public/lib/angular-resource/angular-resource.min.js', 13 | 'public/lib/angular-animate/angular-animate.min.js', 14 | 'public/lib/angular-messages/angular-messages.min.js', 15 | 'public/lib/angular-ui-router/release/angular-ui-router.min.js', 16 | 'public/lib/angular-ui-utils/ui-utils.min.js', 17 | 'public/lib/angular-bootstrap/ui-bootstrap-tpls.min.js', 18 | 'public/lib/angular-file-upload/angular-file-upload.min.js', 19 | 'public/lib/owasp-password-strength-test/owasp-password-strength-test.js' 20 | ] 21 | }, 22 | css: 'public/dist/application.min.css', 23 | js: 'public/dist/application.min.js' 24 | } 25 | }; -------------------------------------------------------------------------------- /config/assets/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | tests: { 5 | client: ['modules/*/tests/client/**/*.js'], 6 | server: ['modules/*/tests/server/**/*.js'], 7 | e2e: ['modules/*/tests/e2e/**/*.js'] 8 | } 9 | }; -------------------------------------------------------------------------------- /config/env/default.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | app: { 5 | title: 'SEAN.JS', 6 | description: 'Full-Stack Javascript with SequelizeJS, ExpressJS, AngularJS, and Node.js', 7 | keywords: 'sequelizejs, expressjs, angularjs, nodejs, postgresql, mysql, sqlite3, passport, redis, socket.io', 8 | googleAnalyticsTrackingID: process.env.GOOGLE_ANALYTICS_TRACKING_ID || '', 9 | reCaptchaSecret: process.env.RECAPTCHA_SECRET || '' 10 | }, 11 | port: process.env.PORT || 3000, 12 | templateEngine: 'swig', 13 | // Session Cookie settings 14 | sessionCookie: { 15 | // session expiration is set by default to 24 hours 16 | maxAge: 24 * (60 * 60 * 1000), 17 | // httpOnly flag makes sure the cookie is only accessed 18 | // through the HTTP protocol and not JS/browser 19 | httpOnly: true, 20 | // secure cookie should be turned to true to provide additional 21 | // layer of security so that the cookie is set only when working 22 | // in HTTPS mode. 23 | secure: Boolean(process.env.ssl) || true 24 | }, 25 | // sessionSecret should be changed for security measures and concerns 26 | sessionSecret: 'SEANJS', 27 | // sessionKey is set to the generic sessionId key used by PHP applications 28 | // for obsecurity reasons 29 | sessionKey: 'sessionId', 30 | sessionCollection: 'sessions', 31 | logo: 'modules/core/client/img/brand/logo.png', 32 | favicon: 'modules/core/client/img/brand/favicon.ico' 33 | }; -------------------------------------------------------------------------------- /config/env/development.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var defaultEnvConfig = require('./default'); 4 | 5 | module.exports = { 6 | secure: { 7 | ssl: Boolean(process.env.ssl) || false, 8 | privateKey: './config/sslcerts/key.pem', 9 | certificate: './config/sslcerts/cert.pem' 10 | }, 11 | db: { 12 | name: process.env.DB_NAME || "seanjs_dev", 13 | host: process.env.DB_HOST || "localhost", 14 | port: process.env.DB_PORT || 5432, 15 | username: process.env.DB_USERNAME || "postgres", 16 | password: process.env.DB_PASSWORD || "postgres", 17 | dialect: process.env.DB_DIALECT || "postgres", //mysql, postgres, sqlite3,... 18 | enableSequelizeLog: process.env.DB_LOG || false, 19 | ssl: process.env.DB_SSL || false, 20 | sync: process.env.DB_SYNC || true //Synchronizing any model changes with database 21 | }, 22 | redis: { 23 | host: process.env.REDIS_HOST || "localhost", 24 | port: process.env.REDIS_PORT || 6379, 25 | database: process.env.REDIS_DATABASE || 0, 26 | password: process.env.REDIS_PASSWORD || "", 27 | }, 28 | log: { 29 | // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' 30 | format: 'dev', 31 | // Stream defaults to process.stdout 32 | // Uncomment to enable logging to a log on the file system 33 | options: { 34 | //stream: 'access.log' 35 | } 36 | }, 37 | app: { 38 | title: defaultEnvConfig.app.title + ' - Development Environment' 39 | }, 40 | facebook: { 41 | clientID: process.env.FACEBOOK_ID || 'APP_ID', 42 | clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', 43 | callbackURL: '/api/auth/facebook/callback' 44 | }, 45 | twitter: { 46 | clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY', 47 | clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET', 48 | callbackURL: '/api/auth/twitter/callback' 49 | }, 50 | google: { 51 | clientID: process.env.GOOGLE_ID || 'APP_ID', 52 | clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET', 53 | callbackURL: '/api/auth/google/callback' 54 | }, 55 | linkedin: { 56 | clientID: process.env.LINKEDIN_ID || 'APP_ID', 57 | clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', 58 | callbackURL: '/api/auth/linkedin/callback' 59 | }, 60 | github: { 61 | clientID: process.env.GITHUB_ID || 'APP_ID', 62 | clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET', 63 | callbackURL: '/api/auth/github/callback' 64 | }, 65 | paypal: { 66 | clientID: process.env.PAYPAL_ID || 'CLIENT_ID', 67 | clientSecret: process.env.PAYPAL_SECRET || 'CLIENT_SECRET', 68 | callbackURL: '/api/auth/paypal/callback', 69 | sandbox: true 70 | }, 71 | mailer: { 72 | from: process.env.MAILER_FROM || 'MAILER_FROM', 73 | options: { 74 | service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER', 75 | auth: { 76 | user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID', 77 | pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD' 78 | } 79 | } 80 | }, 81 | livereload: true 82 | }; -------------------------------------------------------------------------------- /config/env/heroku_example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var defaultEnvConfig = require('./default'); 4 | 5 | module.exports = { 6 | db: { 7 | name: "dfuue4lro8u0qk", 8 | host: "ec2-54-217-240-205.eu-west-1.compute.amazonaws.com", 9 | port: 5432, 10 | username: "xjwecgnyhogzdw", 11 | password: "iH1awetnARl9-HltdMqMWuF0dN", 12 | dialect: "postgres", 13 | enableSequelizeLog: false, 14 | ssl: true, 15 | sync: process.env.DB_SYNC || false //Synchronizing any model changes with database 16 | }, 17 | redis: { 18 | host: process.env.REDIS_HOST || "localhost", 19 | port: process.env.REDIS_PORT || 6379, 20 | database: process.env.REDIS_DATABASE || 0, 21 | password: process.env.REDIS_PASSWORD || "", 22 | }, 23 | log: { 24 | // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' 25 | format: 'dev', 26 | // Stream defaults to process.stdout 27 | // Uncomment to enable logging to a log on the file system 28 | options: { 29 | //stream: 'access.log' 30 | } 31 | }, 32 | app: { 33 | title: defaultEnvConfig.app.title + ' - Heroku Environment' 34 | }, 35 | facebook: { 36 | clientID: process.env.FACEBOOK_ID || 'APP_ID', 37 | clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', 38 | callbackURL: '/api/auth/facebook/callback' 39 | }, 40 | twitter: { 41 | clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY', 42 | clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET', 43 | callbackURL: '/api/auth/twitter/callback' 44 | }, 45 | google: { 46 | clientID: process.env.GOOGLE_ID || 'APP_ID', 47 | clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET', 48 | callbackURL: '/api/auth/google/callback' 49 | }, 50 | linkedin: { 51 | clientID: process.env.LINKEDIN_ID || 'APP_ID', 52 | clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', 53 | callbackURL: '/api/auth/linkedin/callback' 54 | }, 55 | github: { 56 | clientID: process.env.GITHUB_ID || 'APP_ID', 57 | clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET', 58 | callbackURL: '/api/auth/github/callback' 59 | }, 60 | paypal: { 61 | clientID: process.env.PAYPAL_ID || 'CLIENT_ID', 62 | clientSecret: process.env.PAYPAL_SECRET || 'CLIENT_SECRET', 63 | callbackURL: '/api/auth/paypal/callback', 64 | sandbox: true 65 | }, 66 | mailer: { 67 | from: process.env.MAILER_FROM || 'MAILER_FROM', 68 | options: { 69 | service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER', 70 | auth: { 71 | user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID', 72 | pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD' 73 | } 74 | } 75 | }, 76 | livereload: true 77 | }; -------------------------------------------------------------------------------- /config/env/local_example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Rename this file to local.js for having a local configuration variables that 4 | // will not get commited and pushed to remote repositories. 5 | // Use it for your API keys, passwords, etc. 6 | 7 | var defaultEnvConfig = require('./default'); 8 | 9 | module.exports = { 10 | db: { 11 | name: process.env.DB_NAME || "seanjs_dev", 12 | host: process.env.DB_HOST || "localhost", 13 | port: process.env.DB_PORT || 5432, 14 | username: process.env.DB_USERNAME || "postgres", 15 | password: process.env.DB_PASSWORD || "postgres", 16 | dialect: process.env.DB_DIALECT || "postgres", //mysql, postgres, sqlite3,... 17 | enableSequelizeLog: process.env.DB_LOG || false, 18 | ssl: process.env.DB_SSL || false, 19 | sync: process.env.DB_SYNC || true //Synchronizing any model changes with database 20 | }, 21 | redis: { 22 | host: process.env.REDIS_HOST || "localhost", 23 | port: process.env.REDIS_PORT || 6379, 24 | database: process.env.REDIS_DATABASE || 0, 25 | password: process.env.REDIS_PASSWORD || "", 26 | }, 27 | log: { 28 | // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' 29 | format: 'dev', 30 | // Stream defaults to process.stdout 31 | // Uncomment to enable logging to a log on the file system 32 | options: { 33 | //stream: 'access.log' 34 | } 35 | }, 36 | app: { 37 | title: defaultEnvConfig.app.title + ' - Local Environment' 38 | }, 39 | facebook: { 40 | clientID: process.env.FACEBOOK_ID || 'APP_ID', 41 | clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', 42 | callbackURL: '/api/auth/facebook/callback' 43 | }, 44 | twitter: { 45 | clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY', 46 | clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET', 47 | callbackURL: '/api/auth/twitter/callback' 48 | }, 49 | google: { 50 | clientID: process.env.GOOGLE_ID || 'APP_ID', 51 | clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET', 52 | callbackURL: '/api/auth/google/callback' 53 | }, 54 | linkedin: { 55 | clientID: process.env.LINKEDIN_ID || 'APP_ID', 56 | clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', 57 | callbackURL: '/api/auth/linkedin/callback' 58 | }, 59 | github: { 60 | clientID: process.env.GITHUB_ID || 'APP_ID', 61 | clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET', 62 | callbackURL: '/api/auth/github/callback' 63 | }, 64 | paypal: { 65 | clientID: process.env.PAYPAL_ID || 'CLIENT_ID', 66 | clientSecret: process.env.PAYPAL_SECRET || 'CLIENT_SECRET', 67 | callbackURL: '/api/auth/paypal/callback', 68 | sandbox: true 69 | }, 70 | mailer: { 71 | from: process.env.MAILER_FROM || 'MAILER_FROM', 72 | options: { 73 | service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER', 74 | auth: { 75 | user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID', 76 | pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD' 77 | } 78 | } 79 | }, 80 | livereload: true 81 | }; -------------------------------------------------------------------------------- /config/env/production.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | secure: { 5 | ssl: Boolean(process.env.ssl) || false, 6 | privateKey: './config/sslcerts/key.pem', 7 | certificate: './config/sslcerts/cert.pem' 8 | }, 9 | port: process.env.PORT || 8443, 10 | db: { 11 | name: process.env.DB_NAME || "seanjs_dev", 12 | host: process.env.DB_HOST || "localhost", 13 | port: process.env.DB_PORT || 5432, 14 | username: process.env.DB_USERNAME || "postgres", 15 | password: process.env.DB_PASSWORD || "postgres", 16 | dialect: process.env.DB_DIALECT || "postgres", //mysql, postgres, sqlite3,... 17 | enableSequelizeLog: process.env.DB_LOG || false, 18 | ssl: process.env.DB_SSL || false, 19 | sync: process.env.DB_SYNC || false //Synchronizing any model changes with database 20 | }, 21 | redis: { 22 | host: process.env.REDIS_HOST || "localhost", 23 | port: process.env.REDIS_PORT || 6379, 24 | database: parseInt(process.env.REDIS_DATABASE) || 0, 25 | password: process.env.REDIS_PASSWORD || "", 26 | }, 27 | log: { 28 | // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' 29 | format: 'combined', 30 | // Stream defaults to process.stdout 31 | // Uncomment to enable logging to a log on the file system 32 | options: { 33 | stream: 'access.log' 34 | } 35 | }, 36 | facebook: { 37 | clientID: process.env.FACEBOOK_ID || 'APP_ID', 38 | clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', 39 | callbackURL: '/api/auth/facebook/callback' 40 | }, 41 | twitter: { 42 | clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY', 43 | clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET', 44 | callbackURL: '/api/auth/twitter/callback' 45 | }, 46 | google: { 47 | clientID: process.env.GOOGLE_ID || 'APP_ID', 48 | clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET', 49 | callbackURL: '/api/auth/google/callback' 50 | }, 51 | linkedin: { 52 | clientID: process.env.LINKEDIN_ID || 'APP_ID', 53 | clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', 54 | callbackURL: '/api/auth/linkedin/callback' 55 | }, 56 | github: { 57 | clientID: process.env.GITHUB_ID || 'APP_ID', 58 | clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET', 59 | callbackURL: '/api/auth/github/callback' 60 | }, 61 | paypal: { 62 | clientID: process.env.PAYPAL_ID || 'CLIENT_ID', 63 | clientSecret: process.env.PAYPAL_SECRET || 'CLIENT_SECRET', 64 | callbackURL: '/api/auth/paypal/callback', 65 | sandbox: false 66 | }, 67 | mailer: { 68 | from: process.env.MAILER_FROM || 'MAILER_FROM', 69 | options: { 70 | service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER', 71 | auth: { 72 | user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID', 73 | pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD' 74 | } 75 | } 76 | } 77 | }; -------------------------------------------------------------------------------- /config/env/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var defaultEnvConfig = require('./default'); 4 | 5 | module.exports = { 6 | port: process.env.PORT || 3001, 7 | db: { 8 | name: process.env.DB_NAME || "seanjs_dev", 9 | host: process.env.DB_HOST || "localhost", 10 | port: process.env.DB_PORT || 5432, 11 | username: process.env.DB_USERNAME || "postgres", 12 | password: process.env.DB_PASSWORD || "postgres", 13 | dialect: process.env.DB_DIALECT || "postgres", //mysql, postgres, sqlite3,... 14 | enableSequelizeLog: process.env.DB_LOG || false, 15 | ssl: process.env.DB_SSL || false, 16 | sync: process.env.DB_SYNC || true //Synchronizing any model changes with database 17 | }, 18 | redis: { 19 | host: process.env.REDIS_HOST || "localhost", 20 | port: process.env.REDIS_PORT || 6379, 21 | database: process.env.REDIS_DATABASE || 0, 22 | password: process.env.REDIS_PASSWORD || "", 23 | }, 24 | app: { 25 | title: defaultEnvConfig.app.title + ' - Test Environment' 26 | }, 27 | facebook: { 28 | clientID: process.env.FACEBOOK_ID || 'APP_ID', 29 | clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET', 30 | callbackURL: '/api/auth/facebook/callback' 31 | }, 32 | twitter: { 33 | clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY', 34 | clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET', 35 | callbackURL: '/api/auth/twitter/callback' 36 | }, 37 | google: { 38 | clientID: process.env.GOOGLE_ID || 'APP_ID', 39 | clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET', 40 | callbackURL: '/api/auth/google/callback' 41 | }, 42 | linkedin: { 43 | clientID: process.env.LINKEDIN_ID || 'APP_ID', 44 | clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET', 45 | callbackURL: '/api/auth/linkedin/callback' 46 | }, 47 | github: { 48 | clientID: process.env.GITHUB_ID || 'APP_ID', 49 | clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET', 50 | callbackURL: '/api/auth/github/callback' 51 | }, 52 | paypal: { 53 | clientID: process.env.PAYPAL_ID || 'CLIENT_ID', 54 | clientSecret: process.env.PAYPAL_SECRET || 'CLIENT_SECRET', 55 | callbackURL: '/api/auth/paypal/callback', 56 | sandbox: true 57 | }, 58 | mailer: { 59 | from: process.env.MAILER_FROM || 'MAILER_FROM', 60 | options: { 61 | service: process.env.MAILER_SERVICE_PROVIDER || 'MAILER_SERVICE_PROVIDER', 62 | auth: { 63 | user: process.env.MAILER_EMAIL_ID || 'MAILER_EMAIL_ID', 64 | pass: process.env.MAILER_PASSWORD || 'MAILER_PASSWORD' 65 | } 66 | } 67 | } 68 | }; -------------------------------------------------------------------------------- /config/lib/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var config = require('../config'), 7 | express = require('./express'), 8 | chalk = require('chalk'), 9 | sequelize = require('./sequelize-connect'), 10 | winston = require('./winston'); 11 | 12 | 13 | module.exports.init = function init(callback) { 14 | var app = express.init(sequelize); 15 | if (callback) callback(app, sequelize, config); 16 | }; 17 | 18 | module.exports.start = function start(callback) { 19 | winston.info('Initializing SEAN.JS Stack...'); 20 | 21 | var _this = this; 22 | 23 | _this.init(function(app, db, config) { 24 | 25 | // Start the app by listening on 26 | app.listen(config.port, function() { 27 | 28 | // Logging initialization 29 | console.log('--------------------------'); 30 | console.log(chalk.green(config.app.title)); 31 | console.log(chalk.green('Environment:\t\t') + process.env.NODE_ENV); 32 | console.log(chalk.green('Port:\t\t\t') + config.port); 33 | console.log(chalk.green('Database:\t\t') + config.db.name); 34 | if (config.secure && config.secure.ssl === true) { 35 | console.log(chalk.green('SSL:\t\t\tON')); 36 | } 37 | console.info(chalk.green('App version:\t\t') + config.seanjs.version); 38 | 39 | if (config.seanjs['seanjs-version']) { 40 | console.log(chalk.green('SEAN.JS version:\t') + config.seanjs['seanjs-version']); 41 | } 42 | 43 | console.info(chalk.green('App URL:\t\t') + (process.env.NODE_HOST || 'localhost') + ":" + config.port); 44 | 45 | console.log('--------------------------'); 46 | 47 | 48 | if (!config.app.reCaptchaSecret) { 49 | winston.warn('Missing reCaptcha Secret in env!'); 50 | } 51 | 52 | if (callback) callback(app, db, config); 53 | }); 54 | 55 | }); 56 | 57 | }; -------------------------------------------------------------------------------- /config/lib/reCaptcha.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var config = require('../config'), 4 | request = require('request'); 5 | 6 | // Verify the reCaptcha response 7 | exports.verify = function(response, cb) { 8 | request.post({ 9 | url: "https://www.google.com/recaptcha/api/siteverify", 10 | form: { 11 | secret: config.app.reCaptchaSecret, 12 | response: response 13 | }, 14 | json: true 15 | }, function(err, httpResponse, body) { 16 | if (err) { 17 | console.log('reCaptcha error', err); 18 | cb(err); 19 | } else if (body.success !== true) { 20 | cb(body); 21 | } 22 | 23 | if (cb) cb(null); 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /config/lib/sequelize-connect.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var Sequelize = require('sequelize'); 6 | var _ = require('lodash'); 7 | var config = require('../config'); 8 | var winston = require('./winston'); 9 | 10 | winston.info('Initializing Sequelize...'); 11 | 12 | var orm = require('./sequelize'); 13 | 14 | var models = []; 15 | 16 | config.files.server.models.forEach(function(file) { 17 | models.push(path.resolve(file)); 18 | }); 19 | 20 | orm.discover = models; 21 | orm.connect(config.db.name, config.db.username, config.db.password, { 22 | host: config.db.host, 23 | port: config.db.port, 24 | dialect: config.db.dialect, 25 | storage: config.db.storage, 26 | logging: config.db.enableSequelizeLog ? winston.verbose : false, 27 | dialectOptions: { 28 | ssl: config.db.ssl ? config.db.ssl : false 29 | } 30 | }); -------------------------------------------------------------------------------- /config/lib/sequelize.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var 4 | path = require('path'), 5 | config = require(path.resolve('./config/config')), 6 | Sequelize = require('sequelize'), 7 | winston = require('./winston'), 8 | db = {}; 9 | 10 | db.Sequelize = Sequelize; 11 | db.models = {}; 12 | db.discover = []; 13 | 14 | // Expose the connection function 15 | db.connect = function(database, username, password, options) { 16 | 17 | if (typeof db.logger === 'function') 18 | winston.info("Connecting to: " + database + " as: " + username); 19 | 20 | // Instantiate a new sequelize instance 21 | var sequelize = new db.Sequelize(database, username, password, options); 22 | 23 | 24 | db.discover.forEach(function(location) { 25 | var model = sequelize["import"](location); 26 | if (model) 27 | db.models[model.name] = model; 28 | }); 29 | 30 | // Execute the associate methods for each Model 31 | Object.keys(db.models).forEach(function(modelName) { 32 | if (db.models[modelName].options.hasOwnProperty('associate')) { 33 | db.models[modelName].options.associate(db.models); 34 | winston.info("Associating Model: " + modelName); 35 | } 36 | }); 37 | 38 | if (config.db.sync) { 39 | // Synchronizing any model changes with database. 40 | sequelize.sync() 41 | .then(function() { 42 | winston.info("Database synchronized"); 43 | }).catch(function(err) { 44 | winston.error("An error occured: %j", err); 45 | }); 46 | } 47 | 48 | db.sequelize = sequelize; 49 | 50 | winston.info("Finished Connecting to Database"); 51 | 52 | return true; 53 | }; 54 | 55 | module.exports = db; -------------------------------------------------------------------------------- /config/lib/socket.io.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Load the module dependencies 4 | var config = require('../config'), 5 | path = require('path'), 6 | fs = require('fs'), 7 | http = require('http'), 8 | https = require('https'), 9 | cookieParser = require('cookie-parser'), 10 | passport = require('passport'), 11 | socketio = require('socket.io'), 12 | session = require('express-session'), 13 | RedisStore = require('connect-redis')(session); 14 | 15 | // Define the Socket.io configuration method 16 | module.exports = function(app, db) { 17 | var server; 18 | if (config.secure && config.secure.ssl === true) { 19 | // Load SSL key and certificate 20 | var privateKey = fs.readFileSync(path.resolve(config.secure.privateKey), 'utf8'); 21 | var certificate = fs.readFileSync(path.resolve(config.secure.certificate), 'utf8'); 22 | var options = { 23 | key: privateKey, 24 | cert: certificate, 25 | // requestCert : true, 26 | // rejectUnauthorized : true, 27 | secureProtocol: 'TLSv1_method', 28 | ciphers: [ 29 | 'ECDHE-RSA-AES128-GCM-SHA256', 30 | 'ECDHE-ECDSA-AES128-GCM-SHA256', 31 | 'ECDHE-RSA-AES256-GCM-SHA384', 32 | 'ECDHE-ECDSA-AES256-GCM-SHA384', 33 | 'DHE-RSA-AES128-GCM-SHA256', 34 | 'ECDHE-RSA-AES128-SHA256', 35 | 'DHE-RSA-AES128-SHA256', 36 | 'ECDHE-RSA-AES256-SHA384', 37 | 'DHE-RSA-AES256-SHA384', 38 | 'ECDHE-RSA-AES256-SHA256', 39 | 'DHE-RSA-AES256-SHA256', 40 | 'HIGH', 41 | '!aNULL', 42 | '!eNULL', 43 | '!EXPORT', 44 | '!DES', 45 | '!RC4', 46 | '!MD5', 47 | '!PSK', 48 | '!SRP', 49 | '!CAMELLIA' 50 | ].join(':'), 51 | honorCipherOrder: true 52 | }; 53 | 54 | // Create new HTTPS Server 55 | server = https.createServer(options, app); 56 | } else { 57 | // Create a new HTTP server 58 | server = http.createServer(app); 59 | } 60 | // Create a new Socket.io server 61 | var io = socketio.listen(server); 62 | 63 | var redisStore = new RedisStore({ 64 | host: config.redis.host || 'localhost', 65 | port: config.redis.port || 6379, 66 | db: config.redis.database || 0, 67 | pass: config.redis.password || '' 68 | }); 69 | 70 | // Intercept Socket.io's handshake request 71 | io.use(function(socket, next) { 72 | // Use the 'cookie-parser' module to parse the request cookies 73 | cookieParser(config.sessionSecret)(socket.request, {}, function(err) { 74 | // Get the session id from the request cookies 75 | var sessionId = socket.request.signedCookies ? socket.request.signedCookies[config.sessionKey] : undefined; 76 | 77 | if (!sessionId) return next(new Error('sessionId was not found in socket.request'), false); 78 | 79 | // Use the mongoStorage instance to get the Express session information 80 | redisStore.get(sessionId, function(err, session) { 81 | if (err) return next(err, false); 82 | if (!session) return next(new Error('session was not found for ' + sessionId), false); 83 | 84 | // Set the Socket.io session information 85 | socket.request.session = session; 86 | 87 | // Use Passport to populate the user details 88 | passport.initialize()(socket.request, {}, function() { 89 | passport.session()(socket.request, {}, function() { 90 | if (socket.request.user) { 91 | next(null, true); 92 | } else { 93 | next(new Error('User is not authenticated'), false); 94 | } 95 | }); 96 | }); 97 | }); 98 | }); 99 | }); 100 | 101 | // Add an event listener to the 'connection' event 102 | io.on('connection', function(socket) { 103 | config.files.server.sockets.forEach(function(socketConfiguration) { 104 | require(path.resolve(socketConfiguration))(io, socket); 105 | }); 106 | }); 107 | 108 | return server; 109 | }; -------------------------------------------------------------------------------- /config/lib/winston.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * Created by Junaid Anwar on 5/28/15. 5 | */ 6 | var winston = require('winston'); 7 | var logger = new(winston.Logger)(); 8 | 9 | logger.add(winston.transports.Console, { 10 | level: 'verbose', 11 | prettyPrint: true, 12 | colorize: true, 13 | silent: false, 14 | timestamp: false 15 | }); 16 | 17 | logger.stream = { 18 | write: function(message, encoding) { 19 | logger.info(message); 20 | } 21 | }; 22 | 23 | module.exports = logger; -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | postgres: 2 | restart: always 3 | image: postgres:latest 4 | ports: 5 | - "5432:5432" 6 | environment: 7 | LC_ALL: C.UTF-8 8 | POSTGRES_DB: seanjs_dev 9 | POSTGRES_USER: postgres 10 | POSTGRES_PASSWORD: postgres 11 | 12 | redis: 13 | restart: always 14 | image: redis:latest 15 | ports: 16 | - "6379:6379" 17 | 18 | web: 19 | build: . 20 | links: 21 | - postgres:postgres 22 | ports: 23 | - "3000:3000" 24 | environment: 25 | NODE_ENV: development 26 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var _ = require('lodash'), 7 | defaultAssets = require('./config/assets/default'), 8 | testAssets = require('./config/assets/test'); 9 | 10 | // Karma configuration 11 | module.exports = function(karmaConfig) { 12 | karmaConfig.set({ 13 | // Frameworks to use 14 | frameworks: ['jasmine'], 15 | preprocessors: { 16 | 'modules/*/client/views/**/*.html': ['ng-html2js'] 17 | }, 18 | ngHtml2JsPreprocessor: { 19 | moduleName: 'seanjs', 20 | cacheIdFromPath: function(filepath) { 21 | return filepath; 22 | }, 23 | }, 24 | // List of files / patterns to load in the browser 25 | files: _.union(defaultAssets.client.lib.js, defaultAssets.client.lib.tests, defaultAssets.client.js, testAssets.tests.client, defaultAssets.client.views), 26 | // Test results reporter to use 27 | // Possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 28 | reporters: ['progress'], 29 | // Web server port 30 | port: 9876, 31 | // Enable / disable colors in the output (reporters and logs) 32 | colors: true, 33 | // Level of logging 34 | // Possible values: karmaConfig.LOG_DISABLE || karmaConfig.LOG_ERROR || karmaConfig.LOG_WARN || karmaConfig.LOG_INFO || karmaConfig.LOG_DEBUG 35 | logLevel: karmaConfig.LOG_INFO, 36 | // Enable / disable watching file and executing tests whenever any file changes 37 | autoWatch: true, 38 | // Start these browsers, currently available: 39 | // - Chrome 40 | // - ChromeCanary 41 | // - Firefox 42 | // - Opera 43 | // - Safari (only Mac) 44 | // - PhantomJS 45 | // - IE (only Windows) 46 | browsers: ['PhantomJS'], 47 | // If browser does not capture in given timeout [ms], kill it 48 | captureTimeout: 60000, 49 | // Continuous Integration mode 50 | // If true, it capture browsers, run tests and exit 51 | singleRun: true 52 | }); 53 | }; -------------------------------------------------------------------------------- /modules/articles/client/articles.client.module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use Applicaion configuration module to register a new module 4 | ApplicationConfiguration.registerModule('articles'); -------------------------------------------------------------------------------- /modules/articles/client/config/articles.client.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Configuring the Articles module 4 | angular.module('articles').run(['Menus', 5 | function(Menus) { 6 | // Add the articles dropdown item 7 | Menus.addMenuItem('topbar', { 8 | title: 'Articles', 9 | state: 'articles', 10 | type: 'dropdown', 11 | roles: ['*'] 12 | }); 13 | 14 | // Add the dropdown list item 15 | Menus.addSubMenuItem('topbar', 'articles', { 16 | title: 'List Articles', 17 | state: 'articles.list' 18 | }); 19 | 20 | // Add the dropdown create item 21 | Menus.addSubMenuItem('topbar', 'articles', { 22 | title: 'Create Articles', 23 | state: 'articles.create', 24 | roles: ['user'] 25 | }); 26 | } 27 | ]); -------------------------------------------------------------------------------- /modules/articles/client/config/articles.client.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Setting up route 4 | angular.module('articles').config(['$stateProvider', 5 | function($stateProvider) { 6 | // Articles state routing 7 | 8 | $stateProvider 9 | .state('articles', { 10 | abstract: true, 11 | url: '/articles', 12 | template: '' 13 | }) 14 | .state('articles.list', { 15 | url: '', 16 | templateUrl: 'modules/articles/client/views/list-articles.client.view.html' 17 | }) 18 | .state('articles.create', { 19 | url: '/create', 20 | templateUrl: 'modules/articles/client/views/create-article.client.view.html', 21 | data: { 22 | roles: ['user', 'admin'] 23 | } 24 | }) 25 | .state('articles.view', { 26 | url: '/:articleId', 27 | templateUrl: 'modules/articles/client/views/view-article.client.view.html' 28 | }) 29 | .state('articles.edit', { 30 | url: '/:articleId/edit', 31 | templateUrl: 'modules/articles/client/views/edit-article.client.view.html', 32 | data: { 33 | roles: ['user', 'admin'] 34 | } 35 | }); 36 | } 37 | ]); -------------------------------------------------------------------------------- /modules/articles/client/controllers/articles.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Articles controller 4 | angular.module('articles').controller('ArticlesController', ['$scope', '$stateParams', '$location', 'Authentication', 'Articles', 5 | function($scope, $stateParams, $location, Authentication, Articles) { 6 | $scope.authentication = Authentication; 7 | 8 | // Create new Article 9 | $scope.create = function(isValid) { 10 | $scope.error = null; 11 | 12 | if (!isValid) { 13 | $scope.$broadcast('show-errors-check-validity', 'articleForm'); 14 | 15 | return false; 16 | } 17 | 18 | // Create new Article object 19 | var article = new Articles({ 20 | title: this.title, 21 | content: this.content 22 | }); 23 | 24 | // Redirect after save 25 | article.$save(function(response) { 26 | $location.path('articles/' + response.id); 27 | 28 | // Clear form fields 29 | $scope.title = ''; 30 | $scope.content = ''; 31 | }, function(errorResponse) { 32 | $scope.error = errorResponse.data.message; 33 | }); 34 | }; 35 | 36 | // Remove existing Article 37 | $scope.remove = function(article) { 38 | if (article) { 39 | 40 | article.$remove(); 41 | $location.path('articles'); 42 | } else { 43 | $scope.article.$remove(function() { 44 | $location.path('articles'); 45 | }); 46 | } 47 | }; 48 | 49 | // Update existing Article 50 | $scope.update = function(isValid) { 51 | $scope.error = null; 52 | 53 | if (!isValid) { 54 | $scope.$broadcast('show-errors-check-validity', 'articleForm'); 55 | return false; 56 | } 57 | 58 | var article = $scope.article; 59 | 60 | article.$update(function() { 61 | $location.path('articles/' + article.id); 62 | }, function(errorResponse) { 63 | $scope.error = errorResponse.data.message; 64 | }); 65 | }; 66 | 67 | // Find a list of Articles 68 | $scope.find = function() { 69 | $scope.articles = Articles.query(); 70 | }; 71 | 72 | // Find existing Article 73 | $scope.findOne = function() { 74 | $scope.article = Articles.get({ 75 | articleId: $stateParams.articleId 76 | }); 77 | }; 78 | } 79 | ]); -------------------------------------------------------------------------------- /modules/articles/client/services/articles.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Articles service used for communicating with the articles REST endpoints 4 | angular.module('articles').factory('Articles', ['$resource', 5 | function($resource) { 6 | return $resource('api/articles/:articleId', { 7 | articleId: '@id' 8 | }, { 9 | update: { 10 | method: 'PUT' 11 | } 12 | }); 13 | } 14 | ]); -------------------------------------------------------------------------------- /modules/articles/client/views/create-article.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 |
7 |
8 |
9 | 10 | 11 |
12 |

Article title is required.

13 |
14 |
15 |
16 | 17 | 18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /modules/articles/client/views/edit-article.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 |
7 |
8 |
9 | 10 | 11 |
12 |

Article title is required.

13 |
14 |
15 |
16 | 17 | 18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /modules/articles/client/views/list-articles.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 17 |
18 | No articles yet, why don't you create one? 19 |
20 |
-------------------------------------------------------------------------------- /modules/articles/client/views/view-article.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 13 | 14 | 15 | Posted on 16 | 17 | by 18 | 19 | 20 | 21 |

22 |
23 | -------------------------------------------------------------------------------- /modules/articles/server/config/articles.server.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies 5 | */ 6 | var path = require('path'), 7 | config = require(path.resolve('./config/config')); 8 | 9 | /** 10 | * Module init function. 11 | */ 12 | module.exports = function(app, db) { 13 | 14 | }; -------------------------------------------------------------------------------- /modules/articles/server/controllers/articles.server.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var path = require('path'), 7 | errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller')), 8 | db = require(path.resolve('./config/lib/sequelize')).models, 9 | Article = db.article; 10 | 11 | /** 12 | * Create a article 13 | */ 14 | exports.create = function(req, res) { 15 | 16 | req.body.userId = req.user.id; 17 | 18 | Article.create(req.body).then(function(article) { 19 | if (!article) { 20 | return res.send('users/signup', { 21 | errors: 'Could not create the article' 22 | }); 23 | } else { 24 | return res.jsonp(article); 25 | } 26 | }).catch(function(err) { 27 | return res.status(400).send({ 28 | message: errorHandler.getErrorMessage(err) 29 | }); 30 | }); 31 | }; 32 | 33 | /** 34 | * Show the current article 35 | */ 36 | exports.read = function(req, res) { 37 | res.json(req.article); 38 | }; 39 | 40 | /** 41 | * Update a article 42 | */ 43 | exports.update = function(req, res) { 44 | var article = req.article; 45 | 46 | article.updateAttributes({ 47 | title: req.body.title, 48 | content: req.body.content 49 | }).then(function(article) { 50 | res.json(article); 51 | }).catch(function(err) { 52 | return res.status(400).send({ 53 | message: errorHandler.getErrorMessage(err) 54 | }); 55 | }); 56 | }; 57 | 58 | /** 59 | * Delete an article 60 | */ 61 | exports.delete = function(req, res) { 62 | var article = req.article; 63 | 64 | // Find the article 65 | Article.findById(article.id).then(function(article) { 66 | if (article) { 67 | 68 | // Delete the article 69 | article.destroy().then(function() { 70 | return res.json(article); 71 | }).catch(function(err) { 72 | return res.status(400).send({ 73 | message: errorHandler.getErrorMessage(err) 74 | }); 75 | }); 76 | 77 | } else { 78 | return res.status(400).send({ 79 | message: 'Unable to find the article' 80 | }); 81 | } 82 | }).catch(function(err) { 83 | return res.status(400).send({ 84 | message: errorHandler.getErrorMessage(err) 85 | }); 86 | }); 87 | 88 | }; 89 | 90 | /** 91 | * List of Articles 92 | */ 93 | exports.list = function(req, res) { 94 | Article.findAll({ 95 | include: [db.user] 96 | }).then(function(articles) { 97 | if (!articles) { 98 | return res.status(404).send({ 99 | message: 'No articles found' 100 | }); 101 | } else { 102 | res.json(articles); 103 | } 104 | }).catch(function(err) { 105 | res.jsonp(err); 106 | }); 107 | }; 108 | 109 | /** 110 | * Article middleware 111 | */ 112 | exports.articleByID = function(req, res, next, id) { 113 | 114 | if ((id % 1 === 0) === false) { //check if it's integer 115 | return res.status(404).send({ 116 | message: 'Article is invalid' 117 | }); 118 | } 119 | 120 | Article.find({ 121 | where: { 122 | id: id 123 | }, 124 | include: [{ 125 | model: db.user 126 | }] 127 | }).then(function(article) { 128 | if (!article) { 129 | return res.status(404).send({ 130 | message: 'No article with that identifier has been found' 131 | }); 132 | } else { 133 | req.article = article; 134 | next(); 135 | } 136 | }).catch(function(err) { 137 | return next(err); 138 | }); 139 | 140 | }; -------------------------------------------------------------------------------- /modules/articles/server/models/articles.server.model.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = function(sequelize, DataTypes) { 4 | 5 | var Article = sequelize.define('article', { 6 | title: { 7 | type: DataTypes.STRING, 8 | allowNull: false, 9 | validate: { 10 | len: { 11 | args: [1, 250], 12 | msg: "Article title must be between 1 and 250 characters in length" 13 | }, 14 | } 15 | }, 16 | content: DataTypes.TEXT 17 | }, { 18 | associate: function(models) { 19 | Article.belongsTo(models.user); 20 | } 21 | }); 22 | return Article; 23 | }; 24 | -------------------------------------------------------------------------------- /modules/articles/server/policies/articles.server.policy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var 4 | path = require('path'), 5 | config = require(path.resolve('./config/config')), 6 | acl = require('acl'); 7 | 8 | /** 9 | * Module dependencies. 10 | */ 11 | 12 | // Using the redis backend 13 | /* 14 | var redisInstance = require('redis').createClient(config.redis.port, config.redis.host, { 15 | no_ready_check: true 16 | }); 17 | 18 | //Use redis database 1 19 | redisInstance.select(1); 20 | 21 | if (config.redis.password) { 22 | redisInstance.auth(config.redis.password); 23 | } 24 | 25 | acl = new acl(new acl.redisBackend(redisInstance, 'acl')); 26 | */ 27 | 28 | // Using the memory backend 29 | acl = new acl(new acl.memoryBackend()); 30 | 31 | 32 | /** 33 | * Invoke Articles Permissions 34 | */ 35 | exports.invokeRolesPolicies = function() { 36 | acl.allow([{ 37 | roles: ['admin'], 38 | allows: [{ 39 | resources: '/api/articles', 40 | permissions: '*' 41 | }, { 42 | resources: '/api/articles/:articleId', 43 | permissions: '*' 44 | }] 45 | }, { 46 | roles: ['user'], 47 | allows: [{ 48 | resources: '/api/articles', 49 | permissions: ['get', 'post'] 50 | }, { 51 | resources: '/api/articles/:articleId', 52 | permissions: ['get'] 53 | }] 54 | }, { 55 | roles: ['guest'], 56 | allows: [{ 57 | resources: '/api/articles', 58 | permissions: ['get'] 59 | }, { 60 | resources: '/api/articles/:articleId', 61 | permissions: ['get'] 62 | }] 63 | }]); 64 | }; 65 | 66 | /** 67 | * Check If Articles Policy Allows 68 | */ 69 | exports.isAllowed = function(req, res, next) { 70 | var roles = (req.user) ? req.user.roles : ['guest']; 71 | 72 | // If an article is being processed and the current user created it then allow any manipulation 73 | if (req.article && req.user && req.article.userId === req.user.id) { 74 | return next(); 75 | } 76 | 77 | // Check for user roles 78 | acl.areAnyRolesAllowed(roles, req.route.path, req.method.toLowerCase(), function(err, isAllowed) { 79 | if (err) { 80 | // An authorization error occurred. 81 | return res.status(500).send('Unexpected authorization error'); 82 | } else { 83 | if (isAllowed) { 84 | // Access granted! Invoke next middleware 85 | return next(); 86 | } else { 87 | return res.status(403).json({ 88 | message: 'User is not authorized' 89 | }); 90 | } 91 | } 92 | }); 93 | }; 94 | -------------------------------------------------------------------------------- /modules/articles/server/routes/articles.server.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var path = require('path'), 7 | articlesPolicy = require('../policies/articles.server.policy'), 8 | articles = require(path.resolve('./modules/articles/server/controllers/articles.server.controller')); 9 | 10 | 11 | module.exports = function(app) { 12 | 13 | // Articles collection routes 14 | app.route('/api/articles') 15 | .all(articlesPolicy.isAllowed) 16 | .get(articles.list) 17 | .post(articles.create); 18 | 19 | // Single article routes 20 | app.route('/api/articles/:articleId') 21 | .all(articlesPolicy.isAllowed) 22 | .get(articles.read) 23 | .put(articles.update) 24 | .delete(articles.delete); 25 | 26 | // Finish by binding the article middleware 27 | app.param('articleId', articles.articleByID); 28 | 29 | }; -------------------------------------------------------------------------------- /modules/articles/tests/e2e/articles.e2e.tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Articles E2E Tests:', function() { 4 | describe('Test articles page', function() { 5 | it('Should report missing credentials', function() { 6 | browser.get('http://localhost:3000/articles'); 7 | expect(element.all(by.repeater('article in articles')).count()).toEqual(0); 8 | }); 9 | }); 10 | }); -------------------------------------------------------------------------------- /modules/articles/tests/server/article.server.model.tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var should = require('should'), 7 | path = require('path'), 8 | errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller')), 9 | db = require(path.resolve('./config/lib/sequelize')).models, 10 | Article = db.article, 11 | User = db.user; 12 | 13 | /** 14 | * Globals 15 | */ 16 | var user, article; 17 | 18 | /** 19 | * Unit tests 20 | */ 21 | describe('Article Model Unit Tests:', function() { 22 | 23 | before(function(done) { 24 | user = User.build(); 25 | 26 | user.firstName = 'Full'; 27 | user.lastName = 'Name'; 28 | user.displayName = 'Full Name'; 29 | user.email = 'test@test.com'; 30 | user.username = 'username'; 31 | user.salt = user.makeSalt(); 32 | user.hashedPassword = user.encryptPassword('S3@n.jsI$Aw3$0m3', user.salt); 33 | 34 | user.save().then(function(user) { 35 | article = Article.build({ 36 | title: 'Article Title', 37 | content: 'Article Content', 38 | userId: user.id 39 | }); 40 | done(); 41 | }).catch(function(err) {}); 42 | 43 | }); 44 | 45 | // beforeEach(function(done) { 46 | // done(); 47 | // }); 48 | 49 | describe('Method Save', function() { 50 | it('should be able to save without problems', function(done) { 51 | this.timeout(10000); 52 | article.save().then(function(err) { 53 | should.not.exist((err) ? null : err); 54 | done(); 55 | }).catch(function(err) {}); 56 | 57 | }); 58 | 59 | it('should be able to show an error when try to save without title', function(done) { 60 | article.title = ''; 61 | 62 | article.save().then(function(err) { 63 | should.exist(null); 64 | done(); 65 | }).catch(function(err) { 66 | should.exist(errorHandler.getErrorMessage(err)); 67 | done(); 68 | }); 69 | 70 | }); 71 | }); 72 | 73 | // afterEach(function(done) { 74 | // done(); 75 | // }); 76 | 77 | // TODO CHECK 78 | after(function(done) { 79 | User.destroy({ 80 | where: { 81 | email: 'test@test.com' 82 | } 83 | }) 84 | .then(function(success) { 85 | done(); 86 | }).catch(function(err) {}); 87 | }); 88 | 89 | }); 90 | -------------------------------------------------------------------------------- /modules/chat/client/chat.client.module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use Applicaion configuration module to register a new module 4 | ApplicationConfiguration.registerModule('chat'); -------------------------------------------------------------------------------- /modules/chat/client/config/chat.client.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Configuring the Chat module 4 | angular.module('chat').run(['Menus', 5 | function(Menus) { 6 | // Set top bar menu items 7 | Menus.addMenuItem('topbar', { 8 | title: 'Chat', 9 | state: 'chat' 10 | }); 11 | } 12 | ]); -------------------------------------------------------------------------------- /modules/chat/client/config/chat.client.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Configure the 'chat' module routes 4 | angular.module('chat').config(['$stateProvider', 5 | function($stateProvider) { 6 | $stateProvider 7 | .state('chat', { 8 | url: '/chat', 9 | templateUrl: 'modules/chat/client/views/chat.client.view.html', 10 | data: { 11 | roles: ['user', 'admin'] 12 | } 13 | }); 14 | } 15 | ]); -------------------------------------------------------------------------------- /modules/chat/client/controllers/chat.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Create the 'chat' controller 4 | angular.module('chat').controller('ChatController', ['$scope', '$location', 'Authentication', 'Socket', 5 | function($scope, $location, Authentication, Socket) { 6 | // Create a messages array 7 | $scope.messages = []; 8 | 9 | // If user is not signed in then redirect back home 10 | if (!Authentication.user) { 11 | $location.path('/'); 12 | } 13 | 14 | // Make sure the Socket is connected 15 | if (!Socket.socket) { 16 | Socket.connect(); 17 | } 18 | 19 | // Add an event listener to the 'chatMessage' event 20 | Socket.on('chatMessage', function(message) { 21 | $scope.messages.unshift(message); 22 | }); 23 | 24 | // Create a controller method for sending messages 25 | $scope.sendMessage = function() { 26 | // Create a new message object 27 | var message = { 28 | text: this.messageText 29 | }; 30 | 31 | // Emit a 'chatMessage' message event 32 | Socket.emit('chatMessage', message); 33 | 34 | // Clear the message text 35 | this.messageText = ''; 36 | }; 37 | 38 | // Remove the event listener when the controller instance is destroyed 39 | $scope.$on('$destroy', function() { 40 | Socket.removeListener('chatMessage'); 41 | }); 42 | 43 | // Get the current connected clients 44 | Socket.on('currentClients', function(count) { 45 | $scope.currentClients = count; 46 | }); 47 | 48 | } 49 | ]); -------------------------------------------------------------------------------- /modules/chat/client/scss/chat.scss: -------------------------------------------------------------------------------- 1 | .chat-message { 2 | margin-top: 10px; 3 | padding-top: 10px; 4 | } 5 | .chat-message:not(:first-child) { 6 | border-top: 1px solid #e7e7e7; 7 | } 8 | .chat-message-details { 9 | margin-left: 10px; 10 | } 11 | .chat-profile-image { 12 | height: 28px; 13 | width: 28px; 14 | } 15 | -------------------------------------------------------------------------------- /modules/chat/client/views/chat.client.view.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 7 | 8 |
9 |
10 |
11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 |
    19 | 20 |
  • 21 | 22 | 23 | {{message.username}} 24 | 25 | 26 | {{message.username}} 27 | 28 |
    29 | 30 |
    31 | 32 |
    33 |
  • 34 |
35 |
36 | -------------------------------------------------------------------------------- /modules/chat/server/sockets/chat.server.socket.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Create the chat configuration 4 | module.exports = function(io, socket) { 5 | 6 | // When client connects 7 | io.on('connection', function() { 8 | io.emit('currentClients', socket.server.engine.clientsCount); 9 | }); 10 | 11 | // Emit the status event when a new socket client is connected 12 | io.emit('chatMessage', { 13 | type: 'status', 14 | text: 'Is now connected', 15 | created: Date.now(), 16 | profileImageURL: socket.request.user.profileImageURL, 17 | username: socket.request.user.username 18 | }); 19 | 20 | // Send a chat messages to all connected sockets when a message is received 21 | socket.on('chatMessage', function(message) { 22 | message.type = 'message'; 23 | message.created = Date.now(); 24 | message.profileImageURL = socket.request.user.profileImageURL; 25 | message.username = socket.request.user.username; 26 | 27 | // Emit the 'chatMessage' event 28 | io.emit('chatMessage', message); 29 | }); 30 | 31 | // Emit the status event when a socket client is disconnected 32 | socket.on('disconnect', function() { 33 | io.emit('currentClients', socket.server.engine.clientsCount); 34 | io.emit('chatMessage', { 35 | type: 'status', 36 | text: 'disconnected', 37 | created: Date.now(), 38 | username: socket.request.user.username 39 | }); 40 | 41 | }); 42 | 43 | 44 | }; -------------------------------------------------------------------------------- /modules/chat/tests/client/chat.client.controller.tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Chat client controller tests 5 | */ 6 | (function() { 7 | describe('ChatController', function() { 8 | //Initialize global variables 9 | var scope, 10 | Socket, 11 | ChatController, 12 | $timeout, 13 | $location, 14 | Authentication; 15 | 16 | // Load the main application module 17 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 18 | 19 | beforeEach(inject(function($controller, $rootScope, _Socket_, _Authentication_, _$timeout_, _$location_) { 20 | scope = $rootScope.$new(); 21 | Socket = _Socket_; 22 | $timeout = _$timeout_; 23 | $location = _$location_; 24 | Authentication = _Authentication_; 25 | })); 26 | 27 | describe('when user logged out', function() { 28 | beforeEach(inject(function($controller, $rootScope, _Socket_, _Authentication_, _$timeout_, _$location_) { 29 | Authentication.user = undefined; 30 | spyOn($location, 'path'); 31 | ChatController = $controller('ChatController', { 32 | $scope: scope, 33 | }); 34 | })); 35 | 36 | it('should redirect logged out user to /', function() { 37 | expect($location.path).toHaveBeenCalledWith('/'); 38 | }); 39 | }); 40 | 41 | describe('when user logged in', function() { 42 | beforeEach(inject(function($controller, $rootScope, _Socket_, _Authentication_, _$timeout_, _$location_) { 43 | Authentication.user = { 44 | name: 'user', 45 | roles: ['user'] 46 | }; 47 | 48 | ChatController = $controller('ChatController', { 49 | $scope: scope, 50 | }); 51 | })); 52 | 53 | it('should make sure socket is connected', function() { 54 | expect(Socket.socket).toBeTruthy(); 55 | }); 56 | 57 | it('should define messages array', function() { 58 | expect(scope.messages).toBeDefined(); 59 | expect(scope.messages.length).toBe(0); 60 | }); 61 | 62 | describe('sendMessage', function() { 63 | var text = 'hello world!'; 64 | beforeEach(function() { 65 | scope.messageText = text; 66 | scope.sendMessage(); 67 | $timeout.flush(); 68 | }); 69 | 70 | it('should add message to messages', function() { 71 | expect(scope.messages.length).toBe(1); 72 | }); 73 | 74 | it('should add message with proper text attribute set', function() { 75 | expect(scope.messages[0].text).toBe(text); 76 | }); 77 | 78 | it('should clear messageText', function() { 79 | expect(scope.messageText).toBe(''); 80 | }); 81 | }); 82 | 83 | describe('$destroy()', function() { 84 | beforeEach(function() { 85 | scope.$destroy(); 86 | }); 87 | 88 | it('should remove chatMessage listener', function() { 89 | expect(Socket.socket.cbs.chatMessage).toBeUndefined(); 90 | }); 91 | }); 92 | }); 93 | }); 94 | }()); -------------------------------------------------------------------------------- /modules/chat/tests/e2e/chat.e2e.tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Chat e2e tests 5 | */ 6 | describe('Chat E2E Tests:', function() { 7 | // TODO: Add chat e2e tests 8 | }); -------------------------------------------------------------------------------- /modules/chat/tests/server/chat.socket.tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Chat socket tests 5 | */ 6 | describe('Chat Socket Tests:', function() { 7 | // TODO: Add chat socket tests 8 | }); -------------------------------------------------------------------------------- /modules/core/client/app/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Init the application configuration module for AngularJS application 4 | var ApplicationConfiguration = (function() { 5 | // Init module configuration options 6 | var applicationModuleName = 'seanjs'; 7 | var applicationModuleVendorDependencies = ['ngResource', 'ngAnimate', 'ngMessages', 'ui.router', 'ui.bootstrap', 'ui.utils', 'angularFileUpload']; 8 | 9 | // Add a new vertical module 10 | var registerModule = function(moduleName, dependencies) { 11 | // Create angular module 12 | angular.module(moduleName, dependencies || []); 13 | 14 | // Add the module to the AngularJS configuration file 15 | angular.module(applicationModuleName).requires.push(moduleName); 16 | }; 17 | 18 | return { 19 | applicationModuleName: applicationModuleName, 20 | applicationModuleVendorDependencies: applicationModuleVendorDependencies, 21 | registerModule: registerModule 22 | }; 23 | })(); -------------------------------------------------------------------------------- /modules/core/client/app/init.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Start by defining the main module and adding the module dependencies 4 | angular.module(ApplicationConfiguration.applicationModuleName, ApplicationConfiguration.applicationModuleVendorDependencies); 5 | 6 | // Setting HTML5 Location Mode 7 | angular.module(ApplicationConfiguration.applicationModuleName).config(['$locationProvider', '$httpProvider', 8 | function($locationProvider, $httpProvider) { 9 | $locationProvider.html5Mode(true).hashPrefix('!'); 10 | 11 | $httpProvider.interceptors.push('authInterceptor'); 12 | } 13 | ]); 14 | 15 | angular.module(ApplicationConfiguration.applicationModuleName).run(function($rootScope, $state, Authentication) { 16 | 17 | // Check authentication before changing state 18 | $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) { 19 | if (toState.data && toState.data.roles && toState.data.roles.length > 0) { 20 | var allowed = false; 21 | 22 | if (Authentication.user.roles) { 23 | toState.data.roles.forEach(function(role) { 24 | if (Authentication.user.roles !== undefined && Authentication.user.roles.indexOf(role) !== -1) { 25 | allowed = true; 26 | return true; 27 | } 28 | }); 29 | } 30 | 31 | if (!allowed) { 32 | event.preventDefault(); 33 | if (Authentication.user !== undefined && typeof Authentication.user === 'object') { 34 | $state.go('forbidden'); 35 | } else { 36 | $state.go('authentication.signin'); 37 | } 38 | } 39 | } 40 | }); 41 | 42 | // Record previous state 43 | $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) { 44 | if (!fromState.data || !fromState.data.ignoreState) { 45 | $state.previous = { 46 | state: fromState, 47 | params: fromParams, 48 | href: $state.href(fromState, fromParams) 49 | }; 50 | } 51 | }); 52 | }); 53 | 54 | //Then define the init function for starting up the application 55 | angular.element(document).ready(function() { 56 | //Fixing facebook bug with redirect 57 | if (window.location.hash && window.location.hash === '#_=_') { 58 | if (window.history && history.pushState) { 59 | window.history.pushState('', document.title, window.location.pathname); 60 | } else { 61 | // Prevent scrolling by storing the page's current scroll offset 62 | var scroll = { 63 | top: document.body.scrollTop, 64 | left: document.body.scrollLeft 65 | }; 66 | window.location.hash = ''; 67 | // Restore the scroll offset, should be flicker free 68 | document.body.scrollTop = scroll.top; 69 | document.body.scrollLeft = scroll.left; 70 | } 71 | } 72 | 73 | //Then init the app 74 | angular.bootstrap(document, [ApplicationConfiguration.applicationModuleName]); 75 | }); -------------------------------------------------------------------------------- /modules/core/client/config/core-admin.client.menus.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('core.admin').run(['Menus', 4 | function(Menus) { 5 | Menus.addMenuItem('topbar', { 6 | title: 'Admin', 7 | state: 'admin', 8 | type: 'dropdown', 9 | roles: ['admin'] 10 | }); 11 | } 12 | ]); -------------------------------------------------------------------------------- /modules/core/client/config/core-admin.client.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Setting up route 4 | angular.module('core.admin.routes').config(['$stateProvider', 5 | function($stateProvider) { 6 | $stateProvider 7 | .state('admin', { 8 | abstract: true, 9 | url: '/admin', 10 | template: '', 11 | data: { 12 | roles: ['admin'] 13 | } 14 | }); 15 | } 16 | ]); -------------------------------------------------------------------------------- /modules/core/client/config/core.client.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Configuering the core module 4 | angular.module('core').run(['Menus', 5 | function(Menus) { 6 | 7 | //Add the contact-us to the menu 8 | Menus.addMenuItem('topbar', { 9 | title: 'Contact us', 10 | state: 'contact-us', 11 | roles: ['*'] //All users 12 | }); 13 | 14 | } 15 | ]); -------------------------------------------------------------------------------- /modules/core/client/config/core.client.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Setting up route 4 | angular.module('core').config(['$stateProvider', '$urlRouterProvider', 5 | function($stateProvider, $urlRouterProvider) { 6 | 7 | // Redirect to 404 when route not found 8 | $urlRouterProvider.otherwise(function($injector, $location) { 9 | $injector.get('$state').transitionTo('not-found', null, { 10 | location: false 11 | }); 12 | }); 13 | 14 | // Home state routing 15 | $stateProvider 16 | .state('home', { 17 | url: '/', 18 | templateUrl: 'modules/core/client/views/home.client.view.html' 19 | }) 20 | .state('contact-us', { 21 | url: '/contact-us', 22 | templateUrl: 'modules/core/client/views/contact.client.view.html' 23 | }) 24 | .state('not-found', { 25 | url: '/not-found', 26 | templateUrl: 'modules/core/client/views/404.client.view.html', 27 | data: { 28 | ignoreState: true 29 | } 30 | }) 31 | .state('bad-request', { 32 | url: '/bad-request', 33 | templateUrl: 'modules/core/client/views/400.client.view.html', 34 | data: { 35 | ignoreState: true 36 | } 37 | }) 38 | .state('forbidden', { 39 | url: '/forbidden', 40 | templateUrl: 'modules/core/client/views/403.client.view.html', 41 | data: { 42 | ignoreState: true 43 | } 44 | }); 45 | } 46 | ]); 47 | -------------------------------------------------------------------------------- /modules/core/client/controllers/contact.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('core').controller('ContactController', ['$scope', 'ContactForm', 4 | function($scope, ContactForm) { 5 | 6 | $scope.contact = function(isValid) { 7 | $scope.error = null; 8 | $scope.success = null; 9 | 10 | if (!isValid) { 11 | $scope.$broadcast('show-errors-check-validity', 'contactForm'); 12 | return false; 13 | } 14 | 15 | if (grecaptcha.getResponse() === "") { 16 | $scope.error = "Please resolve the captcha first!"; 17 | } else { 18 | var contactForm = new ContactForm({ 19 | name: this.name, 20 | email: this.email, 21 | subject: this.subject, 22 | message: this.message, 23 | //Get the captcha value and send it to the server for verifing 24 | grecaptcha: grecaptcha.getResponse() 25 | }); 26 | 27 | $scope.submitButton = "Working..."; 28 | $scope.submitButtonDisabled = true; 29 | 30 | contactForm.$save(function(response) { 31 | //Reset the reCaptcha 32 | grecaptcha.reset(); 33 | $scope.success = response.message; 34 | }, function(errorResponse) { 35 | console.log('errorResponse', errorResponse); 36 | //Reset the reCaptcha 37 | grecaptcha.reset(); 38 | $scope.error = errorResponse.data.message; 39 | }); 40 | 41 | $scope.submitButton = "Send"; 42 | $scope.submitButtonDisabled = false; 43 | } 44 | }; 45 | 46 | } 47 | 48 | ]); -------------------------------------------------------------------------------- /modules/core/client/controllers/header.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('core').controller('HeaderController', ['$rootScope', '$scope', '$location', '$state', 'Authentication', 'Menus', 4 | function($rootScope, $scope, $location, $state, Authentication, Menus) { 5 | // Expose view variables 6 | $scope.$state = $state; 7 | $scope.authentication = Authentication; 8 | 9 | // Get the topbar menu 10 | $scope.menu = Menus.getMenu('topbar'); 11 | 12 | // Toggle the menu items 13 | $scope.isCollapsed = false; 14 | $scope.toggleCollapsibleMenu = function() { 15 | $scope.isCollapsed = !$scope.isCollapsed; 16 | }; 17 | 18 | // Collapsing the menu after navigation 19 | $scope.$on('$stateChangeSuccess', function() { 20 | $scope.isCollapsed = false; 21 | ga('send', 'pageview', $location.path()); 22 | }); 23 | 24 | } 25 | ]); -------------------------------------------------------------------------------- /modules/core/client/controllers/home.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('core').controller('HomeController', ['$scope', 'Authentication', 4 | function($scope, Authentication) { 5 | // This provides Authentication context. 6 | $scope.authentication = Authentication; 7 | } 8 | ]); -------------------------------------------------------------------------------- /modules/core/client/core.client.module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use Applicaion configuration module to register a new module 4 | ApplicationConfiguration.registerModule('core'); 5 | ApplicationConfiguration.registerModule('core.admin', ['core']); 6 | ApplicationConfiguration.registerModule('core.admin.routes', ['ui.router']); -------------------------------------------------------------------------------- /modules/core/client/directives/show-errors.client.directives.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Edits by Ryan Hutchison 5 | * Credit: https://github.com/paulyoder/angular-bootstrap-show-errors */ 6 | 7 | angular.module('core') 8 | .directive('showErrors', ['$timeout', '$interpolate', function($timeout, $interpolate) { 9 | var linkFn = function(scope, el, attrs, formCtrl) { 10 | var inputEl, inputName, inputNgEl, options, showSuccess, toggleClasses, 11 | initCheck = false, 12 | showValidationMessages = false, 13 | blurred = false; 14 | 15 | options = scope.$eval(attrs.showErrors) || {}; 16 | showSuccess = options.showSuccess || false; 17 | inputEl = el[0].querySelector('.form-control[name]') || el[0].querySelector('[name]'); 18 | inputNgEl = angular.element(inputEl); 19 | inputName = $interpolate(inputNgEl.attr('name') || '')(scope); 20 | 21 | if (!inputName) { 22 | throw 'show-errors element has no child input elements with a \'name\' attribute class'; 23 | } 24 | 25 | var reset = function() { 26 | return $timeout(function() { 27 | el.removeClass('has-error'); 28 | el.removeClass('has-success'); 29 | showValidationMessages = false; 30 | }, 0, false); 31 | }; 32 | 33 | scope.$watch(function() { 34 | return formCtrl[inputName] && formCtrl[inputName].$invalid; 35 | }, function(invalid) { 36 | return toggleClasses(invalid); 37 | }); 38 | 39 | scope.$on('show-errors-check-validity', function(event, name) { 40 | if (angular.isUndefined(name) || formCtrl.$name === name) { 41 | initCheck = true; 42 | showValidationMessages = true; 43 | 44 | return toggleClasses(formCtrl[inputName].$invalid); 45 | } 46 | }); 47 | 48 | scope.$on('show-errors-reset', function(event, name) { 49 | if (angular.isUndefined(name) || formCtrl.$name === name) { 50 | return reset(); 51 | } 52 | }); 53 | 54 | toggleClasses = function(invalid) { 55 | el.toggleClass('has-error', showValidationMessages && invalid); 56 | if (showSuccess) { 57 | return el.toggleClass('has-success', showValidationMessages && !invalid); 58 | } 59 | }; 60 | }; 61 | 62 | return { 63 | restrict: 'A', 64 | require: '^form', 65 | compile: function(elem, attrs) { 66 | if (attrs.showErrors.indexOf('skipFormGroupCheck') === -1) { 67 | if (!(elem.hasClass('form-group') || elem.hasClass('input-group'))) { 68 | throw 'show-errors element does not have the \'form-group\' or \'input-group\' class'; 69 | } 70 | } 71 | return linkFn; 72 | } 73 | }; 74 | }]); -------------------------------------------------------------------------------- /modules/core/client/img/brand/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanjs-stack/seanjs/9477303776600406249499a3f640634ae719af72/modules/core/client/img/brand/favicon.ico -------------------------------------------------------------------------------- /modules/core/client/img/brand/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanjs-stack/seanjs/9477303776600406249499a3f640634ae719af72/modules/core/client/img/brand/favicon.png -------------------------------------------------------------------------------- /modules/core/client/img/brand/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanjs-stack/seanjs/9477303776600406249499a3f640634ae719af72/modules/core/client/img/brand/logo.png -------------------------------------------------------------------------------- /modules/core/client/img/loaders/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanjs-stack/seanjs/9477303776600406249499a3f640634ae719af72/modules/core/client/img/loaders/loader.gif -------------------------------------------------------------------------------- /modules/core/client/img/sample/angularjs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanjs-stack/seanjs/9477303776600406249499a3f640634ae719af72/modules/core/client/img/sample/angularjs.png -------------------------------------------------------------------------------- /modules/core/client/img/sample/express.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanjs-stack/seanjs/9477303776600406249499a3f640634ae719af72/modules/core/client/img/sample/express.png -------------------------------------------------------------------------------- /modules/core/client/img/sample/nodejs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanjs-stack/seanjs/9477303776600406249499a3f640634ae719af72/modules/core/client/img/sample/nodejs.png -------------------------------------------------------------------------------- /modules/core/client/img/sample/sequelize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanjs-stack/seanjs/9477303776600406249499a3f640634ae719af72/modules/core/client/img/sample/sequelize.png -------------------------------------------------------------------------------- /modules/core/client/scss/core.scss: -------------------------------------------------------------------------------- 1 | .seanjs { 2 | color: #682929; 3 | } 4 | .seanjs-capital { 5 | font-weight: bold; 6 | color: #9B3532; 7 | } 8 | .content { 9 | margin-top: 50px; 10 | } 11 | .undecorated-link:hover { 12 | text-decoration: none; 13 | } 14 | [ng\:cloak], [ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 15 | display: none !important; 16 | } 17 | .header-profile-image { 18 | opacity: 0.8; 19 | height: 28px; 20 | width: 28px; 21 | border-radius: 50%; 22 | margin-right: 5px; 23 | } 24 | .header-profile-image-default { 25 | height: 28px; 26 | width: 28px; 27 | margin-right: 5px; 28 | } 29 | .open .header-profile-image, a:hover .header-profile-image { 30 | opacity: 1; 31 | } 32 | .user-header-dropdown-toggle { 33 | padding-top: 11px !important; 34 | padding-bottom: 11px !important; 35 | } 36 | .error-text { 37 | display: none; 38 | } 39 | .has-error .help-block.error-text { 40 | display: block; 41 | } 42 | .has-error .help-inline.error-text { 43 | display: inline; 44 | } 45 | 46 | /* navbar */ 47 | 48 | .navbar-seanjs { 49 | background-color: #2B313D; 50 | border-color: #E7E7E7; 51 | } 52 | 53 | /* title */ 54 | 55 | .navbar-seanjs .navbar-brand { 56 | color: #f1f1f1; 57 | } 58 | .navbar-seanjs .navbar-brand:hover, .navbar-seanjs .navbar-brand:focus { 59 | color: #fff; 60 | } 61 | 62 | /* link */ 63 | 64 | .navbar-seanjs .navbar-nav > li > a { 65 | color: #f1f1f1; 66 | } 67 | .navbar-seanjs .navbar-nav > li > a:hover, .navbar-seanjs .navbar-nav > li > a:focus { 68 | color: #333; 69 | } 70 | .navbar-seanjs .navbar-nav > .active > a, .navbar-seanjs .navbar-nav > .active > a:hover, .navbar-seanjs .navbar-nav > .active > a:focus { 71 | color: #555; 72 | background-color: #E7E7E7; 73 | } 74 | .navbar-seanjs .navbar-nav > .open > a, .navbar-seanjs .navbar-nav > .open > a:hover, .navbar-seanjs .navbar-nav > .open > a:focus { 75 | color: #555; 76 | background-color: #D5D5D5; 77 | } 78 | 79 | /* caret */ 80 | 81 | .navbar-seanjs .navbar-nav > .dropdown > a .caret { 82 | border-top-color: #f1f1f1; 83 | border-bottom-color: #f1f1f1; 84 | } 85 | .navbar-seanjs .navbar-nav > .dropdown > a:hover .caret, .navbar-seanjs .navbar-nav > .dropdown > a:focus .caret { 86 | border-top-color: #333; 87 | border-bottom-color: #333; 88 | } 89 | .navbar-seanjs .navbar-nav > .open > a .caret, .navbar-seanjs .navbar-nav > .open > a:hover .caret, .navbar-seanjs .navbar-nav > .open > a:focus .caret { 90 | border-top-color: #555; 91 | border-bottom-color: #555; 92 | } 93 | 94 | /* mobile version */ 95 | 96 | .navbar-seanjs .navbar-toggle { 97 | border-color: #DDD; 98 | } 99 | .navbar-seanjs .navbar-toggle:hover, .navbar-seanjs .navbar-toggle:focus { 100 | background-color: #DDD; 101 | } 102 | .navbar-seanjs .navbar-toggle .icon-bar { 103 | background-color: #CCC; 104 | } 105 | @media (max-width: 767px) { 106 | .navbar-seanjs .navbar-nav .open .dropdown-menu > li > a { 107 | color: #777; 108 | } 109 | .navbar-seanjs .navbar-nav .open .dropdown-menu > li > a:hover, .navbar-seanjs .navbar-nav .open .dropdown-menu > li > a:focus { 110 | color: #333; 111 | } 112 | } 113 | .navbar-nav > li > a { 114 | text-shadow: none; 115 | } 116 | .footer { 117 | padding: 30px; 118 | background: #eee; 119 | border-top: 1px solid #333; 120 | } 121 | -------------------------------------------------------------------------------- /modules/core/client/services/contact.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Contact form service 4 | angular.module('core').factory('ContactForm', ['$resource', 5 | function($resource) { 6 | return $resource('api/contact', {}, { 7 | update: { 8 | method: 'POST' 9 | } 10 | }); 11 | } 12 | ]); 13 | -------------------------------------------------------------------------------- /modules/core/client/services/interceptors/auth.interceptor.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('core').factory('authInterceptor', ['$q', '$injector', 4 | function($q, $injector) { 5 | return { 6 | responseError: function(rejection) { 7 | if (!rejection.config.ignoreAuthModule) { 8 | switch (rejection.status) { 9 | case 401: 10 | $injector.get('$state').transitionTo('authentication.signin'); 11 | break; 12 | case 403: 13 | $injector.get('$state').transitionTo('forbidden'); 14 | break; 15 | case 404: 16 | $injector.get('$state').transitionTo('not-found'); 17 | break; 18 | } 19 | } 20 | // otherwise, default behaviour 21 | return $q.reject(rejection); 22 | } 23 | }; 24 | } 25 | ]); -------------------------------------------------------------------------------- /modules/core/client/services/socket.io.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Create the Socket.io wrapper service 4 | angular.module('core').service('Socket', ['Authentication', '$state', '$timeout', 5 | function(Authentication, $state, $timeout) { 6 | // Connect to Socket.io server 7 | this.connect = function() { 8 | // Connect only when authenticated 9 | if (Authentication.user) { 10 | this.socket = io(); 11 | } 12 | }; 13 | this.connect(); 14 | 15 | // Wrap the Socket.io 'on' method 16 | this.on = function(eventName, callback) { 17 | if (this.socket) { 18 | this.socket.on(eventName, function(data) { 19 | $timeout(function() { 20 | callback(data); 21 | }); 22 | }); 23 | } 24 | }; 25 | 26 | // Wrap the Socket.io 'emit' method 27 | this.emit = function(eventName, data) { 28 | if (this.socket) { 29 | this.socket.emit(eventName, data); 30 | } 31 | }; 32 | 33 | // Wrap the Socket.io 'removeListener' method 34 | this.removeListener = function(eventName) { 35 | if (this.socket) { 36 | this.socket.removeListener(eventName); 37 | } 38 | }; 39 | } 40 | ]); -------------------------------------------------------------------------------- /modules/core/client/views/400.client.view.html: -------------------------------------------------------------------------------- 1 |

Bad Request

2 | -------------------------------------------------------------------------------- /modules/core/client/views/403.client.view.html: -------------------------------------------------------------------------------- 1 |

Forbidden

2 | -------------------------------------------------------------------------------- /modules/core/client/views/404.client.view.html: -------------------------------------------------------------------------------- 1 |

Page Not Found

2 | -------------------------------------------------------------------------------- /modules/core/client/views/contact.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 6 | 7 |
8 | 9 |
10 | 11 | 12 | 13 | 20 | 21 | 22 | 30 | 31 | 32 | 43 | 44 | 45 | 52 | 53 | 54 | 57 | 58 | 59 | 67 | 68 | 69 | 74 | 75 |
14 | 15 | 16 |
17 |

Your name is required.

18 |
19 |
23 | 24 | 25 |
26 |

Your email address is required.

27 |

Your email address is invalid.

28 |
29 |
33 | 34 | 39 |
40 |

Your message is required.

41 |
42 |
46 | 47 | 48 |
49 |

Your message is required.

50 |
51 |
55 |
56 |
60 |
61 | 62 |
63 |
64 | 65 |
66 |
70 | 73 |
76 | 77 |
78 | 79 |
80 | 81 |
82 | -------------------------------------------------------------------------------- /modules/core/client/views/footer.client.view.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /modules/core/client/views/header.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 11 | 66 |
67 | -------------------------------------------------------------------------------- /modules/core/server/controllers/core.server.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'), 4 | config = require(path.resolve('./config/config')), 5 | reCaptcha = require(path.resolve('./config/lib/reCaptcha')), 6 | async = require('async'), 7 | nodemailer = require('nodemailer'); 8 | 9 | var smtpTransport = nodemailer.createTransport(config.mailer.options); 10 | 11 | 12 | /** 13 | * Render the main application page 14 | */ 15 | exports.renderIndex = function(req, res) { 16 | res.render('modules/core/server/views/index', { 17 | user: req.user || null 18 | }); 19 | }; 20 | 21 | /** 22 | * Render the server error page 23 | */ 24 | exports.renderServerError = function(req, res) { 25 | res.status(500).render('modules/core/server/views/500', { 26 | error: 'Oops! Something went wrong...' 27 | }); 28 | }; 29 | 30 | /** 31 | * Render the server not found responses 32 | * Performs content-negotiation on the Accept HTTP header 33 | */ 34 | exports.renderNotFound = function(req, res) { 35 | 36 | res.status(404).format({ 37 | 'text/html': function() { 38 | res.render('modules/core/server/views/404', { 39 | url: req.originalUrl 40 | }); 41 | }, 42 | 'application/json': function() { 43 | res.json({ 44 | error: 'Path not found' 45 | }); 46 | }, 47 | 'default': function() { 48 | res.send('Path not found'); 49 | } 50 | }); 51 | }; 52 | 53 | exports.contact = function(req, res, next) { 54 | 55 | //Let's do stuff in steps... 56 | async.waterfall([ 57 | function(done) { 58 | //Verify the captcha 59 | reCaptcha.verify(req.body.grecaptcha, function(response) { 60 | if (!response) { 61 | done(null); 62 | } else { 63 | done("Invalid captcha!"); 64 | } 65 | }); 66 | }, 67 | function(done) { 68 | // Prepare the contact form email template 69 | res.render(path.resolve('modules/core/server/templates/contact-form-email'), { 70 | name: req.body.name, 71 | email: req.body.email, 72 | message: req.body.message, 73 | subject: req.body.subject 74 | }, function(err, emailHTML) { 75 | done(err, emailHTML); 76 | }); 77 | }, 78 | function(emailHTML, done) { 79 | //Send the email to the admin 80 | var mailOptions = { 81 | to: config.mailer.from, 82 | from: config.mailer.from, 83 | subject: req.body.name + ' contacted you on contact us form', 84 | html: emailHTML 85 | }; 86 | 87 | smtpTransport.sendMail(mailOptions, function(err) { 88 | if (err) { 89 | done('Failed to send the email, please try again later.'); 90 | } else { 91 | return res.send({ 92 | message: 'Thank you for contacting us! We will get back to you as soon as possible!' 93 | }); 94 | } 95 | }); 96 | } 97 | ], function(err) { 98 | if (err) { 99 | return res.status(400).send({ 100 | message: err 101 | }); 102 | } 103 | }); 104 | 105 | 106 | }; 107 | -------------------------------------------------------------------------------- /modules/core/server/controllers/errors.server.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Get unique error field name 5 | */ 6 | var getUniqueErrorMessage = function(err) { 7 | var output; 8 | 9 | try { 10 | var fieldName = err.errmsg.substring(err.errmsg.lastIndexOf('.$') + 2, err.errmsg.lastIndexOf('_1')); 11 | output = fieldName.charAt(0).toUpperCase() + fieldName.slice(1) + ' already exists'; 12 | 13 | } catch (ex) { 14 | output = 'Unique field already exists'; 15 | } 16 | 17 | return output; 18 | }; 19 | 20 | /** 21 | * Get the error message from error object 22 | */ 23 | exports.getErrorMessage = function(err) { 24 | var message = ''; 25 | 26 | if (err.code) { 27 | switch (err.code) { 28 | case 11000: 29 | case 11001: 30 | message = getUniqueErrorMessage(err); 31 | break; 32 | default: 33 | message = 'Something went wrong'; 34 | } 35 | } else { 36 | for (var errName in err.errors) { 37 | if (err.errors[errName].message) { 38 | message = err.errors[errName].message; 39 | } 40 | } 41 | } 42 | 43 | return message; 44 | }; -------------------------------------------------------------------------------- /modules/core/server/routes/core.server.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(app) { 4 | // Root routing 5 | var core = require('../controllers/core.server.controller'); 6 | 7 | // Define error pages 8 | app.route('/server-error').get(core.renderServerError); 9 | 10 | // Return a 404 for all undefined api, module or lib routes 11 | app.route('/:url(api|modules|lib)/*').get(core.renderNotFound); 12 | 13 | // Define application route 14 | app.route('/*').get(core.renderIndex); 15 | 16 | //Submit contact form data 17 | app.route('/api/contact').post(core.contact); 18 | }; 19 | -------------------------------------------------------------------------------- /modules/core/server/templates/contact-form-email.server.view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

Dear Admin,

11 |

You have a new email from the contact from

12 |
13 | 14 |

Name: {{name}}

15 |

Email: {{email}}

16 |

Subject: {{subject}}

17 |

Message: {{message}}

18 | 19 |
20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /modules/core/server/views/404.server.view.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.server.view.html' %} {% block content %} 2 |

Page Not Found

3 | 8 | {% endblock %} -------------------------------------------------------------------------------- /modules/core/server/views/500.server.view.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.server.view.html' %} {% block content %} 2 |

Server Error

3 |
4 |   {{error}}
5 | 
6 | {% endblock %} -------------------------------------------------------------------------------- /modules/core/server/views/index.server.view.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.server.view.html' %} {% block content %} 2 |
3 | {% endblock %} -------------------------------------------------------------------------------- /modules/core/server/views/layout.server.view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{title}} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | {% for cssFile in cssFiles %}{% endfor %} 36 | 37 | 38 | 39 | 40 |
41 |
42 | {% block content %}{% endblock %} 43 |
44 |
45 |
46 | 47 | 48 | 51 | 52 | 53 | 54 | 55 | 56 | {% for jsFile in jsFiles %}{% endfor %} 57 | 58 | {% if livereload %} 59 | 60 | 61 | {% endif %} 62 | 63 | 64 | {% if googleAnalyticsTrackingID %} 65 | 66 | 75 | 76 | {% endif %} 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /modules/core/tests/client/core.client.tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | beforeAll(function() { 5 | angular.element(document.querySelector('head')).append(''); 6 | }); 7 | }()); -------------------------------------------------------------------------------- /modules/core/tests/client/header.client.controller.tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | describe('HeaderController', function() { 5 | //Initialize global variables 6 | var scope, 7 | HeaderController, 8 | $state, 9 | Authentication; 10 | 11 | // Load the main application module 12 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 13 | 14 | beforeEach(inject(function($controller, $rootScope, _$state_, _Authentication_) { 15 | scope = $rootScope.$new(); 16 | $state = _$state_; 17 | Authentication = _Authentication_; 18 | 19 | HeaderController = $controller('HeaderController', { 20 | $scope: scope 21 | }); 22 | })); 23 | 24 | it('should expose the authentication service', function() { 25 | expect(scope.authentication).toBe(Authentication); 26 | }); 27 | 28 | it('should expose the $state service', function() { 29 | expect(scope.$state).toBe($state); 30 | }); 31 | 32 | it('should default menu to collapsed', function() { 33 | expect(scope.isCollapsed).toBeFalsy(); 34 | }); 35 | 36 | describe('when toggleCollapsibleMenu', function() { 37 | var defaultCollapse; 38 | beforeEach(function() { 39 | defaultCollapse = scope.isCollapsed; 40 | scope.toggleCollapsibleMenu(); 41 | }); 42 | 43 | it('should toggle isCollapsed to non default value', function() { 44 | expect(scope.isCollapsed).not.toBe(defaultCollapse); 45 | }); 46 | 47 | it('should then toggle isCollapsed back to default value', function() { 48 | scope.toggleCollapsibleMenu(); 49 | expect(scope.isCollapsed).toBe(defaultCollapse); 50 | }); 51 | }); 52 | 53 | describe('when view state changes', function() { 54 | beforeEach(function() { 55 | scope.isCollapsed = true; 56 | scope.$broadcast('$stateChangeSuccess'); 57 | }); 58 | 59 | it('should set isCollapsed to false', function() { 60 | expect(scope.isCollapsed).toBeFalsy(); 61 | }); 62 | }); 63 | }); 64 | })(); -------------------------------------------------------------------------------- /modules/core/tests/client/home.client.controller.tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | describe('HomeController', function() { 5 | //Initialize global variables 6 | var scope, 7 | HomeController; 8 | 9 | // Load the main application module 10 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 11 | 12 | beforeEach(inject(function($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | 15 | HomeController = $controller('HomeController', { 16 | $scope: scope 17 | }); 18 | })); 19 | 20 | it('should expose the authentication service', function() { 21 | expect(scope.authentication).toBeTruthy(); 22 | }); 23 | }); 24 | })(); -------------------------------------------------------------------------------- /modules/core/tests/client/interceptors/auth.interceptor.client.tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function() { 4 | describe('authInterceptor', function() { 5 | //Initialize global variables 6 | var authInterceptor, 7 | $q, 8 | $state, 9 | httpProvider; 10 | 11 | // Load the main application module 12 | beforeEach(module(ApplicationConfiguration.applicationModuleName)); 13 | 14 | //Load httpProvider 15 | beforeEach(module(function($httpProvider) { 16 | httpProvider = $httpProvider; 17 | })); 18 | 19 | beforeEach(inject(function(_authInterceptor_, _$q_, _$state_) { 20 | authInterceptor = _authInterceptor_; 21 | $q = _$q_; 22 | $state = _$state_; 23 | spyOn($q, 'reject'); 24 | spyOn($state, 'transitionTo'); 25 | })); 26 | 27 | it('Auth Interceptor should be object', function() { 28 | expect(typeof authInterceptor).toEqual('object'); 29 | }); 30 | 31 | it('Auth Interceptor should contain responseError function', function() { 32 | expect(typeof authInterceptor.responseError).toEqual('function'); 33 | }); 34 | 35 | it('httpProvider Interceptor should have authInterceptor', function() { 36 | expect(httpProvider.interceptors).toContain('authInterceptor'); 37 | }); 38 | 39 | describe('Forbidden Interceptor', function() { 40 | it('should redirect to forbidden route', function() { 41 | var response = { 42 | status: 403, 43 | config: {} 44 | }; 45 | var promise = authInterceptor.responseError(response); 46 | expect($q.reject).toHaveBeenCalled(); 47 | expect($state.transitionTo).toHaveBeenCalledWith('forbidden'); 48 | }); 49 | }); 50 | 51 | describe('Authorization Interceptor', function() { 52 | it('should redirect to signIn page for unauthorized access', function() { 53 | var response = { 54 | status: 401, 55 | config: {} 56 | }; 57 | var promise = authInterceptor.responseError(response); 58 | expect($q.reject).toHaveBeenCalled(); 59 | expect($state.transitionTo).toHaveBeenCalledWith('authentication.signin'); 60 | }); 61 | }); 62 | }); 63 | })(); -------------------------------------------------------------------------------- /modules/core/tests/client/socket.io.client.service.tests.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /* Creates a mock of socket.io for the browser. 5 | * Functionality of the service is tested through 6 | * the chat controller tests. 7 | */ 8 | window.io = function() { 9 | this.cbs = {}; 10 | this.on = function(msg, cb) { 11 | this.cbs[msg] = cb; 12 | }; 13 | this.emit = function(msg, data) { 14 | this.cbs[msg](data); 15 | }; 16 | this.removeListener = function(msg) { 17 | delete this.cbs[msg]; 18 | }; 19 | this.connect = function() { 20 | this.socket = {}; 21 | }; 22 | return this; 23 | }; 24 | })(); -------------------------------------------------------------------------------- /modules/users/client/config/user-admin.client.menus.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Configuring the Articles module 4 | angular.module('user.admin').run(['Menus', 5 | function(Menus) { 6 | Menus.addSubMenuItem('topbar', 'admin', { 7 | title: 'Manage Users', 8 | state: 'admin.users' 9 | }); 10 | } 11 | ]); -------------------------------------------------------------------------------- /modules/users/client/config/user-admin.client.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Setting up route 4 | angular.module('user.admin.routes').config(['$stateProvider', 5 | function($stateProvider) { 6 | $stateProvider 7 | .state('admin.users', { 8 | url: '/users', 9 | templateUrl: 'modules/users/client/views/admin/list-users.client.view.html', 10 | controller: 'UserListController' 11 | }) 12 | .state('admin.user', { 13 | url: '/user/:userId', 14 | templateUrl: 'modules/users/client/views/admin/view-user.client.view.html', 15 | controller: 'UserController', 16 | resolve: { 17 | userResolve: ['$stateParams', 'Admin', function($stateParams, Admin) { 18 | return Admin.get({ 19 | userId: $stateParams.userId 20 | }); 21 | }] 22 | } 23 | }) 24 | .state('admin.user-edit', { 25 | url: '/user/:userId/edit', 26 | templateUrl: 'modules/users/client/views/admin/edit-user.client.view.html', 27 | controller: 'UserController', 28 | resolve: { 29 | userResolve: ['$stateParams', 'Admin', function($stateParams, Admin) { 30 | return Admin.get({ 31 | userId: $stateParams.userId 32 | }); 33 | }] 34 | } 35 | }); 36 | } 37 | ]); -------------------------------------------------------------------------------- /modules/users/client/config/user.client.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Config HTTP Error Handling 4 | angular.module('user').config(['$httpProvider', 5 | function($httpProvider) { 6 | // Set the httpProvider "not authorized" interceptor 7 | $httpProvider.interceptors.push(['$q', '$location', 'Authentication', 8 | function($q, $location, Authentication) { 9 | return { 10 | responseError: function(rejection) { 11 | switch (rejection.status) { 12 | case 401: 13 | // Deauthenticate the global user 14 | Authentication.user = null; 15 | 16 | // Redirect to signin page 17 | $location.path('signin'); 18 | break; 19 | case 403: 20 | // Add unauthorized behaviour 21 | break; 22 | } 23 | 24 | return $q.reject(rejection); 25 | } 26 | }; 27 | } 28 | ]); 29 | } 30 | ]); -------------------------------------------------------------------------------- /modules/users/client/config/user.client.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Setting up route 4 | angular.module('user').config(['$stateProvider', 5 | function($stateProvider) { 6 | // User state routing 7 | $stateProvider 8 | .state('settings', { 9 | abstract: true, 10 | url: '/settings', 11 | templateUrl: 'modules/users/client/views/settings/settings.client.view.html', 12 | data: { 13 | roles: ['user', 'admin'] 14 | } 15 | }) 16 | .state('settings.profile', { 17 | url: '/profile', 18 | templateUrl: 'modules/users/client/views/settings/edit-profile.client.view.html' 19 | }) 20 | .state('settings.password', { 21 | url: '/password', 22 | templateUrl: 'modules/users/client/views/settings/change-password.client.view.html' 23 | }) 24 | .state('settings.accounts', { 25 | url: '/accounts', 26 | templateUrl: 'modules/users/client/views/settings/manage-social-accounts.client.view.html' 27 | }) 28 | .state('settings.picture', { 29 | url: '/picture', 30 | templateUrl: 'modules/users/client/views/settings/change-profile-picture.client.view.html' 31 | }) 32 | .state('authentication', { 33 | abstract: true, 34 | url: '/authentication', 35 | templateUrl: 'modules/users/client/views/authentication/authentication.client.view.html' 36 | }) 37 | .state('authentication.signup', { 38 | url: '/signup', 39 | templateUrl: 'modules/users/client/views/authentication/signup.client.view.html' 40 | }) 41 | .state('authentication.signin', { 42 | url: '/signin?err', 43 | templateUrl: 'modules/users/client/views/authentication/signin.client.view.html' 44 | }) 45 | .state('password', { 46 | abstract: true, 47 | url: '/password', 48 | template: '' 49 | }) 50 | .state('password.forgot', { 51 | url: '/forgot', 52 | templateUrl: 'modules/users/client/views/password/forgot-password.client.view.html' 53 | }) 54 | .state('password.reset', { 55 | abstract: true, 56 | url: '/reset', 57 | template: '' 58 | }) 59 | .state('password.reset.invalid', { 60 | url: '/invalid', 61 | templateUrl: 'modules/users/client/views/password/reset-password-invalid.client.view.html' 62 | }) 63 | .state('password.reset.success', { 64 | url: '/success', 65 | templateUrl: 'modules/users/client/views/password/reset-password-success.client.view.html' 66 | }) 67 | .state('password.reset.form', { 68 | url: '/:token', 69 | templateUrl: 'modules/users/client/views/password/reset-password.client.view.html' 70 | }); 71 | } 72 | ]); -------------------------------------------------------------------------------- /modules/users/client/controllers/admin/list-users.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('user.admin').controller('UserListController', ['$scope', '$filter', 'Admin', 4 | function($scope, $filter, Admin) { 5 | 6 | Admin.query(function(data) { 7 | $scope.users = data; 8 | $scope.buildPager(); 9 | }); 10 | 11 | $scope.buildPager = function() { 12 | $scope.pagedItems = []; 13 | $scope.itemsPerPage = 15; 14 | $scope.currentPage = 1; 15 | $scope.figureOutItemsToDisplay(); 16 | }; 17 | 18 | $scope.figureOutItemsToDisplay = function() { 19 | $scope.filteredItems = $filter('filter')($scope.users, { 20 | $: $scope.search 21 | }); 22 | $scope.filterLength = $scope.filteredItems.length; 23 | var begin = (($scope.currentPage - 1) * $scope.itemsPerPage); 24 | var end = begin + $scope.itemsPerPage; 25 | $scope.pagedItems = $scope.filteredItems.slice(begin, end); 26 | }; 27 | 28 | $scope.pageChanged = function() { 29 | $scope.figureOutItemsToDisplay(); 30 | }; 31 | } 32 | ]); -------------------------------------------------------------------------------- /modules/users/client/controllers/admin/user.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('user.admin').controller('UserController', ['$scope', '$state', 'Authentication', 'userResolve', 4 | function($scope, $state, Authentication, userResolve) { 5 | 6 | $scope.authentication = Authentication; 7 | $scope.user = userResolve; 8 | 9 | $scope.remove = function() { 10 | 11 | if (confirm('Are you sure you want to delete this user?')) { 12 | 13 | var user = $scope.user; 14 | user.$remove({ 15 | 'userId': user.id 16 | }, function() { 17 | $state.go('admin.users'); 18 | }, function(errorResponse) { 19 | $scope.error = errorResponse.data.message; 20 | }); 21 | 22 | 23 | } 24 | }; 25 | 26 | $scope.update = function(isValid) { 27 | if (!isValid) { 28 | $scope.$broadcast('show-errors-check-validity', 'userForm'); 29 | return false; 30 | } 31 | 32 | var user = $scope.user; 33 | 34 | user.$update({ 35 | 'userId': user.id 36 | }, function() { 37 | $state.go('admin.user', { 38 | userId: user.id 39 | }); 40 | }, function(errorResponse) { 41 | $scope.error = errorResponse.data.message; 42 | }); 43 | }; 44 | } 45 | ]); -------------------------------------------------------------------------------- /modules/users/client/controllers/authentication.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('user').controller('AuthenticationController', ['$scope', '$state', '$http', '$location', '$window', 'Authentication', 'PasswordValidator', 4 | function($scope, $state, $http, $location, $window, Authentication, PasswordValidator) { 5 | $scope.authentication = Authentication; 6 | $scope.popoverMsg = PasswordValidator.getPopoverMsg(); 7 | 8 | // Get an eventual error defined in the URL query string: 9 | $scope.error = $location.search().err; 10 | 11 | // If user is signed in then redirect back home 12 | if ($scope.authentication.user) { 13 | $location.path('/'); 14 | } 15 | 16 | $scope.signup = function(isValid) { 17 | $scope.error = null; 18 | 19 | if (!isValid) { 20 | $scope.$broadcast('show-errors-check-validity', 'userForm'); 21 | 22 | return false; 23 | } 24 | 25 | $http.post('/api/auth/signup', $scope.credentials).success(function(response) { 26 | // If successful we assign the response to the global user model 27 | $scope.authentication.user = response; 28 | 29 | // And redirect to the previous or home page 30 | $state.go($state.previous.state.name || 'home', $state.previous.params); 31 | }).error(function(response) { 32 | $scope.error = response.message; 33 | }); 34 | }; 35 | 36 | $scope.signin = function(isValid) { 37 | $scope.error = null; 38 | 39 | if (!isValid) { 40 | $scope.$broadcast('show-errors-check-validity', 'userForm'); 41 | 42 | return false; 43 | } 44 | 45 | $http.post('/api/auth/signin', $scope.credentials).success(function(response) { 46 | // If successful we assign the response to the global user model 47 | $scope.authentication.user = response; 48 | 49 | // And redirect to the previous or home page 50 | $state.go($state.previous.state.name || 'home', $state.previous.params); 51 | }).error(function(response) { 52 | $scope.error = response.message; 53 | }); 54 | }; 55 | 56 | // OAuth provider request 57 | $scope.callOauthProvider = function(url) { 58 | if ($state.previous && $state.previous.href) { 59 | url += '?redirect_to=' + encodeURIComponent($state.previous.href); 60 | } 61 | 62 | // Effectively call OAuth authentication route: 63 | $window.location.href = url; 64 | }; 65 | } 66 | ]); -------------------------------------------------------------------------------- /modules/users/client/controllers/password.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('user').controller('PasswordController', ['$scope', '$stateParams', '$http', '$location', 'Authentication', 'PasswordValidator', 4 | function($scope, $stateParams, $http, $location, Authentication, PasswordValidator) { 5 | $scope.authentication = Authentication; 6 | $scope.popoverMsg = PasswordValidator.getPopoverMsg(); 7 | 8 | //If user is signed in then redirect back home 9 | if ($scope.authentication.user) { 10 | $location.path('/'); 11 | } 12 | 13 | // Submit forgotten password account id 14 | $scope.askForPasswordReset = function(isValid) { 15 | $scope.success = $scope.error = null; 16 | 17 | if (!isValid) { 18 | $scope.$broadcast('show-errors-check-validity', 'forgotPasswordForm'); 19 | 20 | return false; 21 | } 22 | 23 | $http.post('/api/auth/forgot', $scope.credentials).success(function(response) { 24 | // Show user success message and clear form 25 | $scope.credentials = null; 26 | $scope.success = response.message; 27 | 28 | }).error(function(response) { 29 | // Show user error message and clear form 30 | $scope.credentials = null; 31 | $scope.error = response.message; 32 | }); 33 | }; 34 | 35 | // Change user password 36 | $scope.resetUserPassword = function(isValid) { 37 | $scope.success = $scope.error = null; 38 | 39 | if (!isValid) { 40 | $scope.$broadcast('show-errors-check-validity', 'resetPasswordForm'); 41 | 42 | return false; 43 | } 44 | 45 | $http.post('/api/auth/reset/' + $stateParams.token, $scope.passwordDetails).success(function(response) { 46 | // If successful show success message and clear form 47 | $scope.passwordDetails = null; 48 | 49 | // Attach user profile 50 | Authentication.user = response; 51 | 52 | // And redirect to the index page 53 | $location.path('/password/reset/success'); 54 | }).error(function(response) { 55 | $scope.error = response.message; 56 | }); 57 | }; 58 | } 59 | ]); -------------------------------------------------------------------------------- /modules/users/client/controllers/settings/change-password.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('user').controller('ChangePasswordController', ['$scope', '$http', 'Authentication', 'PasswordValidator', 4 | function($scope, $http, Authentication, PasswordValidator) { 5 | $scope.user = Authentication.user; 6 | $scope.popoverMsg = PasswordValidator.getPopoverMsg(); 7 | 8 | // Change user password 9 | $scope.changeUserPassword = function(isValid) { 10 | $scope.success = $scope.error = null; 11 | 12 | if (!isValid) { 13 | $scope.$broadcast('show-errors-check-validity', 'passwordForm'); 14 | 15 | return false; 16 | } 17 | 18 | $http.post('/api/user/password', $scope.passwordDetails).success(function(response) { 19 | // If successful show success message and clear form 20 | $scope.$broadcast('show-errors-reset', 'passwordForm'); 21 | $scope.success = true; 22 | $scope.passwordDetails = null; 23 | }).error(function(response) { 24 | $scope.error = response.message; 25 | }); 26 | }; 27 | } 28 | ]); -------------------------------------------------------------------------------- /modules/users/client/controllers/settings/change-profile-picture.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('user').controller('ChangeProfilePictureController', ['$scope', '$timeout', '$window', 'Authentication', 'FileUploader', 4 | function($scope, $timeout, $window, Authentication, FileUploader) { 5 | $scope.user = Authentication.user; 6 | $scope.imageURL = $scope.user.profileImageURL; 7 | 8 | // Create file uploader instance 9 | $scope.uploader = new FileUploader({ 10 | url: 'api/user/picture' 11 | }); 12 | 13 | // Set file uploader image filter 14 | $scope.uploader.filters.push({ 15 | name: 'imageFilter', 16 | fn: function(item, options) { 17 | var type = '|' + item.type.slice(item.type.lastIndexOf('/') + 1) + '|'; 18 | return '|jpg|png|jpeg|bmp|gif|'.indexOf(type) !== -1; 19 | } 20 | }); 21 | 22 | // Called after the user selected a new picture file 23 | $scope.uploader.onAfterAddingFile = function(fileItem) { 24 | if ($window.FileReader) { 25 | var fileReader = new FileReader(); 26 | fileReader.readAsDataURL(fileItem._file); 27 | 28 | fileReader.onload = function(fileReaderEvent) { 29 | $timeout(function() { 30 | $scope.imageURL = fileReaderEvent.target.result; 31 | }, 0); 32 | }; 33 | } 34 | }; 35 | 36 | // Called after the user has successfully uploaded a new picture 37 | $scope.uploader.onSuccessItem = function(fileItem, response, status, headers) { 38 | // Show success message 39 | $scope.success = true; 40 | 41 | // Populate user object 42 | $scope.user = Authentication.user = response; 43 | 44 | // Clear upload buttons 45 | $scope.cancelUpload(); 46 | }; 47 | 48 | // Called after the user has failed to uploaded a new picture 49 | $scope.uploader.onErrorItem = function(fileItem, response, status, headers) { 50 | // Clear upload buttons 51 | $scope.cancelUpload(); 52 | 53 | // Show error message 54 | $scope.error = response.message; 55 | }; 56 | 57 | // Change user profile picture 58 | $scope.uploadProfilePicture = function() { 59 | // Clear messages 60 | $scope.success = $scope.error = null; 61 | 62 | // Start upload 63 | $scope.uploader.uploadAll(); 64 | }; 65 | 66 | // Cancel the upload process 67 | $scope.cancelUpload = function() { 68 | $scope.uploader.clearQueue(); 69 | $scope.imageURL = $scope.user.profileImageURL; 70 | }; 71 | } 72 | ]); -------------------------------------------------------------------------------- /modules/users/client/controllers/settings/edit-profile.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('user').controller('EditProfileController', ['$scope', '$http', '$location', 'User', 'Authentication', 4 | function($scope, $http, $location, User, Authentication) { 5 | $scope.user = Authentication.user; 6 | 7 | $scope.getProfile = function() { 8 | User.get(function(data) { 9 | $scope.user = data; 10 | }); 11 | }; 12 | 13 | // Update a user profile 14 | $scope.updateUserProfile = function(isValid) { 15 | $scope.success = $scope.error = null; 16 | 17 | if (!isValid) { 18 | $scope.$broadcast('show-errors-check-validity', 'userForm'); 19 | 20 | return false; 21 | } 22 | 23 | var user = new User($scope.user); 24 | 25 | user.$update(function(response) { 26 | $scope.$broadcast('show-errors-reset', 'userForm'); 27 | 28 | $scope.success = true; 29 | Authentication.user = response; 30 | }, function(response) { 31 | $scope.error = response.data.message; 32 | }); 33 | }; 34 | } 35 | ]); -------------------------------------------------------------------------------- /modules/users/client/controllers/settings/manage-social-accounts.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('user').controller('SocialAccountsController', ['$scope', '$http', 'Authentication', 4 | function($scope, $http, Authentication) { 5 | $scope.user = Authentication.user; 6 | 7 | // Check if there are additional accounts 8 | $scope.hasConnectedAdditionalSocialAccounts = function(provider) { 9 | for (var i in $scope.user.additionalProvidersData) { 10 | return true; 11 | } 12 | 13 | return false; 14 | }; 15 | 16 | // Check if provider is already in use with current user 17 | $scope.isConnectedSocialAccount = function(provider) { 18 | return $scope.user.provider === provider || ($scope.user.additionalProvidersData && $scope.user.additionalProvidersData[provider]); 19 | }; 20 | 21 | // Remove a user social account 22 | $scope.removeUserSocialAccount = function(provider) { 23 | $scope.success = $scope.error = null; 24 | 25 | $http.delete('/api/user/accounts', { 26 | params: { 27 | provider: provider 28 | } 29 | }).success(function(response) { 30 | // If successful show success message and clear form 31 | $scope.success = true; 32 | $scope.user = Authentication.user = response; 33 | }).error(function(response) { 34 | $scope.error = response.message; 35 | }); 36 | }; 37 | } 38 | ]); -------------------------------------------------------------------------------- /modules/users/client/controllers/settings/settings.client.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('user').controller('SettingsController', ['$scope', 'Authentication', 4 | function($scope, Authentication) { 5 | $scope.user = Authentication.user; 6 | } 7 | ]); -------------------------------------------------------------------------------- /modules/users/client/directives/password-validator.client.directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('user') 4 | .directive('passwordValidator', ['PasswordValidator', function(PasswordValidator) { 5 | return { 6 | require: 'ngModel', 7 | link: function(scope, element, attrs, modelCtrl) { 8 | modelCtrl.$parsers.unshift(function(password) { 9 | var result = PasswordValidator.getResult(password); 10 | var strengthIdx = 0; 11 | 12 | // Strength Meter - visual indicator for users 13 | var strengthMeter = [{ 14 | color: "danger", 15 | progress: "20" 16 | }, { 17 | color: "warning", 18 | progress: "40" 19 | }, { 20 | color: "info", 21 | progress: "60" 22 | }, { 23 | color: "primary", 24 | progress: "80" 25 | }, { 26 | color: "success", 27 | progress: "100" 28 | }]; 29 | var strengthMax = strengthMeter.length; 30 | 31 | if (result.errors.length < strengthMeter.length) { 32 | strengthIdx = strengthMeter.length - result.errors.length - 1; 33 | } 34 | 35 | scope.strengthColor = strengthMeter[strengthIdx].color; 36 | scope.strengthProgress = strengthMeter[strengthIdx].progress; 37 | 38 | if (result.errors.length) { 39 | scope.popoverMsg = PasswordValidator.getPopoverMsg(); 40 | scope.passwordErrors = result.errors; 41 | modelCtrl.$setValidity('strength', false); 42 | return undefined; 43 | } else { 44 | scope.popoverMsg = ''; 45 | modelCtrl.$setValidity('strength', true); 46 | return password; 47 | } 48 | }); 49 | } 50 | }; 51 | }]); -------------------------------------------------------------------------------- /modules/users/client/directives/password-verify.client.directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('user') 4 | .directive("passwordVerify", function() { 5 | return { 6 | require: "ngModel", 7 | scope: { 8 | passwordVerify: '=' 9 | }, 10 | link: function(scope, element, attrs, modelCtrl) { 11 | scope.$watch(function() { 12 | var combined; 13 | if (scope.passwordVerify || modelCtrl.$viewValue) { 14 | combined = scope.passwordVerify + '_' + modelCtrl.$viewValue; 15 | } 16 | return combined; 17 | }, function(value) { 18 | if (value) { 19 | modelCtrl.$parsers.unshift(function(viewValue) { 20 | var origin = scope.passwordVerify; 21 | if (origin !== viewValue) { 22 | modelCtrl.$setValidity("passwordVerify", false); 23 | return undefined; 24 | } else { 25 | modelCtrl.$setValidity("passwordVerify", true); 26 | return viewValue; 27 | } 28 | }); 29 | } 30 | }); 31 | } 32 | }; 33 | }); -------------------------------------------------------------------------------- /modules/users/client/directives/user.client.directive.js: -------------------------------------------------------------------------------- 1 |  2 | 'use strict'; 3 | 4 | // Users directive used to force lowercase input 5 | angular.module('user').directive('lowercase', function() { 6 | return { 7 | require: 'ngModel', 8 | link: function(scope, element, attrs, modelCtrl) { 9 | modelCtrl.$parsers.push(function(input) { 10 | return input ? input.toLowerCase() : ''; 11 | }); 12 | element.css('text-transform', 'lowercase'); 13 | } 14 | }; 15 | }); -------------------------------------------------------------------------------- /modules/users/client/img/buttons/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanjs-stack/seanjs/9477303776600406249499a3f640634ae719af72/modules/users/client/img/buttons/facebook.png -------------------------------------------------------------------------------- /modules/users/client/img/buttons/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanjs-stack/seanjs/9477303776600406249499a3f640634ae719af72/modules/users/client/img/buttons/github.png -------------------------------------------------------------------------------- /modules/users/client/img/buttons/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanjs-stack/seanjs/9477303776600406249499a3f640634ae719af72/modules/users/client/img/buttons/google.png -------------------------------------------------------------------------------- /modules/users/client/img/buttons/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanjs-stack/seanjs/9477303776600406249499a3f640634ae719af72/modules/users/client/img/buttons/linkedin.png -------------------------------------------------------------------------------- /modules/users/client/img/buttons/paypal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanjs-stack/seanjs/9477303776600406249499a3f640634ae719af72/modules/users/client/img/buttons/paypal.png -------------------------------------------------------------------------------- /modules/users/client/img/buttons/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanjs-stack/seanjs/9477303776600406249499a3f640634ae719af72/modules/users/client/img/buttons/twitter.png -------------------------------------------------------------------------------- /modules/users/client/scss/users.scss: -------------------------------------------------------------------------------- 1 | @media (min-width: 992px) { 2 | .nav-users { 3 | position: fixed; 4 | } 5 | } 6 | .social-account-container { 7 | display: inline-block; 8 | position: relative; 9 | } 10 | .btn-remove-account { 11 | top: 10px; 12 | right: 10px; 13 | position: absolute; 14 | } 15 | .btn-file { 16 | position: relative; 17 | overflow: hidden; 18 | } 19 | .btn-file input[type=file] { 20 | position: absolute; 21 | top: 0; 22 | right: 0; 23 | min-width: 100%; 24 | min-height: 100%; 25 | font-size: 100px; 26 | text-align: right; 27 | filter: alpha(opacity=0); 28 | opacity: 0; 29 | background: white; 30 | cursor: inherit; 31 | display: block; 32 | } 33 | .user-profile-picture { 34 | min-height: 150px; 35 | max-height: 150px; 36 | } 37 | -------------------------------------------------------------------------------- /modules/users/client/services/authentication.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Authentication service for user variables 4 | angular.module('user').factory('Authentication', ['$window', 5 | function($window) { 6 | var auth = { 7 | user: $window.user 8 | }; 9 | 10 | return auth; 11 | } 12 | ]); -------------------------------------------------------------------------------- /modules/users/client/services/password-validator.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // PasswordValidator service used for testing the password strength 4 | angular.module('user').factory('PasswordValidator', ['$window', 5 | function($window) { 6 | var owaspPasswordStrengthTest = $window.owaspPasswordStrengthTest; 7 | 8 | return { 9 | getResult: function(password) { 10 | var result = owaspPasswordStrengthTest.test(password); 11 | return result; 12 | }, 13 | getPopoverMsg: function() { 14 | var popoverMsg = "Please enter a passphrase or password with greater than 10 characters, numbers, lowercase, upppercase, and special characters."; 15 | return popoverMsg; 16 | } 17 | }; 18 | } 19 | ]); -------------------------------------------------------------------------------- /modules/users/client/services/user.client.service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Users service used for communicating with the users REST endpoint 4 | angular.module('user').factory('User', ['$resource', 5 | function($resource) { 6 | return $resource('api/user', {}, { 7 | get: { 8 | method: 'GET' 9 | }, 10 | update: { 11 | method: 'PUT' 12 | } 13 | }); 14 | } 15 | ]); 16 | 17 | angular.module('user.admin').factory('Admin', ['$resource', 18 | function($resource) { 19 | return $resource('api/admin/user/:userId', { 20 | userId: '@_id' 21 | }, { 22 | query: { 23 | method: 'GET', 24 | params: {}, 25 | isArray: true 26 | }, 27 | update: { 28 | method: 'PUT' 29 | } 30 | }); 31 | } 32 | ]); -------------------------------------------------------------------------------- /modules/users/client/user.client.module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Use Applicaion configuration module to register a new module 4 | ApplicationConfiguration.registerModule('user', ['core']); 5 | ApplicationConfiguration.registerModule('user.admin', ['core.admin']); 6 | ApplicationConfiguration.registerModule('user.admin.routes', ['core.admin.routes']); -------------------------------------------------------------------------------- /modules/users/client/views/admin/edit-user.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 25 | 26 | 27 | 30 | 36 | 37 | 38 | 41 | 47 | 48 | 49 | 52 | 58 | 59 | 60 | 63 | 70 | 71 | 72 | 73 | 74 | 82 | 83 | 84 |
Editing {{user.username}}
17 | 18 | 20 | 21 |
22 |

First name is required.

23 |
24 |
28 | 29 | 31 | 32 |
33 |

Last name is required.

34 |
35 |
39 | 40 | 42 | 43 |
44 |

Email is required.

45 |
46 |
50 | 51 | 53 | 54 |
55 |

Username is required.

56 |
57 |
61 | 62 | 64 |
65 | 66 |
67 |

At least one role is required. For example: user, admin

68 |
69 |
75 |
76 | 77 |
78 |
79 | 80 |
81 |
85 | 86 |
87 |
88 |
89 |
90 | -------------------------------------------------------------------------------- /modules/users/client/views/admin/list-users.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 12 | 19 | 20 | 21 |
-------------------------------------------------------------------------------- /modules/users/client/views/admin/view-user.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 61 | 62 | 63 |
Viewing {{user.username}}
First name
Last name
Display name
Username
Email
Provider
Roles
Date Updated
Date Created
54 | 55 | Edit 56 | 57 | 58 | Delete 59 | 60 |
64 | 65 | 66 |
67 |
68 |
69 | -------------------------------------------------------------------------------- /modules/users/client/views/authentication/authentication.client.view.html: -------------------------------------------------------------------------------- 1 |

2 |
3 |

Sign in using your social accounts

4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /modules/users/client/views/authentication/signin.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 59 |
60 |
61 | -------------------------------------------------------------------------------- /modules/users/client/views/authentication/signup.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 106 |
107 |
108 | -------------------------------------------------------------------------------- /modules/users/client/views/password/forgot-password.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 41 | 42 | 43 |
Restore your password
Enter your account username.
19 | 20 |
21 |

Enter a username.

22 |
23 |
29 | 30 |
34 |
35 | 36 |
37 |
38 | 39 |
40 |
44 | 45 | 46 |
47 |
48 |
49 | -------------------------------------------------------------------------------- /modules/users/client/views/password/reset-password-invalid.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Password reset is invalid

3 | Ask for a new password reset 4 |
-------------------------------------------------------------------------------- /modules/users/client/views/password/reset-password-success.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Password successfully reset

3 | Continue to home page 4 |
-------------------------------------------------------------------------------- /modules/users/client/views/password/reset-password.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

3 |
4 | 65 |
66 |
67 | -------------------------------------------------------------------------------- /modules/users/client/views/settings/change-password.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 47 |
48 |
49 | -------------------------------------------------------------------------------- /modules/users/client/views/settings/change-profile-picture.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 31 |
32 |
33 | -------------------------------------------------------------------------------- /modules/users/client/views/settings/edit-profile.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 76 | 77 |
78 |
79 | -------------------------------------------------------------------------------- /modules/users/client/views/settings/manage-social-accounts.client.view.html: -------------------------------------------------------------------------------- 1 |
2 |

Connected social accounts:

3 |
4 | 10 |
11 |

Unconnected social accounts:

12 |
13 | 19 | 25 | 31 | 37 | 43 | 49 |
50 |
-------------------------------------------------------------------------------- /modules/users/client/views/settings/settings.client.view.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 | 22 |
23 |
24 |
25 |
26 |
-------------------------------------------------------------------------------- /modules/users/server/config/strategies/facebook.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var passport = require('passport'), 7 | FacebookStrategy = require('passport-facebook').Strategy, 8 | users = require('../../controllers/user.server.controller'); 9 | 10 | module.exports = function(config) { 11 | // Use facebook strategy 12 | passport.use(new FacebookStrategy({ 13 | clientID: config.facebook.clientID, 14 | clientSecret: config.facebook.clientSecret, 15 | callbackURL: config.facebook.callbackURL, 16 | profileFields: ['id', 'name', 'displayName', 'emails', 'photos'], 17 | passReqToCallback: true 18 | }, 19 | function(req, accessToken, refreshToken, profile, done) { 20 | // Set the provider data and include tokens 21 | var providerData = profile._json; 22 | providerData.accessToken = accessToken; 23 | providerData.refreshToken = refreshToken; 24 | 25 | // Create the user OAuth profile 26 | var providerUserProfile = { 27 | firstName: profile.name.givenName, 28 | lastName: profile.name.familyName, 29 | displayName: profile.displayName, 30 | email: profile.emails ? profile.emails[0].value : undefined, 31 | username: profile.username || generateUsername(profile), 32 | profileImageURL: (profile.id) ? 'https://graph.facebook.com/' + profile.id + '/picture?type=large' : undefined, 33 | provider: 'facebook', 34 | providerIdentifierField: 'id', 35 | providerData: providerData 36 | }; 37 | 38 | // Save the user OAuth profile 39 | users.saveOAuthUserProfile(req, providerUserProfile, done); 40 | 41 | function generateUsername(profile) { 42 | var username = ''; 43 | 44 | if (profile.emails) { 45 | username = profile.emails[0].value.split('@')[0]; 46 | } else if (profile.name) { 47 | username = profile.name.givenName[0] + profile.name.familyName; 48 | } 49 | 50 | return username.toLowerCase() || undefined; 51 | } 52 | } 53 | )); 54 | }; 55 | -------------------------------------------------------------------------------- /modules/users/server/config/strategies/github.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var passport = require('passport'), 7 | GithubStrategy = require('passport-github').Strategy, 8 | users = require('../../controllers/user.server.controller'); 9 | 10 | module.exports = function(config) { 11 | // Use github strategy 12 | passport.use(new GithubStrategy({ 13 | clientID: config.github.clientID, 14 | clientSecret: config.github.clientSecret, 15 | callbackURL: config.github.callbackURL, 16 | passReqToCallback: true 17 | }, 18 | function(req, accessToken, refreshToken, profile, done) { 19 | // Set the provider data and include tokens 20 | var providerData = profile._json; 21 | providerData.accessToken = accessToken; 22 | providerData.refreshToken = refreshToken; 23 | 24 | // Create the user OAuth profile 25 | var displayName = profile.displayName ? profile.displayName.trim() : profile.username.trim(); 26 | var iSpace = displayName.indexOf(' '); // index of the whitespace following the firstName 27 | var firstName = iSpace !== -1 ? displayName.substring(0, iSpace) : displayName; 28 | var lastName = iSpace !== -1 ? displayName.substring(iSpace + 1) : ''; 29 | 30 | var providerUserProfile = { 31 | firstName: firstName, 32 | lastName: lastName, 33 | displayName: displayName, 34 | email: profile.emails[0].value, 35 | username: profile.username, 36 | // jscs:disable requireCamelCaseOrUpperCaseIdentifiers 37 | profileImageURL: (providerData.avatar_url) ? providerData.avatar_url : undefined, 38 | // jscs:enable 39 | provider: 'github', 40 | providerIdentifierField: 'id', 41 | providerData: providerData 42 | }; 43 | 44 | // Save the user OAuth profile 45 | users.saveOAuthUserProfile(req, providerUserProfile, done); 46 | } 47 | )); 48 | }; -------------------------------------------------------------------------------- /modules/users/server/config/strategies/google.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var passport = require('passport'), 7 | GoogleStrategy = require('passport-google-oauth').OAuth2Strategy, 8 | users = require('../../controllers/user.server.controller'); 9 | 10 | module.exports = function(config) { 11 | // Use google strategy 12 | passport.use(new GoogleStrategy({ 13 | clientID: config.google.clientID, 14 | clientSecret: config.google.clientSecret, 15 | callbackURL: config.google.callbackURL, 16 | passReqToCallback: true 17 | }, 18 | function(req, accessToken, refreshToken, profile, done) { 19 | // Set the provider data and include tokens 20 | var providerData = profile._json; 21 | providerData.accessToken = accessToken; 22 | providerData.refreshToken = refreshToken; 23 | 24 | // Create the user OAuth profile 25 | var providerUserProfile = { 26 | firstName: profile.name.givenName, 27 | lastName: profile.name.familyName, 28 | displayName: profile.displayName, 29 | email: profile.emails[0].value, 30 | username: profile.username, 31 | profileImageURL: (providerData.picture) ? providerData.picture : undefined, 32 | provider: 'google', 33 | providerIdentifierField: 'id', 34 | providerData: providerData 35 | }; 36 | 37 | // Save the user OAuth profile 38 | users.saveOAuthUserProfile(req, providerUserProfile, done); 39 | } 40 | )); 41 | }; -------------------------------------------------------------------------------- /modules/users/server/config/strategies/linkedin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var passport = require('passport'), 7 | LinkedInStrategy = require('passport-linkedin').Strategy, 8 | users = require('../../controllers/user.server.controller'); 9 | 10 | module.exports = function(config) { 11 | // Use linkedin strategy 12 | passport.use(new LinkedInStrategy({ 13 | consumerKey: config.linkedin.clientID, 14 | consumerSecret: config.linkedin.clientSecret, 15 | callbackURL: config.linkedin.callbackURL, 16 | passReqToCallback: true, 17 | profileFields: ['id', 'first-name', 'last-name', 'email-address', 'picture-url'] 18 | }, 19 | function(req, accessToken, refreshToken, profile, done) { 20 | // Set the provider data and include tokens 21 | var providerData = profile._json; 22 | providerData.accessToken = accessToken; 23 | providerData.refreshToken = refreshToken; 24 | 25 | // Create the user OAuth profile 26 | var providerUserProfile = { 27 | firstName: profile.name.givenName, 28 | lastName: profile.name.familyName, 29 | displayName: profile.displayName, 30 | email: profile.emails[0].value, 31 | username: profile.username, 32 | profileImageURL: (providerData.pictureUrl) ? providerData.pictureUrl : undefined, 33 | provider: 'linkedin', 34 | providerIdentifierField: 'id', 35 | providerData: providerData 36 | }; 37 | 38 | // Save the user OAuth profile 39 | users.saveOAuthUserProfile(req, providerUserProfile, done); 40 | } 41 | )); 42 | }; -------------------------------------------------------------------------------- /modules/users/server/config/strategies/local.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var 7 | path = require('path'), 8 | passport = require('passport'), 9 | LocalStrategy = require('passport-local').Strategy, 10 | db = require(path.resolve('./config/lib/sequelize')).models, 11 | User = db.user; 12 | 13 | module.exports = function() { 14 | // Use local strategy 15 | passport.use(new LocalStrategy({ 16 | usernameField: 'username', 17 | passwordField: 'password' 18 | }, 19 | function(username, password, done) { 20 | User.findOne({ 21 | where: { 22 | username: username.toLowerCase() 23 | } 24 | }).then(function(user) { 25 | if (!user) { 26 | return done('Invalid username or password', null, null); 27 | } 28 | if (!user || !user.authenticate(password)) { 29 | return done('Invalid username or password', null, null); 30 | } 31 | return done(null, user, null); 32 | }).catch(function(err) { 33 | return done(err, null, null); 34 | }); 35 | } 36 | )); 37 | }; -------------------------------------------------------------------------------- /modules/users/server/config/strategies/paypal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var passport = require('passport'), 7 | PayPalStrategy = require('passport-paypal-openidconnect').Strategy, 8 | users = require('../../controllers/user.server.controller'); 9 | 10 | module.exports = function(config) { 11 | passport.use(new PayPalStrategy({ 12 | clientID: config.paypal.clientID, 13 | clientSecret: config.paypal.clientSecret, 14 | callbackURL: config.paypal.callbackURL, 15 | scope: 'openid profile email', 16 | sandbox: config.paypal.sandbox, 17 | passReqToCallback: true 18 | 19 | }, 20 | function(req, accessToken, refreshToken, profile, done) { 21 | // Set the provider data and include tokens 22 | var providerData = profile._json; 23 | providerData.accessToken = accessToken; 24 | providerData.refreshToken = refreshToken; 25 | 26 | // Create the user OAuth profile 27 | var providerUserProfile = { 28 | firstName: profile.name.givenName, 29 | lastName: profile.name.familyName, 30 | displayName: profile.displayName, 31 | email: profile._json.email, 32 | username: profile.username, 33 | provider: 'paypal', 34 | providerIdentifierField: 'user_id', 35 | providerData: providerData 36 | }; 37 | 38 | // Save the user OAuth profile 39 | users.saveOAuthUserProfile(req, providerUserProfile, done); 40 | } 41 | )); 42 | }; -------------------------------------------------------------------------------- /modules/users/server/config/strategies/twitter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var passport = require('passport'), 7 | TwitterStrategy = require('passport-twitter').Strategy, 8 | users = require('../../controllers/user.server.controller'); 9 | 10 | module.exports = function(config) { 11 | // Use twitter strategy 12 | passport.use(new TwitterStrategy({ 13 | consumerKey: config.twitter.clientID, 14 | consumerSecret: config.twitter.clientSecret, 15 | callbackURL: config.twitter.callbackURL, 16 | passReqToCallback: true 17 | }, 18 | function(req, token, tokenSecret, profile, done) { 19 | // Set the provider data and include tokens 20 | var providerData = profile._json; 21 | providerData.token = token; 22 | providerData.tokenSecret = tokenSecret; 23 | 24 | // Create the user OAuth profile 25 | var displayName = profile.displayName.trim(); 26 | var iSpace = displayName.indexOf(' '); // index of the whitespace following the firstName 27 | var firstName = iSpace !== -1 ? displayName.substring(0, iSpace) : displayName; 28 | var lastName = iSpace !== -1 ? displayName.substring(iSpace + 1) : ''; 29 | 30 | var providerUserProfile = { 31 | firstName: firstName, 32 | lastName: lastName, 33 | displayName: displayName, 34 | username: profile.username, 35 | profileImageURL: profile.photos[0].value.replace('normal', 'bigger'), 36 | provider: 'twitter', 37 | providerIdentifierField: 'id_str', 38 | providerData: providerData 39 | }; 40 | 41 | // Save the user OAuth profile 42 | users.saveOAuthUserProfile(req, providerUserProfile, done); 43 | } 44 | )); 45 | }; -------------------------------------------------------------------------------- /modules/users/server/config/user.server.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var 7 | path = require('path'), 8 | passport = require('passport'), 9 | config = require(path.resolve('./config/config')), 10 | db = require(path.resolve('./config/lib/sequelize')).models, 11 | User = db.user; 12 | 13 | /** 14 | * Module init function. 15 | */ 16 | module.exports = function(app, db) { 17 | 18 | // Serialize sessions 19 | passport.serializeUser(function(user, done) { 20 | var userData = { 21 | id: user.id, 22 | firstName: user.firstName, 23 | lastName: user.lastName, 24 | displayName: user.displayName, 25 | username: user.username, 26 | email: user.email, 27 | profileImageURL: user.profileImageURL, 28 | roles: user.roles, 29 | additionalProvidersData: user.additionalProvidersData 30 | }; 31 | done(null, userData); 32 | }); 33 | 34 | // Deserialize sessions 35 | passport.deserializeUser(function(user, done) { 36 | done(null, user); 37 | }); 38 | 39 | // Initialize strategies 40 | config.utils.getGlobbedPaths(path.join(__dirname, './strategies/**/*.js')).forEach(function(strategy) { 41 | require(path.resolve(strategy))(config); 42 | }); 43 | 44 | // Add passport's middleware 45 | app.use(passport.initialize()); 46 | app.use(passport.session()); 47 | }; -------------------------------------------------------------------------------- /modules/users/server/controllers/admin.server.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var path = require('path'), 7 | errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller')), 8 | db = require(path.resolve('./config/lib/sequelize')).models, 9 | User = db.user; 10 | 11 | 12 | /** 13 | * Show the current user 14 | */ 15 | exports.read = function(req, res) { 16 | res.json(req.model); 17 | }; 18 | 19 | /** 20 | * Update a User 21 | */ 22 | exports.update = function(req, res) { 23 | 24 | User.find({ 25 | where: { 26 | id: req.model.id 27 | } 28 | }).then(function(user) { 29 | if (user) { 30 | 31 | user.firstName = req.body.firstName; 32 | user.lastName = req.body.lastName; 33 | user.displayName = req.body.firstName + ' ' + req.body.lastName; 34 | user.username = req.body.username; 35 | user.email = req.body.email; 36 | user.roles = req.body.roles; 37 | user.updatedAt = Date.now(); 38 | 39 | user.save().then(function() { 40 | res.json(user); 41 | }).catch(function(err) { 42 | res.status(400).send({ 43 | message: errorHandler.getErrorMessage(err) 44 | }); 45 | }); 46 | 47 | } else { 48 | return res.status(400).send({ 49 | message: 'Could not find user' 50 | }); 51 | } 52 | }).catch(function(err) { 53 | res.status(400).send({ 54 | message: errorHandler.getErrorMessage(err) 55 | }); 56 | }); 57 | 58 | }; 59 | 60 | /** 61 | * Delete a user 62 | */ 63 | exports.delete = function(req, res) { 64 | 65 | if (req.user.id === req.model.id) { 66 | return res.status(400).send({ 67 | message: 'You cannot delete yourself!' 68 | }); 69 | } else { 70 | User.find({ 71 | where: { 72 | id: req.model.id 73 | } 74 | }).then(function(user) { 75 | if (user) { 76 | user.destroy().then(function() { 77 | return res.json(user); 78 | }).catch(function(err) { 79 | return res.status(400).send({ 80 | message: errorHandler.getErrorMessage(err) 81 | }); 82 | }); 83 | } 84 | }); 85 | } 86 | 87 | 88 | }; 89 | 90 | /** 91 | * List of Users 92 | */ 93 | exports.list = function(req, res) { 94 | User.findAll({ 95 | order: [ 96 | ['createdAt', 'DESC'] 97 | ] 98 | }).then(function(users) { 99 | if (!users) { 100 | return res.status(400).send({ 101 | message: 'Unable to get list of users' 102 | }); 103 | } else { 104 | res.json(users); 105 | } 106 | }).catch(function(err) { 107 | res.jsonp(err); 108 | }); 109 | }; 110 | 111 | /** 112 | * User middleware 113 | */ 114 | exports.userByID = function(req, res, next, id) { 115 | if (!id) { 116 | return res.status(400).send({ 117 | message: 'User is invalid' 118 | }); 119 | } 120 | 121 | User.findById(id).then(function(user) { 122 | if (!user) { 123 | return next(new Error('Failed to load user ' + id)); 124 | } else { 125 | 126 | var data = {}; 127 | 128 | data.id = user.id; 129 | data.firstName = user.firstName; 130 | data.lastName = user.lastName; 131 | data.displayName = user.displayName; 132 | data.email = user.email; 133 | data.username = user.username; 134 | data.roles = user.roles; 135 | data.provider = user.provider; 136 | data.updatedAt = user.updatedAt; 137 | data.createdAt = user.createdAt; 138 | 139 | req.model = data; 140 | next(); 141 | } 142 | }).catch(function(err) { 143 | return next(err); 144 | }); 145 | 146 | }; -------------------------------------------------------------------------------- /modules/users/server/controllers/user.server.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var _ = require('lodash'); 7 | 8 | /** 9 | * Extend user's controller 10 | */ 11 | module.exports = _.extend( 12 | require('./users/user.authentication.server.controller'), 13 | require('./users/user.authorization.server.controller'), 14 | require('./users/user.password.server.controller'), 15 | require('./users/user.profile.server.controller') 16 | ); -------------------------------------------------------------------------------- /modules/users/server/controllers/users/user.authorization.server.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var _ = require('lodash'), 7 | path = require('path'), 8 | db = require(path.resolve('./config/lib/sequelize')).models, 9 | User = db.user; 10 | 11 | /** 12 | * User middleware 13 | */ 14 | exports.userByID = function(req, res, next, id) { 15 | if (!id) { 16 | return res.status(400).send({ 17 | message: 'User is invalid' 18 | }); 19 | } 20 | User.findOne({ 21 | where: { 22 | id: id 23 | } 24 | }).then(function(user) { 25 | if (!user) { 26 | return next(new Error('Failed to load User ' + id)); 27 | } 28 | req.profile = user; 29 | next(); 30 | }); 31 | }; 32 | 33 | 34 | exports.requiresLogin = function(req, res, next) { 35 | if (!req.isAuthenticated()) { 36 | return res.status(401).send({ 37 | message: 'User is not logged in' 38 | }); 39 | } 40 | next(); 41 | }; 42 | 43 | exports.hasAuthorization = function(roles) { 44 | var _this = this; 45 | return function(req, res, next) { 46 | _this.requiresLogin(req, res, function() { 47 | if (_.intersection(req.user.roles, roles).length) { 48 | return next(); 49 | } else { 50 | return res.status(403).send({ 51 | message: 'User is not authorized' 52 | }); 53 | } 54 | }); 55 | }; 56 | }; -------------------------------------------------------------------------------- /modules/users/server/models/user.server.model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * User Model 5 | */ 6 | 7 | var crypto = require('crypto'); 8 | 9 | /** 10 | * A Validation function for local strategy properties 11 | */ 12 | var validateLocalStrategyProperty = function(property) { 13 | if (((this.provider !== 'local' && !this.updated) || property.length !== 0) === false) { 14 | throw new Error('Local strategy failed'); 15 | } 16 | }; 17 | 18 | /** 19 | * A Validation function for local strategy password 20 | */ 21 | var validateLocalStrategyPassword = function(password) { 22 | if ((this.provider !== 'local' || (password && password.length > 6)) === false) { 23 | throw new Error('One field is missing'); 24 | } 25 | }; 26 | 27 | module.exports = function(sequelize, DataTypes) { 28 | var User = sequelize.define('user', { 29 | firstName: { 30 | type: DataTypes.STRING, 31 | defaultValue: '', 32 | validate: { 33 | isValid: validateLocalStrategyProperty, 34 | len: { 35 | args: [1, 30], 36 | msg: "First name title must be between 1 and 30 characters in length" 37 | }, 38 | } 39 | }, 40 | lastName: { 41 | type: DataTypes.STRING, 42 | defaultValue: '', 43 | validate: { 44 | isValid: validateLocalStrategyProperty 45 | } 46 | }, 47 | displayName: { 48 | type: DataTypes.STRING, 49 | defaultValue: '' 50 | }, 51 | username: { 52 | type: DataTypes.STRING, 53 | allowNull: false, 54 | unique: true, 55 | validate: { 56 | isUnique: function(value, next) { 57 | var self = this; 58 | User.find({ 59 | where: { 60 | username: value 61 | } 62 | }) 63 | .then(function(user) { 64 | // reject if a different user wants to use the same email 65 | if (user && self.id !== user.id) { 66 | return next('Username already exists, please choose another'); 67 | } 68 | return next(); 69 | }) 70 | .catch(function(err) { 71 | return next(err); 72 | }); 73 | } 74 | } 75 | }, 76 | email: { 77 | type: DataTypes.STRING, 78 | unique: true, 79 | validate: { 80 | isValid: validateLocalStrategyProperty, 81 | isEmail: { 82 | msg: 'Please fill a valid email address' 83 | }, 84 | isUnique: function(value, next) { 85 | var self = this; 86 | User.find({ 87 | where: { 88 | email: value 89 | } 90 | }) 91 | .then(function(user) { 92 | // reject if a different user wants to use the same email 93 | if (user && self.id !== user.id) { 94 | return next('Email already exists, please choose another'); 95 | } 96 | return next(); 97 | }) 98 | .catch(function(err) { 99 | return next(err); 100 | }); 101 | } 102 | } 103 | }, 104 | profileImageURL: DataTypes.STRING, 105 | roles: { 106 | type: DataTypes.JSON, 107 | defaultValue: ["user"], 108 | isArray: true 109 | }, 110 | hashedPassword: { 111 | type: DataTypes.STRING, 112 | default: '', 113 | validate: { 114 | isValid: validateLocalStrategyPassword 115 | } 116 | }, 117 | provider: DataTypes.STRING, 118 | providerData: { 119 | type: DataTypes.JSON 120 | }, 121 | additionalProvidersData: { 122 | type: DataTypes.JSON 123 | }, 124 | salt: DataTypes.STRING, 125 | resetPasswordToken: DataTypes.STRING, 126 | resetPasswordExpires: DataTypes.BIGINT 127 | }, { 128 | classMethods: { 129 | findUniqueUsername: function(username, suffix, callback) { 130 | var _this = this; 131 | var possibleUsername = username + (suffix || ''); 132 | 133 | _this.find({ 134 | where: { 135 | username: possibleUsername 136 | } 137 | }).then(function(user) { 138 | if (!user) { 139 | callback(possibleUsername); 140 | } else { 141 | return _this.findUniqueUsername(username, (suffix || 0) + 1, callback); 142 | } 143 | }); 144 | } 145 | } 146 | }); 147 | 148 | User.prototype.makeSalt = function() { 149 | return crypto.randomBytes(16).toString('base64'); 150 | }; 151 | 152 | User.prototype.authenticate = function(plainText) { 153 | return this.encryptPassword(plainText, this.salt) === this.hashedPassword; 154 | }; 155 | 156 | User.prototype.encryptPassword = function(password, salt) { 157 | if (!password || !salt) 158 | return ''; 159 | salt = new Buffer(salt, 'base64'); 160 | return crypto.pbkdf2Sync(password, salt, 10000, 64).toString('base64'); 161 | }; 162 | 163 | User.associate = function(models) { 164 | if (models.article) { 165 | User.hasMany(models.article); 166 | } 167 | }; 168 | 169 | return User; 170 | }; 171 | -------------------------------------------------------------------------------- /modules/users/server/policies/admin.server.policy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var 4 | path = require('path'), 5 | config = require(path.resolve('./config/config')), 6 | acl = require('acl'); 7 | 8 | /** 9 | * Module dependencies. 10 | */ 11 | 12 | // Using the redis backend 13 | /* 14 | var redisInstance = require('redis').createClient(config.redis.port, config.redis.host, { 15 | no_ready_check: true 16 | }); 17 | 18 | //Use redis database 1 19 | redisInstance.select(1); 20 | 21 | if (config.redis.password) { 22 | redisInstance.auth(config.redis.password); 23 | } 24 | 25 | acl = new acl(new acl.redisBackend(redisInstance, 'acl')); 26 | */ 27 | 28 | // Using the memory backend 29 | acl = new acl(new acl.memoryBackend()); 30 | 31 | /** 32 | * Invoke Admin Permissions 33 | */ 34 | exports.invokeRolesPolicies = function() { 35 | acl.allow([{ 36 | roles: ['admin'], 37 | allows: [{ 38 | resources: '/api/admin/user', 39 | permissions: '*' 40 | }, { 41 | resources: '/api/admin/user/:userId', 42 | permissions: '*' 43 | }] 44 | }]); 45 | }; 46 | 47 | /** 48 | * Check If Admin Policy Allows 49 | */ 50 | exports.isAllowed = function(req, res, next) { 51 | var roles = (req.user) ? req.user.roles : ['guest']; 52 | 53 | // Check for user roles 54 | acl.areAnyRolesAllowed(roles, req.route.path, req.method.toLowerCase(), function(err, isAllowed) { 55 | if (err) { 56 | // An authorization error occurred. 57 | return res.status(500).send('Unexpected authorization error'); 58 | } else { 59 | if (isAllowed) { 60 | // Access granted! Invoke next middleware 61 | return next(); 62 | } else { 63 | return res.status(403).json({ 64 | message: 'User is not authorized' 65 | }); 66 | } 67 | } 68 | }); 69 | }; 70 | -------------------------------------------------------------------------------- /modules/users/server/routes/admin.server.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var adminPolicy = require('../policies/admin.server.policy'), 7 | admin = require('../controllers/admin.server.controller'); 8 | 9 | module.exports = function(app) { 10 | // User route registration first. Ref: #713 11 | //require('./user.server.routes.js')(app); 12 | 13 | app.route('/api/admin/user') 14 | .get(adminPolicy.isAllowed, admin.list); 15 | 16 | 17 | app.route('/api/admin/user/:userId') 18 | .get(adminPolicy.isAllowed, admin.read) 19 | .put(adminPolicy.isAllowed, admin.update) 20 | .delete(adminPolicy.isAllowed, admin.delete); 21 | 22 | app.param('userId', admin.userByID); 23 | }; -------------------------------------------------------------------------------- /modules/users/server/routes/auth.server.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var passport = require('passport'); 7 | 8 | module.exports = function(app) { 9 | // User Routes 10 | var users = require('../controllers/user.server.controller'); 11 | 12 | // Setting up the users password api 13 | app.route('/api/auth/forgot').post(users.forgot); 14 | app.route('/api/auth/reset/:token').get(users.validateResetToken); 15 | app.route('/api/auth/reset/:token').post(users.reset); 16 | 17 | // Setting up the users authentication api 18 | app.route('/api/auth/signup').post(users.signup); 19 | app.route('/api/auth/signin').post(users.signin); 20 | app.route('/api/auth/signout').get(users.signout); 21 | 22 | // Setting the facebook oauth routes 23 | app.route('/api/auth/facebook').get(users.oauthCall('facebook', { 24 | scope: ['email'] 25 | })); 26 | app.route('/api/auth/facebook/callback').get(users.oauthCallback('facebook')); 27 | 28 | // Setting the twitter oauth routes 29 | app.route('/api/auth/twitter').get(users.oauthCall('twitter')); 30 | app.route('/api/auth/twitter/callback').get(users.oauthCallback('twitter')); 31 | 32 | // Setting the google oauth routes 33 | app.route('/api/auth/google').get(users.oauthCall('google', { 34 | scope: [ 35 | 'https://www.googleapis.com/auth/userinfo.profile', 36 | 'https://www.googleapis.com/auth/userinfo.email' 37 | ] 38 | })); 39 | app.route('/api/auth/google/callback').get(users.oauthCallback('google')); 40 | 41 | // Setting the linkedin oauth routes 42 | app.route('/api/auth/linkedin').get(users.oauthCall('linkedin', { 43 | scope: [ 44 | 'r_basicprofile', 45 | 'r_emailaddress' 46 | ] 47 | })); 48 | app.route('/api/auth/linkedin/callback').get(users.oauthCallback('linkedin')); 49 | 50 | // Setting the github oauth routes 51 | app.route('/api/auth/github').get(users.oauthCall('github')); 52 | app.route('/api/auth/github/callback').get(users.oauthCallback('github')); 53 | 54 | // Setting the paypal oauth routes 55 | app.route('/api/auth/paypal').get(users.oauthCall('paypal')); 56 | app.route('/api/auth/paypal/callback').get(users.oauthCallback('paypal')); 57 | }; -------------------------------------------------------------------------------- /modules/users/server/routes/user.server.routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(app) { 4 | // User Routes 5 | var user = require('../controllers/user.server.controller'); 6 | 7 | // Setting up the users profile api 8 | app.route('/api/user/me').get(user.requiresLogin, user.me); 9 | app.route('/api/user') 10 | .get(user.requiresLogin, user.getProfile) 11 | .put(user.requiresLogin, user.update); 12 | app.route('/api/user/accounts').delete(user.requiresLogin, user.removeOAuthProvider); 13 | app.route('/api/user/password').post(user.requiresLogin, user.changePassword); 14 | app.route('/api/user/picture').post(user.requiresLogin, user.changeProfilePicture); 15 | 16 | // Finish by binding the user middleware 17 | app.param('userId', user.userByID); 18 | }; 19 | -------------------------------------------------------------------------------- /modules/users/server/templates/reset-password-confirm-email.server.view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

Dear {{name}},

10 |

11 |

This is a confirmation that the password for your account has just been changed

12 |
13 |
14 |

The {{appName}} Support Team

15 | 16 | 17 | -------------------------------------------------------------------------------- /modules/users/server/templates/reset-password-email.server.view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

Dear {{name}},

10 |
11 |

12 | You have requested to have your password reset for your account at {{appName}} 13 |

14 |

Please visit this url to reset your password:

15 |

{{url}}

16 | If you didn't make this request, you can ignore this email. 17 |
18 |
19 |

The {{appName}} Support Team

20 | 21 | 22 | -------------------------------------------------------------------------------- /modules/users/tests/e2e/users.e2e.tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Users E2E Tests:', function() { 4 | describe('Signin Validation', function() { 5 | it('Should report missing credentials', function() { 6 | browser.get('http://localhost:3000/authentication/signin'); 7 | element(by.css('button[type=submit]')).click(); 8 | element(by.binding('error')).getText().then(function(errorText) { 9 | expect(errorText).toBe('Missing credentials'); 10 | }); 11 | }); 12 | }); 13 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seanjs", 3 | "description": "Full-Stack Javascript with SequelizeJS, ExpressJS, AngularJS, and NodeJS", 4 | "version": "0.0.1", 5 | "seanjs-version": "0.0.43", 6 | "private": false, 7 | "author": "https://github.com/seanjs-stack/seanjs/graphs/contributors", 8 | "license": "MIT", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/seanjs-stack/seanjs.git" 12 | }, 13 | "engines": { 14 | "node": ">=0.10.28", 15 | "npm": ">=1.4.28" 16 | }, 17 | "scripts": { 18 | "start": "grunt", 19 | "test": "grunt test", 20 | "postinstall": "bower install --config.interactive=false" 21 | }, 22 | "dependencies": { 23 | "acl": "~0.4.4", 24 | "async": "^2.4.1", 25 | "body-parser": "^1.17.2", 26 | "bower": "^1.7.1", 27 | "brace-expansion": "^1.1.8", 28 | "chalk": "^1.1.1", 29 | "compression": "^1.5.0", 30 | "connect-flash": "~0.1.1", 31 | "connect-redis": "^3.3.0", 32 | "consolidate": "~0.14.5", 33 | "cookie-parser": "^1.3.2", 34 | "crypto": "0.0.3", 35 | "express": "^4.15.3", 36 | "express-session": "^1.15.3", 37 | "forever": "^0.15.1", 38 | "glob": "^7.1.2", 39 | "helmet": "^3.6.1", 40 | "jasmine-core": "^2.6.2", 41 | "lodash": "^4.17.4", 42 | "method-override": "^2.3.9", 43 | "mocha": "^3.4.2", 44 | "morgan": "^1.8.2", 45 | "multer": "^1.1.0", 46 | "mysql2": "^1.3.4", 47 | "nodemailer": "^4.0.1", 48 | "owasp-password-strength-test": "^1.3.0", 49 | "passport": "^0.3.2", 50 | "passport-facebook": "^2.0.0", 51 | "passport-github": "^1.0.0", 52 | "passport-google-oauth": "~1.0.0", 53 | "passport-linkedin": "^1.0.0", 54 | "passport-local": "^1.0.0", 55 | "passport-paypal-openidconnect": "^0.1.1", 56 | "passport-twitter": "^1.0.2", 57 | "pg": "^6.2.4", 58 | "pg-hstore": "^2.3.2", 59 | "phantomjs": ">=1.9.0", 60 | "redis": "^2.4.2", 61 | "request": "^2.81.0", 62 | "sequelize": "^4.1.0", 63 | "serve-favicon": "^2.4.3", 64 | "socket.io": "^2.0.3", 65 | "swig": "^1.4.2", 66 | "validator": "^7.0.0", 67 | "winston": "^2.1.0" 68 | }, 69 | "devDependencies": { 70 | "coveralls": "^2.11.6", 71 | "grunt": "0.4.5", 72 | "grunt-cli": "^0.1.13", 73 | "grunt-concurrent": "^2.0.0", 74 | "grunt-contrib-copy": "~0.8.0", 75 | "grunt-contrib-csslint": "^0.5.0", 76 | "grunt-contrib-cssmin": "~0.14.0", 77 | "grunt-contrib-jshint": "^0.11.3", 78 | "grunt-contrib-less": "^1.0.1", 79 | "grunt-contrib-sass": "^0.9.2", 80 | "grunt-contrib-uglify": "^0.11.0", 81 | "grunt-contrib-watch": "^0.6.1", 82 | "grunt-env": "~0.4.4", 83 | "grunt-karma": "~0.12.1", 84 | "grunt-mocha-test": "~0.12.7", 85 | "grunt-ng-annotate": "^1.0.1", 86 | "grunt-nodemon": "~0.4.1", 87 | "grunt-protractor-runner": "^3.0.0", 88 | "grunt-sass": "^1.1.0", 89 | "karma": "^0.13.15", 90 | "karma-chrome-launcher": "~0.2.2", 91 | "karma-coverage": "~0.5.3", 92 | "karma-firefox-launcher": "~0.1.6", 93 | "karma-jasmine": "~0.3.6", 94 | "karma-ng-html2js-preprocessor": "^0.2.0", 95 | "karma-phantomjs-launcher": "~0.2.0", 96 | "load-grunt-tasks": "^3.2.0", 97 | "run-sequence": "^1.1.1", 98 | "should": "^8.0.2", 99 | "supertest": "^1.0.1" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Protractor configuration 4 | exports.config = { 5 | specs: ['modules/*/tests/e2e/*.js'] 6 | }; -------------------------------------------------------------------------------- /public/humans.txt: -------------------------------------------------------------------------------- 1 | # humanstxt.org/ 2 | # The humans responsible & technology colophon 3 | 4 | # TEAM 5 | 6 | -- -- 7 | 8 | # THANKS 9 | 10 | 11 | 12 | # TECHNOLOGY COLOPHON 13 | 14 | HTML5, CSS3 15 | jQuery, Modernizr 16 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org/ 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /public/uploads/users/profile/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | -------------------------------------------------------------------------------- /scripts/generate-ssl-certs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ ! -e server.js ] 4 | then 5 | echo "Error: could not find main application server.js file" 6 | echo "You should run the generate-ssl-certs.sh script from the main SEAN.JS application root directory" 7 | echo "i.e: bash scripts/generate-ssl-certs.sh" 8 | exit -1 9 | fi 10 | 11 | echo "Generating self-signed certificates..." 12 | mkdir -p ./config/sslcerts 13 | openssl genrsa -out ./config/sslcerts/key.pem 4096 14 | openssl req -new -key ./config/sslcerts/key.pem -out ./config/sslcerts/csr.pem 15 | openssl x509 -req -days 365 -in ./config/sslcerts/csr.pem -signkey ./config/sslcerts/key.pem -out ./config/sslcerts/cert.pem 16 | rm ./config/sslcerts/csr.pem 17 | chmod 600 ./config/sslcerts/key.pem ./config/sslcerts/cert.pem 18 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var app = require('./config/lib/app'); 7 | var server = app.start(); -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var app; 7 | 8 | var path = require('path'); 9 | var app = require(path.resolve('./config/lib/app')); 10 | 11 | app.init(function() { 12 | console.log('Initialized test automation'); 13 | }); --------------------------------------------------------------------------------