├── .bowerrc
├── .codeclimate.yml
├── .csslintrc
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .jshint
├── .travis.yml
├── LICENSE
├── Procfile
├── README.md
├── app
├── controllers
│ ├── auth.js
│ ├── index.js
│ └── register.js
├── models
│ └── user.js
├── public
│ ├── images
│ │ └── logo.png
│ ├── javascript
│ │ └── application.js
│ └── stylesheets
│ │ ├── cover.css
│ │ └── style.css
├── routes
│ ├── api
│ │ └── user.js
│ ├── auth.js
│ ├── index.js
│ └── register.js
├── services
│ ├── auth.js
│ ├── index.js
│ └── register.js
├── utils
│ ├── crypt.js
│ └── validation.js
└── views
│ ├── auth
│ └── login.jade
│ ├── error.jade
│ ├── includes
│ ├── facebook-meta.jade
│ ├── footer.jade
│ ├── landing.jade
│ ├── messages.jade
│ ├── meta.jade
│ ├── twitter-cards.jade
│ └── user-info.jade
│ ├── index.jade
│ ├── layouts
│ └── default.jade
│ └── register
│ └── index.jade
├── bin
└── www
├── bower.json
├── config
├── config.json
├── mongoose.js
├── passport.js
└── routes.js
├── favicon.ico
├── locales
├── en.json
├── pt-br.json
└── pt.json
├── main.js
├── package.json
└── tests.js
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "app/public/vendor"
3 | }
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | ---
2 | engines:
3 | csslint:
4 | enabled: true
5 | duplication:
6 | enabled: false
7 | config:
8 | languages:
9 | - javascript
10 | eslint:
11 | enabled: true
12 | fixme:
13 | enabled: true
14 | ratings:
15 | paths:
16 | - "**.css"
17 | - "**.js"
--------------------------------------------------------------------------------
/.csslintrc:
--------------------------------------------------------------------------------
1 | --exclude-exts=.min.css
2 | --ignore=adjoining-classes,box-model,ids,order-alphabetical,unqualified-attributes
3 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | charset = utf-8
6 | trim_trailing_whitespace = true
7 | insert_final_newline = false
8 | indent_style = space
9 | indent_size = 4
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [*.json]
15 | indent_size = 2
16 |
17 | [app/public/**.{jade,js,css}]
18 | indent_size = 2
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/*{.,-}min.js
2 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | ecmaFeatures:
2 | modules: true
3 | jsx: true
4 |
5 | env:
6 | amd: true
7 | browser: true
8 | es6: true
9 | jquery: true
10 | node: true
11 |
12 | # http://eslint.org/docs/rules/
13 | rules:
14 | # Possible Errors
15 | comma-dangle: [2, never]
16 | no-cond-assign: 2
17 | no-console: 0
18 | no-constant-condition: 2
19 | no-control-regex: 2
20 | no-debugger: 2
21 | no-dupe-args: 2
22 | no-dupe-keys: 2
23 | no-duplicate-case: 2
24 | no-empty: 2
25 | no-empty-character-class: 2
26 | no-ex-assign: 2
27 | no-extra-boolean-cast: 2
28 | no-extra-parens: 0
29 | no-extra-semi: 2
30 | no-func-assign: 2
31 | no-inner-declarations: [2, functions]
32 | no-invalid-regexp: 2
33 | no-irregular-whitespace: 2
34 | no-negated-in-lhs: 2
35 | no-obj-calls: 2
36 | no-regex-spaces: 2
37 | no-sparse-arrays: 2
38 | no-unexpected-multiline: 2
39 | no-unreachable: 2
40 | use-isnan: 2
41 | valid-jsdoc: 0
42 | valid-typeof: 2
43 |
44 | # Best Practices
45 | accessor-pairs: 2
46 | block-scoped-var: 0
47 | complexity: 0
48 | consistent-return: 0
49 | curly: 0
50 | default-case: 0
51 | dot-location: 0
52 | dot-notation: 0
53 | eqeqeq: 2
54 | guard-for-in: 2
55 | no-alert: 2
56 | no-caller: 2
57 | no-case-declarations: 2
58 | no-div-regex: 2
59 | no-else-return: 0
60 | no-empty-label: 0
61 | no-empty-pattern: 2
62 | no-eq-null: 2
63 | no-eval: 2
64 | no-extend-native: 2
65 | no-extra-bind: 2
66 | no-fallthrough: 2
67 | no-floating-decimal: 0
68 | no-implicit-coercion: 0
69 | no-implied-eval: 2
70 | no-invalid-this: 0
71 | no-iterator: 2
72 | no-labels: 0
73 | no-lone-blocks: 2
74 | no-loop-func: 2
75 | no-magic-number: 0
76 | no-multi-spaces: 0
77 | no-multi-str: 0
78 | no-native-reassign: 2
79 | no-new-func: 2
80 | no-new-wrappers: 2
81 | no-new: 2
82 | no-octal-escape: 2
83 | no-octal: 2
84 | no-proto: 2
85 | no-redeclare: 2
86 | no-return-assign: 2
87 | no-script-url: 2
88 | no-self-compare: 2
89 | no-sequences: 0
90 | no-throw-literal: 0
91 | no-unused-expressions: 2
92 | no-useless-call: 2
93 | no-useless-concat: 2
94 | no-void: 2
95 | no-warning-comments: 0
96 | no-with: 2
97 | radix: 2
98 | vars-on-top: 0
99 | wrap-iife: 2
100 | yoda: 0
101 |
102 | # Strict
103 | strict: [2, "global"]
104 |
105 | # Variables
106 | init-declarations: 0
107 | no-catch-shadow: 2
108 | no-delete-var: 2
109 | no-label-var: 2
110 | no-shadow-restricted-names: 2
111 | no-shadow: 0
112 | no-undef-init: 2
113 | no-undef: 0
114 | no-undefined: 0
115 | no-unused-vars: 0
116 | no-use-before-define: 0
117 |
118 | # Node.js and CommonJS
119 | callback-return: 2
120 | global-require: 0
121 | handle-callback-err: 2
122 | no-mixed-requires: 0
123 | no-new-require: 0
124 | no-path-concat: 2
125 | no-process-exit: 0
126 | no-restricted-modules: 0
127 | no-sync: 0
128 |
129 | # Stylistic Issues
130 | array-bracket-spacing: 0
131 | block-spacing: 0
132 | brace-style: 0
133 | camelcase: 0
134 | comma-spacing: 0
135 | comma-style: 0
136 | computed-property-spacing: 0
137 | consistent-this: 0
138 | eol-last: 0
139 | func-names: 0
140 | func-style: 0
141 | id-length: 0
142 | id-match: 0
143 | indent: 0
144 | jsx-quotes: 0
145 | key-spacing: 0
146 | linebreak-style: 0
147 | lines-around-comment: 0
148 | max-depth: 0
149 | max-len: 0
150 | max-nested-callbacks: 0
151 | max-params: 0
152 | max-statements: [2, 30]
153 | new-cap: 0
154 | new-parens: 0
155 | newline-after-var: 0
156 | no-array-constructor: 0
157 | no-bitwise: 0
158 | no-continue: 0
159 | no-inline-comments: 0
160 | no-lonely-if: 0
161 | no-mixed-spaces-and-tabs: 0
162 | no-multiple-empty-lines: 0
163 | no-negated-condition: 0
164 | no-nested-ternary: 0
165 | no-new-object: 0
166 | no-plusplus: 0
167 | no-restricted-syntax: 0
168 | no-spaced-func: 0
169 | no-ternary: 0
170 | no-trailing-spaces: 0
171 | no-underscore-dangle: 0
172 | no-unneeded-ternary: 0
173 | object-curly-spacing: 0
174 | one-var: 0
175 | operator-assignment: 0
176 | operator-linebreak: 0
177 | padded-blocks: 0
178 | quote-props: 0
179 | quotes: 0
180 | require-jsdoc: 0
181 | semi-spacing: 0
182 | semi: 0
183 | sort-vars: 0
184 | space-after-keywords: 0
185 | space-before-blocks: 0
186 | space-before-function-paren: 0
187 | space-before-keywords: 0
188 | space-in-parens: 0
189 | space-infix-ops: 0
190 | space-return-throw-case: 0
191 | space-unary-ops: 0
192 | spaced-comment: 0
193 | wrap-regex: 0
194 |
195 | # ECMAScript 6
196 | arrow-body-style: 0
197 | arrow-parens: 0
198 | arrow-spacing: 0
199 | constructor-super: 0
200 | generator-star-spacing: 0
201 | no-arrow-condition: 0
202 | no-class-assign: 0
203 | no-const-assign: 0
204 | no-dupe-class-members: 0
205 | no-this-before-super: 0
206 | no-var: 0
207 | object-shorthand: 0
208 | prefer-arrow-callback: 0
209 | prefer-const: 0
210 | prefer-reflect: 0
211 | prefer-spread: 0
212 | prefer-template: 0
213 | require-yield: 0
214 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | *.iml
3 |
4 | # Logs
5 | logs
6 | *.log
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 |
13 | # Directory for instrumented libs generated by jscoverage/JSCover
14 | lib-cov
15 |
16 | # Coverage directory used by tools like istanbul
17 | coverage
18 |
19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
20 | .grunt
21 |
22 | # node-waf configuration
23 | .lock-wscript
24 |
25 | # Compiled binary addons (http://nodejs.org/api/addons.html)
26 | build/Release
27 |
28 | # Dependency directory
29 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
30 | node_modules
31 | bower_components
32 |
33 | # Debug log from npm
34 | npm-debug.log
35 |
36 | app/public/vendor
--------------------------------------------------------------------------------
/.jshint:
--------------------------------------------------------------------------------
1 | {
2 | "asi": false,
3 | "bitwise": false,
4 | "browser": true,
5 | "curly": true,
6 | "eqeqeq": true,
7 | "esnext": true,
8 | "evil": false,
9 | "forin": false,
10 | "globals": {
11 | "after": true,
12 | "afterEach": true,
13 | "assert": true,
14 | "before": true,
15 | "beforeEach": true,
16 | "describe": true,
17 | "it": true,
18 | "mocha": true,
19 | "test": true,
20 | "Kairos": true,
21 | "fabric": true
22 | },
23 | "immed": true,
24 | "indent": 2,
25 | "lastsemic": false,
26 | "maxdepth": false,
27 | "multistr": false,
28 | "newcap": true,
29 | "noarg": true,
30 | "node": true,
31 | "onevar": false,
32 | "quotmark": "single",
33 | "regexp": true,
34 | "smarttabs": true,
35 | "strict": true,
36 | "trailing": true,
37 | "undef": true,
38 | "unused": true
39 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 4.2.3
4 | - 4.2.4
5 | - 5.3.0
6 | install:
7 | - npm install
8 | script:
9 | - npm test
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016, Rodrigo Gomes da Silva
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above
8 | copyright notice, this list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation and/or
12 | other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
15 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
17 | THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
19 | OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
21 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
22 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: npm install bower -g && bower install && node ./bin/www
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Node.js Web Jade Scaffold
2 |
3 | [](https://travis-ci.org/rodrigogs/nodejs-web-jade-scaffold)
4 | [](https://codeclimate.com/github/rodrigogs/nodejs-web-jade-scaffold)
5 | [](https://david-dm.org/rodrigogs/nodejs-web-jade-scaffold)
6 | [](https://david-dm.org/rodrigogs/nodejs-web-jade-scaffold#info=devDependencies)
7 |
8 | ## Structure
9 | ```
10 | project
11 | ├── app
12 | │ ├── controllers
13 | │ │ ├── api
14 | │ │ │ └── example.js
15 | │ │ └── example.js
16 | │ │
17 | │ ├── models
18 | │ │ └── example.js
19 | │ │
20 | │ ├── public
21 | │ │ ├── javascript
22 | │ │ │ └── example.js
23 | │ │ └── stylesheets
24 | │ │ └── example.css
25 | │ │
26 | │ ├── routes
27 | │ │ ├── api
28 | │ │ │ └── example.js
29 | │ │ └── example.js
30 | │ │
31 | │ ├── services
32 | │ │ └── example.js
33 | │ │
34 | │ ├── utils
35 | │ │ └── example.js
36 | │ │
37 | │ └── views
38 | │ ├── error.jade <-- Error template
39 | │ ├── includes
40 | │ │ ├── footer.jade <-- Footer template
41 | │ │ ├── messages.jade <-- Flash messages template
42 | │ │ ├── meta.jade <-- General meta content
43 | │ │ ├── facebook-meta.jade <-- Facebook meta data
44 | │ │ └── twitter-cards.jade <-- Twitter meta data for whitelisting
45 | │ ├── layouts
46 | │ │ └── default.jade <-- Default layout
47 | │ └── index.jade <-- Index template
48 | │
49 | ├── bin
50 | │ └── www <-- HTTP server runner
51 | │
52 | ├── config
53 | │ ├── config.json <-- General configurations file
54 | │ ├── mongoose.js <-- Mongoose connection configuration
55 | │ ├── passport.js <-- Passport routes and strategies
56 | │ └── routes.js <-- General routes
57 | │
58 | ├── locales <-- Locale files should be referenced in config.json file
59 | │ ├── en.json
60 | │ ├── pt-br.json
61 | │ └── pt.json
62 | │
63 | ├── main.js <-- Main app file
64 | ├── tests.js < -- Tests file
65 | ├── bower.json
66 | ├── favicon.ico <-- Website favicon
67 | └── package.json
68 | ```
69 |
70 | ## Setup
71 |
72 | ### Installation
73 |
74 | > npm install
75 |
76 | > npm install bower -g
77 |
78 | > bower install
79 |
80 | > [Install MongoDB](https://www.mongodb.org/downloads)
81 |
82 | ### Configuration
83 |
84 | #### Environment variables
85 | * IP
86 | - export IP="192.168.0.1"
87 | * PORT
88 | - export PORT="8080"
89 | * SESSION_SECRET
90 | - export SESSION_SECRET="mysecret"
91 | * DATABASE_URL
92 | - export DATABASE_URL="mongodb://localhost:27017/example"
93 | * FACEBOOK_APP_ID: See https://developers.facebook.com/docs/apps/register#app-id
94 | - export FACEBOOK_APP_ID="myfacebookid"
95 | * FACEBOOK_APP_SECRET: See https://developers.facebook.com/docs/apps/register#app-secret
96 | - export FACEBOOK_APP_SECRET="myfacebooksecret"
97 | * TWITTER_CONSUMER_KEY
98 | - export TWITTER_CONSUMER_KEY="mytwitterconsumerkey"
99 | * TWITTER_CONSUMER_SECRET
100 | - export TWITTER_CONSUMER_SECRET="mytwitterconsumersecret"
101 | * GOOGLE_CLIENT_ID
102 | - export GOOGLE_CLIENT_ID="mygoogleclientid"
103 | * GOOGLE_CLIENT_SECRET
104 | - exports GOOGLE_CLIENT_SECRET="mygoogleclientsecret"
105 | * GITHUB_CLIENT_ID
106 | - export GITHUB_CLIENT_ID="mygithubclientid"
107 | * GITHUB_CLIENT_SECRET
108 | - exports GITHUB_CLIENT_SECRET="mygithubclientsecret"
109 | * LINKEDIN_KEY
110 | - export LINKEDIN_KEY="mylinkedinkey"
111 | * LINKEDIN_KEY
112 | - export LINKEDIN_SECRET="mylinkedinsecret"
113 | * INSTAGRAM_CLIENT_ID
114 | - export INSTAGRAM_CLIENT_ID="myinstamgramclientid"
115 | * INSTAGRAM_CLIENT_SECRET
116 | - export INSTAGRAM_CLIENT_SECRET="myinstamgramclientsecret"
117 |
118 | #### Project configuration
119 | * Project/config/config.json
120 | - DATABASE.RECONNECT: Enable or disable auto reconnection.
121 | - DATABASE.RECONNECTION_INTERVAL: Interval for auto reconnection tries.
122 | - LOCALES: Reference your locale files in the locale folder. Only the locales defined here will be use by the app.
123 | - HTTP_LOG_CONFIG: See https://github.com/expressjs/morgan#predefined-formats
124 | - AUTH.ENABLED: Enable or disable authentication.
125 | - AUTH.LOCAL.ENABLED: Enable or disable local authentication.
126 | - AUTH.FACEBOOK.ENABLED: Enable or disable Facebook authentication.
127 | - AUTH.FACEBOOK.PROFILE_FIELDS: Facebook profile fields wanted.
128 | - AUTH.FACEBOOK.OPTIONS: Facebook API options.
129 | - AUTH.TWITTER.ENABLED: Enable or disable Twitter authentication.
130 | - AUTH.GOOGLE.ENABLED: Enable or disable Google authentication.
131 | - AUTH.GOOGLE.OPTIONS: Google API options.
132 | - AUTH.GITHUB.ENABLED: Enable or disable GitHub authentication.
133 | - AUTH.LINKEDIN.ENABLED: Enable or disable Linkedin authentication.
134 | - AUTH.LINKEDIN.OPTIONS: Linkedin api options.
135 | - AUTH.INSTAGRAM.ENABLED: Enable or disable Instagram authentication.
136 |
137 | ## Launching
138 |
139 | First start MongoDB if you don't have a running instance
140 |
141 | > mongod
142 |
143 | #### Development
144 |
145 | > npm start
146 |
147 | #### Production
148 |
149 | > node bin/www
150 |
151 | #### Test
152 |
153 | > npm test
154 |
155 | [Running Example](http://nodejs-web-jade-scaffold.herokuapp.com/)
156 |
157 | ## TODO
158 |
159 | * User/Role management
160 | * More tests
161 |
162 | ## License
163 |
164 | [Licence](https://github.com/rodrigogs/nodejs-web-jade-scaffold/blob/master/LICENSE) © Rodrigo Gomes da Silva
165 |
--------------------------------------------------------------------------------
/app/controllers/auth.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const CONFIG = require('../../config/config.json');
4 | const passport = require('passport');
5 |
6 | const OPTIONS = {
7 | badRequestMessage: 'passport.badrequest', // 18n reference
8 | successRedirect: '/',
9 | failureRedirect: '/auth/login',
10 | failureFlash: true
11 | };
12 |
13 | module.exports = {
14 |
15 | /**
16 | * Index action
17 | */
18 | index: (req, res, next) => {
19 | res.render('auth/login');
20 | },
21 |
22 | /**
23 | * Login action
24 | */
25 | login: passport.authenticate('local', OPTIONS),
26 |
27 | /**
28 | * Facebook action
29 | */
30 | facebook: passport.authenticate('facebook', CONFIG.AUTH.FACEBOOK.OPTIONS),
31 |
32 | /**
33 | * FacebookCallback action
34 | */
35 | facebookCallback: passport.authenticate('facebook', OPTIONS),
36 |
37 | /**
38 | * Twitter action
39 | */
40 | twitter: passport.authenticate('twitter'),
41 |
42 | /**
43 | * TwitterCallback action
44 | */
45 | twitterCallback: passport.authenticate('twitter', OPTIONS),
46 |
47 | /**
48 | * Google action
49 | */
50 | google: passport.authenticate('google', CONFIG.AUTH.GOOGLE.OPTIONS),
51 |
52 | /**
53 | * GoogleCallback action
54 | */
55 | googleCallback: passport.authenticate('google', OPTIONS),
56 |
57 | /**
58 | * Github action
59 | */
60 | github: passport.authenticate('github'),
61 |
62 | /**
63 | * GithubCallback action
64 | */
65 | githubCallback: passport.authenticate('github', OPTIONS),
66 |
67 | /**
68 | * Linkedin action
69 | */
70 | linkedin: passport.authenticate('linkedin', { state: CONFIG.AUTH.LINKEDIN.OPTIONS.state }),
71 |
72 | /**
73 | * LinkedinCallback action
74 | */
75 | linkedinCallback: passport.authenticate('linkedin', OPTIONS),
76 |
77 | /**
78 | * Instagram action
79 | */
80 | instagram: passport.authenticate('instagram'),
81 |
82 | /**
83 | * InstagramCallback action
84 | */
85 | instagramCallback: passport.authenticate('instagram', OPTIONS),
86 |
87 | /**
88 | * Logout action
89 | */
90 | logout: (req, res, next) => {
91 | req.logout();
92 | res.redirect('/');
93 | }
94 | };
--------------------------------------------------------------------------------
/app/controllers/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const IndexService = require('../services/index');
4 |
5 | module.exports = {
6 |
7 | /**
8 | * Index action
9 | */
10 | index: (req, res, next) => {
11 | let message = IndexService.getMessage();
12 |
13 | res.render('index', {message: message});
14 | }
15 | };
--------------------------------------------------------------------------------
/app/controllers/register.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const RegisterService = require('../services/register');
4 |
5 | module.exports = {
6 |
7 | /**
8 | * Index action
9 | */
10 | index: (req, res, next) => {
11 | res.render('register/index');
12 | },
13 |
14 | /**
15 | * Register action
16 | */
17 | register: (req, res, next) => {
18 | RegisterService.register(req.body, (err, errors, user) => {
19 | if (err) {
20 | return next(err);
21 | }
22 |
23 | if (errors) {
24 | for (let error in errors) {
25 | if ((error = errors[error]).messageCode) {
26 | req.flash('warning', req.__(error.messageCode, req.__(error.property), error.value));
27 | }
28 | }
29 | return res.render('register/index', {user: req.body});
30 | }
31 |
32 | if (!user) {
33 | req.flash('warning', req.__('user.error-creating'));
34 | return res.render('register/index', {user: req.body});
35 | }
36 |
37 | req.flash('success', req.__('user.created'));
38 | res.redirect('/');
39 | });
40 | }
41 | };
--------------------------------------------------------------------------------
/app/models/user.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const mongoose = require('mongoose');
4 | const uniqueValidator = require('mongoose-unique-validator');
5 | const Schema = mongoose.Schema;
6 | const CryptUtils = require('../utils/crypt');
7 | const validator = require('validator');
8 |
9 | const UserSchema = new Schema({
10 | name: {
11 | type: String,
12 | required: true
13 | },
14 | last_name: {
15 | type: String
16 | },
17 | email: {
18 | type: String,
19 | index: { unique: true },
20 | required: true,
21 | validate: [email => { // Since Validator version 5.0.0 validator.isEmail itself does not work. I had to wrap it with a function, so it works.
22 | return validator.isEmail(email)
23 | }, 'user.invalidemail']
24 | },
25 | user_name: {
26 | type: String,
27 | index: { unique: true },
28 | required: true
29 | },
30 | password: {
31 | type: String
32 | },
33 | facebook_id: {
34 | type: String,
35 | index: {
36 | unique: true,
37 | sparse: true
38 | }
39 | },
40 | twitter_id: {
41 | type: String,
42 | index: {
43 | unique: true,
44 | sparse: true
45 | }
46 | },
47 | google_id: {
48 | type: String,
49 | index: {
50 | unique: true,
51 | sparse: true
52 | }
53 | },
54 | github_id: {
55 | type: String,
56 | index: {
57 | unique: true,
58 | sparse: true
59 | }
60 | },
61 | linkedin_id: {
62 | type: String,
63 | index: {
64 | unique: true,
65 | sparse: true
66 | }
67 | },
68 | instagram_id: {
69 | type: String,
70 | index: {
71 | unique: true,
72 | sparse: true
73 | }
74 | },
75 | creation_date: {
76 | type: Date,
77 | default: new Date()
78 | }
79 | });
80 |
81 | UserSchema.post('validate', doc => {
82 | if (doc.isModified('password')) {
83 | doc.password = CryptUtils.encrypt(doc.password);
84 | }
85 |
86 | if (!doc._id) {
87 | doc.creation_date = Date.now();
88 | }
89 | });
90 |
91 | UserSchema.plugin(uniqueValidator, { message: 'mongoose.unique-error' });
92 |
93 | module.exports = mongoose.model('User', UserSchema);
--------------------------------------------------------------------------------
/app/public/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rodrigogs/nodejs-web-jade-scaffold/b2d87b790a02baaa82f7b8e7317ce854a97a72bd/app/public/images/logo.png
--------------------------------------------------------------------------------
/app/public/javascript/application.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rodrigogs/nodejs-web-jade-scaffold/b2d87b790a02baaa82f7b8e7317ce854a97a72bd/app/public/javascript/application.js
--------------------------------------------------------------------------------
/app/public/stylesheets/cover.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Globals
3 | */
4 |
5 | /* Links */
6 | a,
7 | a:focus,
8 | a:hover {
9 | color: #fff;
10 | }
11 |
12 | /* Custom default button */
13 | .btn-default,
14 | .btn-default:hover,
15 | .btn-default:focus {
16 | color: #333;
17 | text-shadow: none; /* Prevent inheritence from `body` */
18 | background-color: #fff;
19 | border: 1px solid #fff;
20 | }
21 |
22 |
23 | /*
24 | * Base structure
25 | */
26 |
27 | html,
28 | body {
29 | height: 100%;
30 | background-color: #333;
31 | }
32 | body {
33 | color: #fff;
34 | text-align: center;
35 | text-shadow: 0 1px 3px rgba(0,0,0,.5);
36 | }
37 |
38 | /* Extra markup and styles for table-esque vertical and horizontal centering */
39 | .site-wrapper {
40 | display: table;
41 | width: 100%;
42 | height: 100%; /* For at least Firefox */
43 | min-height: 100%;
44 | -webkit-box-shadow: inset 0 0 100px rgba(0,0,0,.5);
45 | box-shadow: inset 0 0 100px rgba(0,0,0,.5);
46 | }
47 | .site-wrapper-inner {
48 | display: table-cell;
49 | vertical-align: top;
50 | }
51 | .cover-container {
52 | margin-right: auto;
53 | margin-left: auto;
54 | }
55 |
56 | /* Padding for spacing */
57 | .inner {
58 | padding: 30px;
59 | }
60 |
61 |
62 | /*
63 | * Header
64 | */
65 | .masthead-brand {
66 | margin-top: 10px;
67 | margin-bottom: 10px;
68 | }
69 |
70 | .masthead-nav > li {
71 | display: inline-block;
72 | }
73 | .masthead-nav > li + li {
74 | margin-left: 20px;
75 | }
76 | .masthead-nav > li > a {
77 | padding-right: 0;
78 | padding-left: 0;
79 | font-size: 16px;
80 | font-weight: bold;
81 | color: #fff; /* IE8 proofing */
82 | color: rgba(255,255,255,.75);
83 | border-bottom: 2px solid transparent;
84 | }
85 | .masthead-nav > li > a:hover,
86 | .masthead-nav > li > a:focus {
87 | background-color: transparent;
88 | border-bottom-color: #a9a9a9;
89 | border-bottom-color: rgba(255,255,255,.25);
90 | }
91 | .masthead-nav > .active > a,
92 | .masthead-nav > .active > a:hover,
93 | .masthead-nav > .active > a:focus {
94 | color: #fff;
95 | border-bottom-color: #fff;
96 | }
97 |
98 | @media (min-width: 768px) {
99 | .masthead-brand {
100 | float: left;
101 | }
102 | .masthead-nav {
103 | float: right;
104 | }
105 | }
106 |
107 |
108 | /*
109 | * Cover
110 | */
111 |
112 | .cover {
113 | padding: 65px 20px;
114 | }
115 | .cover .btn-lg {
116 | padding: 10px 20px;
117 | font-weight: bold;
118 | }
119 |
120 |
121 | /*
122 | * Footer
123 | */
124 |
125 | .mastfoot {
126 | color: #999; /* IE8 proofing */
127 | color: rgba(255,255,255,.5);
128 | }
129 |
130 |
131 | /*
132 | * Affix and center
133 | */
134 |
135 | @media (min-width: 768px) {
136 | /* Pull out the header and footer */
137 | .masthead {
138 | position: fixed;
139 | top: 0;
140 | }
141 | .mastfoot {
142 | position: fixed;
143 | bottom: 0;
144 | }
145 | /* Start the vertical centering */
146 | .site-wrapper-inner {
147 | vertical-align: middle;
148 | }
149 | /* Handle the widths */
150 | .masthead,
151 | .mastfoot,
152 | .cover-container {
153 | width: 100%; /* Must be percentage or pixels for horizontal alignment */
154 | }
155 | }
156 |
157 | @media (min-width: 992px) {
158 | .masthead,
159 | .mastfoot,
160 | .cover-container {
161 | width: 700px;
162 | }
163 | }
--------------------------------------------------------------------------------
/app/public/stylesheets/style.css:
--------------------------------------------------------------------------------
1 | .cover-heading {
2 | margin-top: 50px;
3 | }
4 |
5 | #tech-slider li.sy-slide > a {
6 | height: 100%;
7 | width: auto;
8 | }
9 |
10 | #tech-slider li.sy-slide a > img {
11 | max-width: 100%;
12 | max-height: 250px;
13 | height: auto;
14 | width: auto;
15 | margin: auto;
16 | }
--------------------------------------------------------------------------------
/app/routes/api/user.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const express = require('express');
4 | const router = express.Router();
5 |
6 | const UserController = require('../../controllers/api/user');
7 |
8 | router.route('/')
9 | .get(UserController.list)
10 | .post(UserController.create)
11 |
12 | router.route('/:id')
13 | .get(UserController.findById)
14 | .put(UserController.update)
15 | .delete(UserController.delete)
16 |
17 | module.exports = router;
--------------------------------------------------------------------------------
/app/routes/auth.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const express = require('express');
4 | const router = express.Router();
5 |
6 | const AuthController = require('../controllers/auth');
7 |
8 | router.route('/auth/login')
9 | .get(AuthController.index)
10 | .post(AuthController.login)
11 | .get(AuthController.logout);
12 |
13 | router
14 | .get('/auth/facebook', AuthController.facebook)
15 | .get('/auth/facebook/callback', AuthController.facebookCallback);
16 |
17 | router
18 | .get('/auth/twitter', AuthController.twitter)
19 | .get('/auth/twitter/callback', AuthController.twitterCallback);
20 |
21 | router
22 | .get('/auth/google', AuthController.google)
23 | .get('/auth/google/callback', AuthController.googleCallback);
24 |
25 | router
26 | .get('/auth/github', AuthController.github)
27 | .get('/auth/github/callback', AuthController.githubCallback);
28 |
29 | router
30 | .get('/auth/linkedin', AuthController.linkedin)
31 | .get('/auth/linkedin/callback', AuthController.linkedinCallback);
32 |
33 | router
34 | .get('/auth/instagram', AuthController.instagram)
35 | .get('/auth/instagram/callback', AuthController.instagramCallback);
36 |
37 | router.route('/auth/logout')
38 | .get(AuthController.logout);
39 |
40 | module.exports = router;
--------------------------------------------------------------------------------
/app/routes/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const express = require('express');
4 | const router = express.Router();
5 |
6 | const IndexController = require('../controllers/index');
7 |
8 | router.route('/')
9 | .get(IndexController.index);
10 |
11 | module.exports = router;
--------------------------------------------------------------------------------
/app/routes/register.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const express = require('express');
4 | const router = express.Router();
5 |
6 | const RegisterController = require('../controllers/register');
7 |
8 | router.route('/register')
9 | .get(RegisterController.index)
10 | .post(RegisterController.register);
11 |
12 | module.exports = router;
--------------------------------------------------------------------------------
/app/services/auth.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const validator = require('validator');
4 | const UserSchema = require('../models/user');
5 |
6 | /**
7 | *
8 | */
9 | const _getQuery = info => {
10 | const query = {$or: []};
11 | if (info.user_name) {
12 | query.$or.push({
13 | user_name: info.user_name,
14 | password: info.password
15 | });
16 | }
17 | if (info.email) query.$or.push({email: info.email});
18 | if (info.facebook_id) query.$or.push({facebook_id: info.facebook_id});
19 | if (info.twitter_id) query.$or.push({twitter_id: info.twitter_id});
20 | if (info.google_id) query.$or.push({google_id: info.google_id});
21 | if (info.github_id) query.$or.push({github_id: info.github_id});
22 | if (info.linkedin_id) query.$or.push({linkedin_id: info.linkedin_id});
23 | if (info.instagram_id) query.$or.push({instagram_id: info.instagram_id});
24 |
25 | return query;
26 | };
27 |
28 | /**
29 | *
30 | */
31 | const _resolveLocal = (user, callback) => {
32 | if (!user) {
33 | return callback(null, false, {type: 'danger', message: 'auth.failed'});
34 | }
35 |
36 | return callback(null, user);
37 | };
38 |
39 | /**
40 | *
41 | */
42 | const _updateUser = (user, info, callback) => {
43 | user.facebook_id = info.facebook_id || user.facebook_id;
44 | user.twitter_id = info.twitter_id || user.twitter_id;
45 | user.google_id = info.google_id || user.google_id;
46 | user.github_id = info.github_id || user.github_id;
47 | user.linkedin_id = info.linkedin_id || user.linkedin_id;
48 | user.instagram_id = info.instagram_id || user.instagram_id;
49 |
50 | user.save(callback);
51 | };
52 |
53 | /**
54 | *
55 | */
56 | const _createUser = (info, callback) => {
57 | if (!info.user_name && info.email && validator.isEmail(info.email)) {
58 | info.user_name = info.email.split('@')[0];
59 | }
60 |
61 | let user = new UserSchema(info);
62 |
63 | user.save(callback);
64 | };
65 |
66 | module.exports = {
67 |
68 | /**
69 | * ResolveUser
70 | */
71 | resolveUser: (userInfo, callback) => {
72 | const isLocal = userInfo.isLocal;
73 | delete userInfo.isLocal;
74 |
75 | UserSchema.findOne(_getQuery(userInfo), (err, user) => {
76 | if (err) {
77 | return callback(err);
78 | }
79 |
80 | if (isLocal) {
81 | return _resolveLocal(user, callback);
82 | }
83 |
84 | if (user) {
85 | return _updateUser(user, userInfo, callback);
86 | }
87 |
88 | return _createUser(userInfo, callback);
89 | });
90 | }
91 | };
--------------------------------------------------------------------------------
/app/services/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 |
5 | /**
6 | *
7 | */
8 | getMessage: () => {
9 | return 'main.cover-description';
10 | }
11 | };
--------------------------------------------------------------------------------
/app/services/register.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const UserSchema = require('../models/user');
4 | const ValidationUtils = require('../utils/validation');
5 |
6 | module.exports = {
7 |
8 | /**
9 | *
10 | */
11 | register: (user, callback) => {
12 | UserSchema.findOne({user_name: user.user_name}, (err, usr) => {
13 | if (err) {
14 | return callback(err);
15 | }
16 |
17 | if (usr) {
18 | return callback(null, [{ messageCode: 'user.exists' }]);
19 | }
20 |
21 | user = new UserSchema(user);
22 | user.save((err, user) => {
23 | if (err) {
24 | if (ValidationUtils.hasValidationErrors(err)) {
25 | return callback(null, ValidationUtils.extractErrors(err, 'user'));
26 | }
27 | return callback(err);
28 | }
29 |
30 | callback(null, null, user);
31 | });
32 | });
33 | }
34 | };
--------------------------------------------------------------------------------
/app/utils/crypt.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const crypto = require('crypto');
4 |
5 | module.exports = {
6 |
7 | /**
8 | *
9 | */
10 | encrypt: value => {
11 | let cipher = crypto.createCipher('aes-256-ctr', 'my-key');
12 | let crypted = cipher.update(value, 'utf8', 'hex');
13 | crypted += cipher.final('hex');
14 |
15 | return crypted;
16 | }
17 | };
--------------------------------------------------------------------------------
/app/utils/validation.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const filterObject = require('filter-object');
4 |
5 | module.exports = {
6 |
7 | /**
8 | *
9 | */
10 | hasValidationErrors: error => {
11 | if (!error.errors) {
12 | return false;
13 | }
14 |
15 | return !!filterObject(error.errors, err => {
16 | return err.name === 'ValidatorError';
17 | });
18 | },
19 |
20 | /**
21 | *
22 | */
23 | extractErrors: (error, i18nAlias) => {
24 | const errors = [];
25 |
26 | if (!error.errors) {
27 | return [];
28 | }
29 |
30 | const validationErrors = filerObject(error.errors, err => {
31 | return err.name === 'ValidatorError';
32 | });
33 |
34 | for (let err in validationErrors) {
35 | if ((err = validationErrors[err]).message) {
36 | errors.push({
37 | messageCode: err.message,
38 | property: i18nAlias ? `${i18nAlias}.${err.path}` : err.path,
39 | value: err.value
40 | });
41 | }
42 | }
43 |
44 | return errors;
45 | }
46 | };
47 |
--------------------------------------------------------------------------------
/app/views/auth/login.jade:
--------------------------------------------------------------------------------
1 | extends ../layouts/default
2 |
3 | block stylesheets
4 |
5 | block javascripts
6 |
7 | block content
8 | .masthead.clearfix
9 | .inner
10 | h3.masthead-brand #{__('login')}
11 | nav
12 | ul.nav.masthead-nav
13 | li.active
14 | a(href='/') #{__('landing-page')}
15 |
16 | .inner.cover
17 | include ../includes/messages
18 |
19 | form.form.col-md-12.center-block(method='post')
20 | if AUTH_CONFIG.LOCAL.ENABLED
21 | .form-group
22 | input.form-control.input-lg(type='text' placeholder='#{__("user.user_name")}' name='username')
23 | .form-group
24 | input.form-control.input-lg(type='password' placeholder='#{__("user.password")}' name='password')
25 | .form-group
26 | button.btn.btn-success.btn-lg.btn-block #{__('sign-in')}
27 | span.pull-right
28 | a(href='/register') #{__('register')}
29 |
30 | br
31 | hr
32 |
33 | .row
34 | if AUTH_CONFIG.FACEBOOK.ENABLED
35 | .col-xs-12.col-md-6
36 | .form-group
37 | a.btn.btn-block.btn-social.btn-facebook(href='/auth/facebook')
38 | span.fa.fa-facebook
39 | | #{__('facebook.sign-in')}
40 |
41 | if AUTH_CONFIG.TWITTER.ENABLED
42 | .col-xs-12.col-md-6
43 | .form-group
44 | a.btn.btn-block.btn-social.btn-twitter(href='/auth/twitter')
45 | span.fa.fa-twitter
46 | | #{__('twitter.sign-in')}
47 |
48 | if AUTH_CONFIG.GOOGLE.ENABLED
49 | .col-xs-12.col-md-6
50 | .form-group
51 | a.btn.btn-block.btn-social.btn-google(href='/auth/google')
52 | span.fa.fa-google
53 | | #{__('google.sign-in')}
54 |
55 | if AUTH_CONFIG.GITHUB.ENABLED
56 | .col-xs-12.col-md-6
57 | .form-group
58 | a.btn.btn-block.btn-social.btn-github(href='/auth/github')
59 | span.fa.fa-github
60 | | #{__('github.sign-in')}
61 |
62 | if AUTH_CONFIG.LINKEDIN.ENABLED
63 | .col-xs-12.col-md-6
64 | .form-group
65 | a.btn.btn-block.btn-social.btn-linkedin(href='/auth/linkedin')
66 | span.fa.fa-linkedin
67 | | #{__('linkedin.sign-in')}
68 |
69 | if AUTH_CONFIG.INSTAGRAM.ENABLED
70 | .col-xs-12.col-md-6
71 | .form-group
72 | a.btn.btn-block.btn-social.btn-instagram(href='/auth/instagram')
73 | span.fa.fa-instagram
74 | | #{__('instagram.sign-in')}
--------------------------------------------------------------------------------
/app/views/error.jade:
--------------------------------------------------------------------------------
1 | extends layouts/default
2 |
3 | block content
4 | .masthead.clearfix
5 | .inner
6 | h3.masthead-brand #{__('error')}
7 | nav
8 | ul.nav.masthead-nav
9 | li.active
10 | a(href='/') #{__('landing-page')}
11 |
12 | .inner.cover
13 | include includes/messages
14 |
15 | p.lead #{error.message}
16 | p #{error.stack}
--------------------------------------------------------------------------------
/app/views/includes/facebook-meta.jade:
--------------------------------------------------------------------------------
1 | meta(property='og:url' content='//nodejs-web-jade-scaffold.herokuapp.com/')
2 | meta(property='og:type' content='article')
3 | meta(property='og:title' content='#{__("title")}')
4 | meta(property='og:description' content='#{__("main.cover-description")}')
5 | meta(property='og:image' content='http://nodejs-web-jade-scaffold.herokuapp.com/static/images/logo.png')
--------------------------------------------------------------------------------
/app/views/includes/footer.jade:
--------------------------------------------------------------------------------
1 | p GitHub 
2 | a(href='https://github.com/rodrigogs/nodejs-web-jade-scaffold') Repository
3 | | , by 
4 | a(href='https://github.com/rodrigogs') rodrigogs
5 | | .
--------------------------------------------------------------------------------
/app/views/includes/landing.jade:
--------------------------------------------------------------------------------
1 | h1.cover-heading #{__('main.cover-title')}
2 | p.lead #{__(message)}
3 |
4 | .inner.cover
5 | ul#out-of-the-box-demo
6 | li
7 | a(href='https://nodejs.org')
8 | img(src='https://nodejs.org/static/images/logos/nodejs-new-white-pantone.png')
9 | li
10 | a(href='http://jade-lang.com/')
11 | img(src='http://jade-lang.com/style/jade-logo-header.svg')
12 | li
13 | a(href='http://expressjs.com')
14 | img(src='https://i.cloudup.com/zfY6lL7eFa-3000x3000.png')
15 | li
16 | a(href='http://passportjs.org/')
17 | img(src='http://passportjs.org/images/logo.svg')
18 | li
19 | a(href='https://www.mongodb.org/')
20 | img(src='https://upload.wikimedia.org/wikipedia/en/thumb/4/45/MongoDB-Logo.svg/527px-MongoDB-Logo.svg.png')
21 | li
22 | a(href='http://getbootstrap.com/')
23 | img(src='https://upload.wikimedia.org/wikipedia/commons/e/ea/Boostrap_logo.svg')
--------------------------------------------------------------------------------
/app/views/includes/messages.jade:
--------------------------------------------------------------------------------
1 | while _msg = flash.shift()
2 | - _msg.type = _msg.type === 'error' ? 'danger' : _msg.type;
3 | div(class='alert alert-#{_msg.type}' role='alert')
4 | p= __(_msg.message)
--------------------------------------------------------------------------------
/app/views/includes/meta.jade:
--------------------------------------------------------------------------------
1 | meta(http-equiv='Content-Type' content='text/html; charset=utf-8')
2 | meta(http-equiv='cache-control' content='max-age=86400000')
3 | meta(http-equiv='X-UA-Compatible' content='IE=edge')
4 | meta(name='viewport' content='width=device-width, initial-scale=1')
5 | meta(name='robots' content='index, follow')
6 | meta(name='description' content='#{__("main.cover-description")}')
7 | meta(name='keywords' content='JavaScript,Node.js,Jade,Express,Passport,Scaffold,MongoDB,Bootstrap,kickstart,opensource')
8 | meta(name='author' content='Rodrigo Gomes da Silva')
--------------------------------------------------------------------------------
/app/views/includes/twitter-cards.jade:
--------------------------------------------------------------------------------
1 | meta(name='twitter:card' content='summary')
2 | meta(name='twitter:site' content='@rodrigoagogo')
3 | meta(name='twitter:creator' content='@rodrigoagogo')
4 | meta(name='twitter:title' content='#{__("title")}')
5 | meta(name='twitter:description' content='#{__("main.cover-description")}')
6 | meta(name='twitter:image' content='http://nodejs-web-jade-scaffold.herokuapp.com/static/images/logo.png')
--------------------------------------------------------------------------------
/app/views/includes/user-info.jade:
--------------------------------------------------------------------------------
1 | h1.cover-heading #{user.info.name}
2 | if user.info.image
3 | .lead
4 | img(src='#{user.info.image}')
5 | if user.info.description
6 | p.lead #{user.info.description}
--------------------------------------------------------------------------------
/app/views/index.jade:
--------------------------------------------------------------------------------
1 | extends layouts/default
2 |
3 | block stylesheets
4 |
5 | block javascripts
6 | script(type='text/javascript').
7 | $('#out-of-the-box-demo').slippry({
8 | transition: 'fade',
9 | slippryWrapper: '',
10 | captions: false,
11 | pager: false
12 | });
13 |
14 | block content
15 | .masthead.clearfix
16 | .inner
17 | h3.masthead-brand #{__('landing-page')}
18 | if AUTH_CONFIG.ENABLED
19 | nav
20 | ul.nav.masthead-nav
21 | li.active
22 | if user
23 | a(href='/auth/logout') #{__('logout')}
24 | else
25 | a(href='/auth/login') #{__('login')}
26 |
27 | include includes/messages
28 |
29 | if user
30 | include includes/user-info
31 | else
32 | include includes/landing
--------------------------------------------------------------------------------
/app/views/layouts/default.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang='en')
3 | head
4 | include ../includes/meta
5 | include ../includes/twitter-cards
6 | include ../includes/facebook-meta
7 |
8 | block meta
9 |
10 | title #{__('title')}
11 | body
12 | .site-wrapper
13 | .site-wrapper-inner
14 | .cover-container
15 | block content
16 | .mastfoot
17 | .inner
18 | include ../includes/footer
19 |
20 | link(rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css' integrity='sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7' crossorigin='anonymous')
21 | link(rel='stylesheet' href='/static/vendor/bootstrap-social/bootstrap-social.css')
22 | link(rel='stylesheet' href='/static/vendor/font-awesome/css/font-awesome.min.css')
23 | link(rel='stylesheet' href='/static/vendor/slippry/dist/slippry.css')
24 | link(rel='stylesheet' href='/static/stylesheets/cover.css')
25 | link(rel='stylesheet' href='/static/stylesheets/style.css')
26 |
27 | block stylesheets
28 |
29 | script(src='/static/javascript/application.js')
30 | script(src='https://code.jquery.com/jquery-2.1.4.min.js')
31 | script(src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js' integrity='sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS' crossorigin='anonymous')
32 | script(src='/static/vendor/slippry/dist/slippry.min.js')
33 |
34 | block javascripts
--------------------------------------------------------------------------------
/app/views/register/index.jade:
--------------------------------------------------------------------------------
1 | extends ../layouts/default
2 |
3 | block stylesheets
4 |
5 | block javascripts
6 |
7 | block content
8 | .masthead.clearfix
9 | .inner
10 | h3.masthead-brand #{__('register')}
11 | nav
12 | ul.nav.masthead-nav
13 | li.active
14 | a(href='/auth/login') #{__('login')}
15 |
16 | .inner.cover
17 | include ../includes/messages
18 |
19 | - user = user || {};
20 | form.form.col-md-12.center-block(method='post')
21 | .form-group
22 | input.form-control.input-lg(type='text' placeholder='#{__("user.name")}' name='name' required value='#{user.name || ""}')
23 | .form-group
24 | input.form-control.input-lg(type='text' placeholder='#{__("user.last_name")}' name='last_name' required value='#{user.last_name || ""}')
25 | .form-group
26 | input.form-control.input-lg(type='email' placeholder='#{__("user.email")}' name='email' required value='#{user.email || ""}')
27 | .form-group
28 | input.form-control.input-lg(type='text' placeholder='#{__("user.user_name")}' name='user_name' required value='#{user.user_name || ""}')
29 | .form-group
30 | input.form-control.input-lg(type='password' placeholder='#{__("user.password")}' name='password' required value='#{user.password || ""}')
31 | .form-group
32 | button.btn.btn-success.btn-lg.btn-block #{__('register')}
--------------------------------------------------------------------------------
/bin/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | /**
5 | * Module dependencies.
6 | */
7 | const app = require('../main');
8 | const http = require('http');
9 |
10 | /**
11 | * Normalize a port into a number, string, or false.
12 | */
13 | const normalizePort = val => {
14 | const port = parseInt(val, 10);
15 |
16 | if (isNaN(port)) {
17 | // named pipe
18 | return val;
19 | }
20 |
21 | if (port >= 0) {
22 | // port number
23 | return port;
24 | }
25 |
26 | return false;
27 | };
28 |
29 | /**
30 | * Event listener for HTTP server "error" event.
31 | */
32 | const onError = error => {
33 | if (error.syscall !== 'listen') {
34 | throw error;
35 | }
36 |
37 | const bind = typeof port === 'string'
38 | ? `Pipe ${port}`
39 | : `Port ${port}`;
40 |
41 | // handle specific listen errors with friendly messages
42 | switch (error.code) {
43 | case 'EACCES':
44 | console.error(`${bind} requires elevated privileges`);
45 | process.exit(1);
46 | break;
47 | case 'EADDRINUSE':
48 | console.error(`${bind} is already in use`);
49 | process.exit(1);
50 | break;
51 | default:
52 | throw error;
53 | }
54 | };
55 |
56 | /**
57 | * Event listener for HTTP server "listening" event.
58 | */
59 | const onListening = () => {
60 | const addr = server.address();
61 | console.log(`Listening at ${addr.address}:${addr.port}`);
62 | };
63 |
64 | /**
65 | * Get port from environment and store in Express.
66 | */
67 | const port = normalizePort(process.env.PORT || '3000');
68 | app.set('port', port);
69 | app.set('address', process.env.IP || 'localhost');
70 |
71 | /**
72 | * Create HTTP server.
73 | */
74 | let server = http.createServer(app);
75 |
76 | /**
77 | * Listen on provided port, on all network interfaces.
78 | */
79 | if (process.env.IP) {
80 | server.listen(app.get('port'), app.get('address'));
81 | } else {
82 | server.listen(app.get('port'));
83 | }
84 |
85 | server.on('error', onError);
86 | server.on('listening', onListening);
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nodejs-web-jade-scaffold",
3 | "description": "",
4 | "main": "main.js",
5 | "authors": [
6 | "Rodrigo Gomes da Silva "
7 | ],
8 | "license": "BSD-2-Clause",
9 | "keywords": [
10 | "web",
11 | "jade",
12 | "scaffold",
13 | "express",
14 | "kickstart",
15 | "simple"
16 | ],
17 | "moduleType": [],
18 | "homepage": "",
19 | "ignore": [
20 | "**/.*",
21 | "node_modules",
22 | "bower_components",
23 | "test",
24 | "tests"
25 | ],
26 | "dependencies": {
27 | "bootstrap-social": "~5.0.0",
28 | "slippry": "^1.3.1"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/config/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "DATABASE": {
3 | "RECONNECT": true,
4 | "RECONNECTION_INTERVAL": 5000
5 | },
6 | "HTTP_LOG_CONFIG": "dev",
7 | "LOCALES": [
8 | "en",
9 | "pt",
10 | "pt-br"
11 | ],
12 | "AUTH": {
13 | "ENABLED": true,
14 | "LOCAL": {
15 | "ENABLED": true
16 | },
17 | "FACEBOOK": {
18 | "ENABLED": true,
19 | "PROFILE_FIELDS": [
20 | "id",
21 | "first_name",
22 | "last_name",
23 | "email",
24 | "picture",
25 | "about",
26 | "bio"
27 | ],
28 | "OPTIONS": {
29 | "display": "touch",
30 | "scope": [
31 | "public_profile",
32 | "user_about_me",
33 | "email"
34 | ],
35 | "authorizationURL": "https://www.facebook.com/v2.5/dialog/oauth",
36 | "profileURL": "https://graph.facebook.com/v2.5/me"
37 | }
38 | },
39 | "TWITTER": {
40 | "ENABLED": true
41 | },
42 | "GOOGLE": {
43 | "ENABLED": true,
44 | "OPTIONS": {
45 | "scope": [
46 | "https://www.googleapis.com/auth/userinfo.profile",
47 | "https://www.googleapis.com/auth/userinfo.email"
48 | ]
49 | }
50 | },
51 | "GITHUB": {
52 | "ENABLED": true
53 | },
54 | "LINKEDIN": {
55 | "ENABLED": true,
56 | "OPTIONS": {
57 | "state": "SOME STATE",
58 | "scope": [
59 | "r_emailaddress",
60 | "r_basicprofile"
61 | ]
62 | }
63 | },
64 | "INSTAGRAM": {
65 | "ENABLED": true
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/config/mongoose.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const CONFIG = require('./config.json');
4 | const mongoose = require('mongoose');
5 |
6 | const CONN_STR = process.env.DATABASE_URL;
7 |
8 | mongoose.connect(CONN_STR);
9 |
10 | mongoose.connection.on('connected', () => {
11 | console.log(`Mongoose default connection open to ${CONN_STR}`);
12 | });
13 |
14 | mongoose.connection.on('error', err => {
15 | console.log(`Mongoose default connection error: ${err}`);
16 | if (CONFIG.DATABASE.RECONNECT) {
17 | return;
18 | }
19 | process.exit(1);
20 | });
21 |
22 | mongoose.connection.on('disconnected', () => {
23 | console.log('Mongoose default connection disconnected');
24 | if (CONFIG.DATABASE.RECONNECT) {
25 | reconnect();
26 | }
27 | });
28 |
29 | mongoose.connection.once('open', () => {
30 | console.log('Mongoose default connection is open');
31 | });
32 |
33 | process.on('SIGINT', () => {
34 | mongoose.connection.close(() => {
35 | console.log('Mongoose default connection disconnected through app termination');
36 | process.exit(0);
37 | });
38 | });
39 |
40 | const reconnect = () => {
41 | const timeout = CONFIG.DATABASE.RECONNECTION_INTERVAL || 5000;
42 | console.log(`Retrying to connect to database in ${timeout / 1000} seconds`);
43 |
44 | setTimeout(() => {
45 | mongoose.connect(CONN_STR);
46 | }, timeout);
47 | };
--------------------------------------------------------------------------------
/config/passport.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const CONFIG = require('./config.json');
4 | const passport = require('passport');
5 | const LocalStrategy = require('passport-local').Strategy;
6 | const FacebookStrategy = require('passport-facebook').Strategy;
7 | const TwitterStrategy = require('passport-twitter').Strategy;
8 | const GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
9 | const GitHubStrategy = require('passport-github').Strategy;
10 | const LinkedInStrategy = require('passport-linkedin-oauth2').Strategy;
11 | const InstagramStrategy = require('passport-instagram').Strategy;
12 | const AuthService = require('../app/services/auth');
13 | const UserSchema = require('../app/models/user');
14 | const CryptUtils = require('../app/utils/crypt');
15 |
16 | const _getThirdPartyInfo = profile => {
17 | if (profile.provider === 'facebook') {
18 | return {
19 | name: `${profile._json.first_name} ${profile._json.last_name}`,
20 | description: profile._json.about || profile._json.bio,
21 | image: profile._json.picture.data.url
22 | };
23 | }
24 | if (profile.provider === 'twitter') {
25 | return {
26 | name: profile.displayName,
27 | description: profile._json.description,
28 | image: profile._json.profile_image_url
29 | };
30 | }
31 | if (profile.provider === 'google') {
32 | const currentOrg = profile._json
33 | .organizations.find((org) => { return org.primary === true }) || {};
34 |
35 | const currentPlace = profile
36 | ._json.placesLived.find((pla) => { return pla.primary === true }) || {};
37 |
38 | return {
39 | name: profile.displayName,
40 | description: (currentOrg ? `${currentOrg.title} - ${currentOrg.name}` : currentPlace.value) || '',
41 | image: profile.photos[0].value
42 | };
43 | }
44 | if (profile.provider === 'github') {
45 | return {
46 | name: profile.displayName,
47 | description: ((profile._json.company || profile._json.location) || progile._json.blog) || profile._json.email,
48 | image: profile.photos[0].value
49 | };
50 | }
51 | if (profile.provider === 'linkedin') {
52 | return {
53 | name: profile.displayName,
54 | description: profile._json.headline,
55 | image: profile.photos[0].value
56 | }
57 | }
58 | if (profile.provider === 'instagram') {
59 | return {
60 | name: profile.displayName,
61 | description: profile._json.data.bio,
62 | image: profile._json.data.profile_picture
63 | }
64 | }
65 | };
66 |
67 | /*--------------- Serialization ---------------*/
68 |
69 | passport.serializeUser((user, done) => {
70 | done(null, {id: user._id, info: user.info});
71 | });
72 |
73 | passport.deserializeUser((user, done) => {
74 | const info = user.info || {};
75 | UserSchema.findById(user.id, (err, user) => {
76 | if (err) {
77 | return done(err);
78 | }
79 | user = user.toObject();
80 | user.info = info;
81 | done(err, user);
82 | });
83 | });
84 |
85 | /*----------------- Strategies -----------------*/
86 |
87 | if (CONFIG.AUTH.LOCAL.ENABLED) {
88 | passport.use(new LocalStrategy((username, password, done) => {
89 | AuthService.resolveUser({
90 | user_name: username,
91 | password: CryptUtils.encrypt(password),
92 | isLocal: true
93 | }, (err, user, info) => {
94 | if (err) {
95 | return done(err);
96 | }
97 | if (user) {
98 | user.info = { name: `${user.name} ${user.last_name}`};
99 | }
100 | done(null, user, info);
101 | });
102 | }));
103 | }
104 |
105 | if (CONFIG.AUTH.FACEBOOK.ENABLED) {
106 | passport.use(new FacebookStrategy({
107 | clientID: process.env.FACEBOOK_APP_ID,
108 | clientSecret: process.env.FACEBOOK_APP_SECRET,
109 | callbackURL: '/auth/facebook/callback',
110 | profileFields: CONFIG.AUTH.FACEBOOK.PROFILE_FIELDS
111 | }, (accessToken, refreshToken, profile, done) => {
112 | AuthService.resolveUser({
113 | facebook_id: profile.id,
114 | email: profile._json.email,
115 | name: profile._json.first_name,
116 | last_name: profile._json.last_name
117 | }, (err, user, info) => {
118 | if (err) {
119 | return done(err);
120 | }
121 | if (user) {
122 | user.info = _getThirdPartyInfo(profile);
123 | }
124 | done(null, user, info);
125 | });
126 | }));
127 | }
128 |
129 | if (CONFIG.AUTH.TWITTER.ENABLED) {
130 | passport.use(new TwitterStrategy({
131 | consumerKey: process.env.TWITTER_CONSUMER_KEY,
132 | consumerSecret: process.env.TWITTER_CONSUMER_SECRET,
133 | callbackURL: "/auth/twitter/callback",
134 | includeEmail: true
135 | }, (accessToken, refreshToken, profile, done) => {
136 | AuthService.resolveUser({
137 | twitter_id: profile.id,
138 | name: profile.displayName.split(' ')[0],
139 | last_name: profile.displayName.split(' ').slice(1).join(' '),
140 | email: `${profile.id}@twitter.com` // Until there is no way to retrieve email from twitter API
141 | }, (err, user, info) => {
142 | if (err) {
143 | return done(err);
144 | }
145 | if (user) {
146 | user.info = _getThirdPartyInfo(profile);
147 | }
148 | done(null, user, info);
149 | });
150 | }));
151 | }
152 |
153 | if (CONFIG.AUTH.GOOGLE.ENABLED) {
154 | passport.use(new GoogleStrategy({
155 | clientID: process.env.GOOGLE_CLIENT_ID,
156 | clientSecret: process.env.GOOGLE_CLIENT_SECRET,
157 | callbackURL: '/auth/google/callback'
158 | }, (accessToken, refreshToken, profile, done) => {
159 | // Dont forget to enable Google+ API in Developer Console in order to get user's information
160 | AuthService.resolveUser({
161 | google_id: profile.id,
162 | name: profile.name.givenName,
163 | last_name: profile.name.familyName,
164 | email: profile.emails[0].value
165 | }, (err, user, info) => {
166 | if (err) {
167 | return done(err);
168 | }
169 | if (user) {
170 | user.info = _getThirdPartyInfo(profile);
171 | }
172 | done(null, user, info);
173 | });
174 | }));
175 | }
176 |
177 | if (CONFIG.AUTH.GITHUB.ENABLED) {
178 | passport.use(new GitHubStrategy({
179 | clientID: process.env.GITHUB_CLIENT_ID,
180 | clientSecret: process.env.GITHUB_CLIENT_SECRET,
181 | callbackURL: '/auth/github/callback'
182 | }, (accessToken, refreshToken, profile, done) => {
183 | AuthService.resolveUser({
184 | github_id: profile.id,
185 | name: profile.displayName.split(' ')[0],
186 | last_name: profile.displayName.split(' ').slice(1).join(' '),
187 | email: profile.emails[0].value
188 | }, (err, user, info) => {
189 | if (err) {
190 | return done(err);
191 | }
192 | if (user) {
193 | user.info = _getThirdPartyInfo(profile);
194 | }
195 | done(null, user, info);
196 | });
197 | }));
198 | }
199 |
200 | if (CONFIG.AUTH.LINKEDIN.ENABLED) {
201 | passport.use(new LinkedInStrategy({
202 | clientID: process.env.LINKEDIN_KEY,
203 | clientSecret: process.env.LINKEDIN_SECRET,
204 | callbackURL: '/auth/linkedin/callback',
205 | scope: CONFIG.AUTH.LINKEDIN.OPTIONS.scope
206 | }, (accessToken, refreshToken, profile, done) => {
207 | AuthService.resolveUser({
208 | linkedin_id: profile.id,
209 | name: profile.name.givenName,
210 | last_name: profile.name.familyName,
211 | email: profile.emails[0].value
212 | }, (err, user, info) => {
213 | if (err) {
214 | return done(err);
215 | }
216 | if (user) {
217 | user.info = _getThirdPartyInfo(profile);
218 | }
219 | done(null, user, info);
220 | });
221 | }));
222 | }
223 |
224 | if (CONFIG.AUTH.INSTAGRAM.ENABLED) {
225 | passport.use(new InstagramStrategy({
226 | clientID: process.env.INSTAGRAM_CLIENT_ID,
227 | clientSecret: process.env.INSTAGRAM_CLIENT_SECRET,
228 | callbackURL: '/auth/instagram/callback'
229 | }, (accessToken, refreshToken, profile, done) => {
230 | AuthService.resolveUser({
231 | instagram_id: profile.id,
232 | name: profile.displayName.split(' ')[0],
233 | last_name: profile.displayName.split(' ').slice(1).join(' '),
234 | email: `${profile.username}@instagram.com`
235 | }, (err, user, info) => {
236 | if (err) {
237 | return done(err);
238 | }
239 | if (user) {
240 | user.info = _getThirdPartyInfo(profile);
241 | }
242 | done(null, user, info);
243 | });
244 | }));
245 | }
--------------------------------------------------------------------------------
/config/routes.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const CONFIG = require('./config.json');
4 | const router = require('express').Router();
5 | const logger = require('winston');
6 |
7 | module.exports = () => {
8 |
9 | router.use((req, res, next) => {
10 | // Remove express http headers
11 | res.removeHeader('X-Powered-By');
12 | res.locals.user = req.user;
13 | next();
14 | });
15 |
16 | /*------------------- Routes -------------------*/
17 |
18 | const auth = require('../app/routes/auth');
19 | const register = require('../app/routes/register');
20 | const index = require('../app/routes/index');
21 |
22 | if (CONFIG.AUTH.ENABLED) {
23 | router.use(auth);
24 | router.use(register);
25 | }
26 |
27 | router.use(index);
28 |
29 | /*----------------- Routes API -----------------*/
30 |
31 | // const user = require('../app/routes/api/user');
32 |
33 | // router.use('/api/user', user);
34 |
35 | /*--------------- Error Handler ----------------*/
36 |
37 | router.use((req, res, next) => {
38 | let err = new Error('Not Found');
39 | err.statusCode = 404;
40 | next(err);
41 | });
42 |
43 | router.use((err, req, res, next) => {
44 | logger.error(err);
45 |
46 | const isApiRequest = req.originalUrl.substring(0, 4) === '/api';
47 |
48 | /**
49 | * Remove Error's `stack` property. We don't want
50 | * users to see this at the production env
51 | */
52 | if (req.app.get('env') !== 'development') {
53 | delete err.stack;
54 | }
55 |
56 | res.status(err.statusCode || 500);
57 |
58 | if (isApiRequest) {
59 | res.send({error: err});
60 | } else {
61 | res.render('error', {error: err});
62 | }
63 | });
64 |
65 | return router;
66 | };
67 |
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rodrigogs/nodejs-web-jade-scaffold/b2d87b790a02baaa82f7b8e7317ce854a97a72bd/favicon.ico
--------------------------------------------------------------------------------
/locales/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Node.js Web Jade Scaffold",
3 | "main.cover-title": "Node.js Web Jade Scaffold",
4 | "main.cover-description": "Web application featuring Node.js, Express, Jade, Passport, MongoDB and Bootstrap",
5 | "landing-page": "Landing Page",
6 | "login": "Login",
7 | "logout": "Logout",
8 | "sign-in": "Sign In",
9 | "facebook.sign-in": "Sign In with Facebook",
10 | "twitter.sign-in": "Sign In with Twitter",
11 | "google.sign-in": "Sign In with Google",
12 | "github.sign-in": "Sign In with GitHub",
13 | "linkedin.sign-in": "Sign In with Linkedin",
14 | "instagram.sign-in": "Sign In with Instagram",
15 | "register": "Register",
16 | "error": "Error",
17 | "auth.failed": "Invalid username or password",
18 | "user.name": "Name",
19 | "user.user_name": "Username",
20 | "user.last_name": "Last name",
21 | "user.email": "Email",
22 | "user.password": "Password",
23 | "user.created": "User created",
24 | "user.exists": "User exists",
25 | "user.invalidemail": "Invalid email",
26 | "user.error-creating": "Error creating user",
27 | "passport.badrequest": "Missing credentials",
28 | "mongoose.unique-error": "Field %s is unique, value %s is already taken"
29 | }
--------------------------------------------------------------------------------
/locales/pt-br.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Node.js Web Jade Scaffold",
3 | "main.cover-title": "Node.js Web Jade Scaffold",
4 | "main.cover-description": "Aplicação web implementando Node.js, Express, Jade, Passport, MongoDB e Bootstrap",
5 | "landing-page": "Página Inicial",
6 | "login": "Fazer Login",
7 | "logout": "Sair",
8 | "sign-in": "Entrar",
9 | "facebook.sign-in": "Entrar com o Facebook",
10 | "twitter.sign-in": "Entrar com o Twitter",
11 | "google.sign-in": "Entrar com o Google",
12 | "github.sign-in": "Entrar com o GitHub",
13 | "linkedin.sign-in": "Entrar com o Linkedin",
14 | "instagram.sign-in": "Entrar com o Instagram",
15 | "register": "Registrar",
16 | "error": "Erro",
17 | "auth.failed": "Nome de usuário ou senha inválido",
18 | "user.name": "Nome",
19 | "user.user_name": "Nome de usuário",
20 | "user.last_name": "Sobrenome",
21 | "user.email": "Email",
22 | "user.password": "Senha",
23 | "user.created": "Usuário criado",
24 | "user.exists": "Usuário já cadastrado",
25 | "user.invalidemail": "Email inválido",
26 | "user.error-creating": "Erro criando usuário",
27 | "passport.badrequest": "Credenciais não infomadas",
28 | "mongoose.unique-error": "O campo %s é único, o valor %s já existe"
29 | }
--------------------------------------------------------------------------------
/locales/pt.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Node.js Web Jade Scaffold",
3 | "main.cover-title": "Node.js Web Jade Scaffold",
4 | "main.cover-description": "Aplicação web implementando Node.js, Express, Jade, Passport, MongoDB e Bootstrap",
5 | "landing-page": "Página Inicial",
6 | "login": "Fazer Login",
7 | "logout": "Sair",
8 | "sign-in": "Entrar",
9 | "facebook.sign-in": "Entrar com o Facebook",
10 | "twitter.sign-in": "Entrar com o Twitter",
11 | "google.sign-in": "Entrar com o Google",
12 | "github.sign-in": "Entrar com o GitHub",
13 | "linkedin.sign-in": "Entrar com o Linkedin",
14 | "instagram.sign-in": "Entrar com o Instagram",
15 | "register": "Registrar",
16 | "error": "Erro",
17 | "auth.failed": "Nome de usuário ou senha inválido",
18 | "user.name": "Nome",
19 | "user.user_name": "Nome de usuário",
20 | "user.last_name": "Sobrenome",
21 | "user.email": "Email",
22 | "user.password": "Senha",
23 | "user.created": "Usuário criado",
24 | "user.exists": "Usuário já cadastrado",
25 | "user.invalidemail": "Email inválido",
26 | "user.error-creating": "Erro criando usuário",
27 | "passport.badrequest": "Credenciais não infomadas",
28 | "mongoose.unique-error": "O campo %s é único, o valor %s já existe"
29 | }
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const express = require('express');
5 | const helmet = require('helmet');
6 | const logger = require('morgan');
7 | const bodyParser = require('body-parser');
8 | const favicon = require('serve-favicon');
9 | const flash = require('flash');
10 | const session = require('express-session');
11 | const methodOverride = require('method-override');
12 | const passport = require('passport');
13 | const i18n = require('i18n');
14 | const cookieParser = require('cookie-parser');
15 | const robots = require('express-robots');
16 | const compression = require('compression')
17 |
18 | const CONFIG = require('./config/config.json');
19 | const routes = require('./config/routes')();
20 |
21 | const app = express();
22 |
23 | app.locals.AUTH_CONFIG = CONFIG.AUTH;
24 |
25 | app.use(helmet());
26 | app.use(logger(CONFIG.HTTP_LOG_CONFIG));
27 | app.use(cookieParser());
28 | app.use(bodyParser.json());
29 | app.use(bodyParser.urlencoded({extended: false}));
30 | app.use(session({secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: true}));
31 | app.use(flash());
32 | app.use(methodOverride('_method'));
33 | app.use(passport.initialize());
34 | app.use(passport.session());
35 |
36 | // i18n config: https://github.com/mashpie/i18n-node#list-of-configuration-options
37 | i18n.configure({
38 | directory: path.join(__dirname, 'locales'),
39 | locales: CONFIG.LOCALES,
40 | updateFiles: false
41 | });
42 |
43 | app.use(i18n.init);
44 |
45 | // Robots config: https://www.npmjs.com/package/express-robots
46 | app.use(robots({UserAgent: '*', Disallow: ''}));
47 |
48 | // Compression config: https://www.npmjs.com/package/compression
49 | app.use(compression());
50 |
51 | // Static resources
52 | app.use(favicon(path.join(__dirname, 'favicon.ico')));
53 | app.use('/static', express.static(path.join(__dirname, 'app/public')));
54 |
55 | // View engine setup
56 | app.set('views', path.join(__dirname, 'app/views'));
57 | app.set('view engine', 'jade');
58 |
59 | // Config
60 | app.use(routes);
61 | require('./config/mongoose');
62 | require('./config/passport');
63 |
64 | module.exports = app;
65 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nodejs-web-jade-scaffold",
3 | "version": "0.0.1",
4 | "description": "",
5 | "main": "main.js",
6 | "scripts": {
7 | "start": "node node_modules/nodemon/bin/nodemon.js ./bin/www",
8 | "test": "ava tests.js"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/rodrigogs/nodejs-web-jade-scaffold"
13 | },
14 | "keywords": [
15 | "web",
16 | "jade",
17 | "scaffold",
18 | "express",
19 | "kickstart",
20 | "simple"
21 | ],
22 | "author": {
23 | "name": "Rodrigo Gomes da Silva",
24 | "email": "rodrigo.smscom@gmail.com",
25 | "web": "https://github.com/rodrigogs"
26 | },
27 | "license": "BSD-2-Clause",
28 | "engines": {
29 | "node": ">=4.2.3"
30 | },
31 | "dependencies": {
32 | "body-parser": "^1.15.0",
33 | "compression": "^1.6.1",
34 | "cookie-parser": "^1.4.1",
35 | "express": "^4.13.4",
36 | "express-robots": "^0.1.5",
37 | "express-session": "^1.13.0",
38 | "filter-object": "^2.1.0",
39 | "flash": "^1.1.0",
40 | "helmet": "^2.1.1",
41 | "i18n": "^0.8.0",
42 | "jade": "^1.11.0",
43 | "method-override": "^2.3.5",
44 | "mongoose": "^4.4.7",
45 | "mongoose-unique-validator": "^1.0.2",
46 | "morgan": "^1.7.0",
47 | "nodemon": "^1.9.1",
48 | "passport": "^0.3.2",
49 | "passport-facebook": "^2.1.0",
50 | "passport-github": "^1.1.0",
51 | "passport-google-oauth": "^1.0.0",
52 | "passport-instagram": "^1.0.0",
53 | "passport-linkedin-oauth2": "^1.4.0",
54 | "passport-local": "^1.0.0",
55 | "passport-twitter": "^1.0.4",
56 | "serve-favicon": "^2.3.0",
57 | "validator": "^5.1.0",
58 | "winston": "^2.2.0"
59 | },
60 | "devDependencies": {
61 | "ava": "^0.16.0",
62 | "request": "^2.69.0"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/tests.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import test from 'ava';
4 | import http from 'http';
5 | import request from 'request';
6 | import app from './main';
7 |
8 | const baseUrl = 'http://127.0.0.1:3000';
9 | let server;
10 |
11 | test.before(() => {
12 | server = http.createServer(app).listen(3000, '127.0.0.1');
13 | });
14 |
15 | test.after(() => {
16 | server.close();
17 | });
18 |
19 | const getMethod = (t, url, params) => {
20 | t.plan(3);
21 |
22 | request.get({uri: url, qs: params}, (err, response, body) => {
23 | t.notOk(err, `Request error for url ${url}`);
24 | t.true(response && (response.statusCode >= 200 && response.statusCode < 399), `Url ${url} failed with status ${response ? response.statusCode : ''}`);
25 | t.ok(body, `Body is empty`);
26 | t.end();
27 | });
28 | };
29 |
30 | const postMethod = (t, url, params) => {
31 | t.plan(3);
32 |
33 | request.post(url, {form: params}, (err, response, body) => {
34 | t.notOk(err, `Request error for url ${url}`);
35 | t.true(response && (response.statusCode >= 200 && response.statusCode < 399), `Url ${url} failed with status ${response ? response.statusCode : ''}`);
36 | t.ok(body, `Body is empty`);
37 | t.end();
38 | });
39 | };
40 |
41 |
42 | test.cb('test / url', t => {
43 | const url = `${baseUrl}/`;
44 | getMethod(t, url);
45 | });
46 |
47 | test.cb('test /login url', t => {
48 | const url = `${baseUrl}/auth/login`;
49 | getMethod(t, url);
50 | });
51 |
52 | test.cb('test /logout url', t => {
53 | const url = `${baseUrl}/auth/logout`;
54 | getMethod(t, url);
55 | });
56 |
57 | test.cb('test /logout url', t => {
58 | const url = `${baseUrl}/register`;
59 | getMethod(t, url);
60 | });
61 |
62 | test.cb('test /logout url', t => {
63 | const url = `${baseUrl}/register`;
64 | let now = new Date();
65 | postMethod(t, url, {name: 'test', last_name: 'last name', user_name: 'test' + now.toString(), password: '123', email: now.getTime() + '@test.com'});
66 | });
67 |
--------------------------------------------------------------------------------