├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── gulpfile.babel.js ├── package.json ├── pm2.cluster.config.js ├── pm2.dev.config.js ├── pm2.simple.config.js ├── src ├── api │ ├── controllers │ │ ├── example.controller.js │ │ └── user.controller.js │ ├── models │ │ ├── example.model.js │ │ ├── hooks │ │ │ └── user.hook.js │ │ ├── methods │ │ │ └── user.method.js │ │ ├── seeds │ │ │ ├── example.seed.js │ │ │ └── user.seed.js │ │ ├── statics │ │ │ └── user.static.js │ │ └── user.model.js │ ├── routers │ │ ├── example.router.js │ │ └── user.router.js │ └── sockets │ │ └── example.socket.js ├── app.js ├── assets │ └── .gitkeep ├── auth │ ├── controllers │ │ ├── bitbucket.controller.js │ │ ├── facebook.controller.js │ │ ├── github.controller.js │ │ ├── google.controller.js │ │ ├── local.controller.js │ │ ├── redis.controller.js │ │ ├── session.controller.js │ │ └── twitter.controller.js │ ├── passports │ │ ├── bitbucket.passport.js │ │ ├── facebook.passport.js │ │ ├── github.passport.js │ │ ├── google.passport.js │ │ ├── local.passport.js │ │ └── twitter.passport.js │ ├── routers │ │ ├── bitbucket.router.js │ │ ├── facebook.router..js │ │ ├── github.router.js │ │ ├── google.router.js │ │ ├── local.router.js │ │ ├── redis.router.js │ │ ├── session.router.js │ │ └── twitter.router.js │ └── services │ │ ├── mw.service.js │ │ ├── router.service.js │ │ └── session.service.js ├── config │ ├── index.js │ └── production.js ├── lib │ ├── express │ │ ├── client.js │ │ └── index.js │ ├── mongoose │ │ ├── index.js │ │ └── seed.js │ ├── redis-jwt │ │ └── index.js │ └── socket.io │ │ └── index.js └── views │ ├── 404.html │ └── default │ ├── client.html │ ├── demo.js │ ├── development.html │ ├── favicon.ico │ ├── logo.svg │ ├── production.html │ ├── socket.html │ └── style.css └── test └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "es2016", "stage-0"], 3 | "plugins": ["transform-runtime", "transform-flow-comments"], 4 | "compact": "true", 5 | "comments": false, 6 | "ignore": [] 7 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src/views/default 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "rules": { 8 | // Erros 9 | "valid-typeof": 2, 10 | // Best Practices 11 | "no-useless-escape": 1, 12 | "eqeqeq": 1, 13 | "vars-on-top": 2, 14 | "default-case": 2, 15 | // Variables 16 | "init-declarations": 1, 17 | "no-delete-var": 1, 18 | "no-label-var": 1, 19 | "no-shadow-restricted-names": 1, 20 | "no-undef": 2, 21 | "no-undef-init": 2, 22 | "no-unused-vars": 0, 23 | // Node 24 | "callback-return": 2, 25 | "handle-callback-err": 0, 26 | "no-mixed-requires": 1, 27 | "no-restricted-modules": 2, 28 | // Stylistic Issues 29 | // ECMAScript 6 30 | "no-var": 1, 31 | "constructor-super": 1, 32 | "no-duplicate-imports": 1, 33 | "no-const-assign": 1, 34 | } 35 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | package-lock.json 4 | nbproject 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - node 5 | - 8 6 | - 7 7 | - 6 8 | services: 9 | - mongodb 10 | - redis-server 11 | script: 12 | - npm test 13 | fail_fast: true 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Leonardo Rico 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## ===> NEWS! 🔥🔥 NEW FASTER AND MORE SCALABLE VERSION HERE! 3 |
4 | 5 | 6 | # Nodetomic Api 7 | 8 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/f5084c4bad544b2586e3e973c8e3a336)](https://www.codacy.com/app/kevoj/nodetomic-api?utm_source=github.com&utm_medium=referral&utm_content=kevoj/nodetomic-api&utm_campaign=Badge_Grade) [![NPM version](https://badge.fury.io/js/nodetomic-api.svg)](https://npmjs.org/package/nodetomic-api) [![Build Status](https://travis-ci.org/kevoj/nodetomic-api.svg?branch=master)](https://travis-ci.org/kevoj/nodetomic-api) [![dependencies Status](https://david-dm.org/kevoj/nodetomic-api/status.svg)](https://david-dm.org/kevoj/nodetomic-api) [![devDependencies Status](https://david-dm.org/kevoj/nodetomic-api/dev-status.svg)](https://david-dm.org/kevoj/nodetomic-api?type=dev) [![Gitter chat](https://img.shields.io/gitter/room/kevoj/scaling-fullstack.svg)](https://gitter.im/scaling-fullstack/Lobby) [![GitHub license](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://raw.githubusercontent.com/kevoj/nodetomic-api/master/LICENSE) [![Downloads](https://img.shields.io/npm/dt/nodetomic-api.svg?style=flat-square)](https://npmjs.org/package/nodetomic-api) 9 | 10 | > RESTful API Nodejs designed for horizontal scalability with support for cluster, based on Express, MongoDB, Redis, JWT, Socket.io, Passport. 11 | 12 | If you want, the **swagger** version is also available: nodetomic-api-swagger 13 | 14 | 15 | 16 | ### Technologies 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ### Horizontal scalability 40 | 41 | View horizontal scaling representation image with nodetomic-api HERE! 42 | 43 | 44 | ### Structure 45 | 46 |
 
 47 | /src/
 48 | |-- api
 49 | |   |-- controllers
 50 | |   |-- models
 51 | |   |-- sockets
 52 | |   `-- routers
 53 | |-- assets
 54 | |-- auth
 55 | |   |-- controllers
 56 | |   |-- passports
 57 | |   |-- services
 58 | |   `-- routers
 59 | |-- config
 60 | |-- lib
 61 | |   |-- express
 62 | |   |-- mongoose
 63 | |   |-- redis-jwt
 64 | |   `-- socket.io
 65 | |-- views
 66 | `-- app.js
 67 | 
68 | 69 | ## Preview 70 | 71 | ##### Development 72 | ![Imgur](https://i.imgur.com/brGk8Qt.png) 73 | 74 | ##### Production 75 | ![Imgur](https://i.imgur.com/2KLfEUq.png) 76 | 77 | ## Requirements 78 | 79 | - [Nodejs](https://nodejs.org) >= **6.x.x** (Recommended **9.x.x**) 80 | - [MongoDB](https://www.mongodb.com) >= **3.x.x** 81 | - [Redis](https://redis.io) >= **3.x.x** (Recommended **4.x.x**) 82 | 83 | ## Installation 84 | 85 | **Npm** 86 | 87 | ```bash 88 | git clone https://github.com/kevoj/nodetomic-api 89 | cd nodetomic-api 90 | npm i 91 | ``` 92 | 93 | **Yarn** 94 | 95 | ```bash 96 | yarn add nodetomic-api --ignore-engines 97 | ``` 98 | 99 | ## Development 100 | 101 | ### Start 102 | 103 | **Command:** `npm start` 104 | 105 | **Description:** Start the project in development mode 106 | 107 | ![Imgur](https://i.imgur.com/dV6o7p9.png) 108 | 109 | **Note:** if you want work with **nodemon** execute the command `npm run modemon` 110 | 111 | ### Build 112 | 113 | **Command:** `npm run build` 114 | 115 | **Description:** Compile the project by outputting the dist folder 116 | 117 | ![Imgur](http://i.imgur.com/NoXdDO4.png) 118 | 119 | **Note:** Generate folder **`dist`**. So "dist/client" is optional. You can paste the compilation of a client here, for example of Vue, React, Angular... 120 | 121 | ![Imgur](https://i.imgur.com/bVFqr1f.png) 122 | 123 | ### Test 124 | 125 | **Command:** `npm test` 126 | 127 | **Description:** Run Lint and run Build in production mode and execute the authentication methods and basic requests. 128 | 129 | ![Imgur](http://i.imgur.com/ouKpQg1.png) 130 | 131 | ### Lint 132 | 133 | **Command:** `npm run lint` 134 | 135 | **Description:** Run ESLint to verify the entire project code 136 | 137 |
138 | 139 | ## Pm2 [Development] 140 | 141 | ### Dev-Simple 142 | 143 | **Command:** `npm run dev-simple` 144 | 145 | **Description:** Run Pm2 and compile the project in development mode in a single instance 146 | 147 | ![Imgur](http://i.imgur.com/cNuBVzK.png) 148 | 149 | ### Dev-Cluster 150 | 151 | **Command:** `npm run dev-cluster` 152 | 153 | **Description:** Run Pm2 and compile the project in development mode in multiple instances 154 | 155 | ![Imgur](http://i.imgur.com/wEU2Uz5.png) 156 | 157 | ## Pm2 [Production] 158 | 159 | ### Simple 160 | 161 | **Command:** `npm run simple` 162 | 163 | **Description:** Run Pm2 and compile the project in production mode in a single instance 164 | 165 | ![Imgur](http://i.imgur.com/tLA2hu7.png) 166 | 167 | ### Cluster 168 | 169 | **Command:** `npm run cluster` 170 | 171 | **Description:** Run Pm2 and compile the project in production mode in multiple instances 172 | 173 | ![Imgur](http://i.imgur.com/HTWJcUk.png) 174 | 175 | ## Stop 176 | 177 | ### Pm2 178 | 179 | **Command:** `npm stop` 180 | 181 | **Description:** Stops all processes associated with project pm2 182 | 183 | ### Node 184 | 185 | **Command:** `killall node` 186 | 187 | **Description:** Destroyed all process for node 188 | 189 | ## API Docs 190 | 191 | You can find the documentation HERE! 192 | 193 | ## License 194 | 195 | MIT © [Leonardo Rico](https://github.com/kevoj/nodetomic-api/blob/master/LICENSE) 196 | -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import chalk from 'chalk'; 3 | import gulp from 'gulp'; 4 | import babel from 'gulp-babel'; 5 | import rename from 'gulp-rename'; 6 | import clean from 'gulp-rimraf'; 7 | import minify from 'gulp-minifier'; 8 | import jeditor from "gulp-json-editor"; 9 | import runSequence from 'run-sequence'; 10 | import config from './src/config'; 11 | 12 | const dist = './dist'; 13 | const dist_server = `${dist}/server`; 14 | const dist_client = `${dist}/client`; 15 | const pm2_simple = `pm2.simple.config.js`; 16 | const pm2_cluster = `pm2.cluster.config.js`; 17 | 18 | gulp.task('build', () => { 19 | // Sequence 20 | runSequence('build-clean', 'build-babel', 'build-replace'); 21 | }); 22 | 23 | gulp.task('build-clean', () => { 24 | // Remove files dist, but ignore assets 25 | return gulp.src([ 26 | `${dist_server}/*`, `!${dist_server}/assets`, `${dist_client}` 27 | ], { read: false }).pipe(clean({ force: true })); 28 | }); 29 | 30 | gulp.task('build-babel', () => { 31 | // Babel transform 32 | return gulp.src(['src/**/*.js', '!src/config/*.js']).pipe(babel()).pipe(gulp.dest(dist_server)); 33 | }); 34 | 35 | gulp.task('build-replace', () => { 36 | // Copy file config dev or production 37 | const conf = process.argv[3] ? 'index' : 'production'; 38 | // Copy config production 39 | gulp.src([`src/config/${conf}.js`]).pipe(babel()).pipe(rename('index.js')).pipe(gulp.dest(`${dist_server}/config`)); 40 | // Copy views 41 | gulp.src(['src/views/**/*.*', '!src/views/**/*.js']).pipe(minify({ minify: true, collapseWhitespace: true, conservativeCollapse: true, minifyCSS: true })).pipe(gulp.dest(`${dist_server}/views`)); 42 | // Copy assets 43 | gulp.src(['src/assets/**/*']).pipe(gulp.dest(`${dist_server}/assets`)); 44 | // Copy *.yaml 45 | gulp.src(['src/**/*.yaml']).pipe(gulp.dest(dist_server)); 46 | // package.json 47 | gulp.src("package.json").pipe(jeditor((json) => { 48 | delete json.devDependencies; 49 | json.scripts = { 50 | "start": `node server/app.js`, 51 | "simple": `npm stop && pm2 start ${pm2_simple} --env production`, 52 | "cluster": `npm stop && pm2 start ${pm2_cluster} --env production`, 53 | "stop": `pm2 delete ${pm2_simple} ${pm2_cluster}` 54 | }; 55 | return json; 56 | })).pipe(gulp.dest(dist)); 57 | // Copy pm2 files 58 | gulp.src([pm2_simple, pm2_cluster]).pipe(gulp.dest(dist)); 59 | // If exits client folder, then copy current client 60 | if (fs.existsSync(`${dist}/client`)) { 61 | gulp.src(['client/**/*']).pipe(gulp.dest(dist_client)); 62 | } else { 63 | // If not exists client folder, then copy default client 64 | gulp.src(['src/views/default/favicon.ico', 'src/views/default/logo.svg']).pipe(gulp.dest(dist_client)); 65 | gulp.src(['src/views/default/client.html']).pipe(minify({ minify: true, collapseWhitespace: true, conservativeCollapse: true })).pipe(rename('index.html')).pipe(gulp.dest(dist_client)); 66 | } 67 | // Copy assets if not exists 68 | if (!fs.existsSync(`${dist_server}/assets`)) { 69 | gulp.src('src/assets').pipe(gulp.dest(`${dist_server}`)); 70 | } 71 | // Success 72 | setTimeout(() => console.log(chalk.greenBright('\n---------\nBuild success!\n---------\n')), 500); 73 | }); 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodetomic-api", 3 | "version": "2.0.1", 4 | "description": "RESTful API Nodejs designed for horizontal scalability with support for cluster, based on Express, MongoDB, Redis, JWT, Socket.io, Passport", 5 | "main": "src/app.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/kevoj/nodetomic-api.git" 9 | }, 10 | "scripts": { 11 | "start": "npm stop && pm2-dev pm2.dev.config.js", 12 | "nodemon": "export NODE_ENV=development & set NODE_PATH=development && npm stop && nodemon src/app.js --exec babel-node -e js,yaml", 13 | "build": "npm run lint && gulp build", 14 | "test": "npm run simple && node ./node_modules/npm-delay 4000 && mocha --require babel-core/register && npm stop", 15 | "stop": "pm2 delete pm2.simple.config.js pm2.cluster.config.js", 16 | "lint": "eslint src --ext .js", 17 | "dev-simple": "npm run build -- -dev && cd dist && npm stop && pm2-dev pm2.simple.config.js", 18 | "dev-cluster": "npm run build -- -dev && cd dist && npm stop && pm2-dev pm2.cluster.config.js", 19 | "simple": "npm run build && cd dist && npm run simple", 20 | "cluster": "npm run build && cd dist && npm run cluster", 21 | "update": "pm2 update" 22 | }, 23 | "author": "Leonardo Rico Guevara - https://github.com/kevoj", 24 | "license": "MIT", 25 | "keywords": [ 26 | "RESTful", 27 | "api-node", 28 | "api-rest", 29 | "rest-api", 30 | "api-auth", 31 | "node", 32 | "node-cluster", 33 | "node-socket", 34 | "node-api", 35 | "nodejs-api", 36 | "api-socket.io", 37 | "api-socket", 38 | "scaling", 39 | "socket.io", 40 | "cluster-socket", 41 | "nodejs", 42 | "es6", 43 | "es7", 44 | "api", 45 | "rest", 46 | "redis", 47 | "passport", 48 | "passport-node", 49 | "express", 50 | "mongodb", 51 | "cluster", 52 | "horizontal", 53 | "scalability" 54 | ], 55 | "dependencies": { 56 | "bcrypt": "^2.0.0", 57 | "body-parser": "^1.18.2", 58 | "chalk": "^2.3.2", 59 | "compression": "^1.7.2", 60 | "cookie-parser": "^1.4.3", 61 | "cors": "^2.8.4", 62 | "express": "^4.16.3", 63 | "express-easy-helper": "1.1.0", 64 | "express-session": "^1.15.6", 65 | "helmet": "^3.12.0", 66 | "method-override": "^2.3.10", 67 | "mongoose": "5.0.14", 68 | "mongoose-paginate": "^5.0.3", 69 | "passport": "^0.4.0", 70 | "passport-bitbucket": "^2.0.0", 71 | "passport-facebook": "^2.1.1", 72 | "passport-github": "^1.1.0", 73 | "passport-google-oauth": "^1.0.0", 74 | "passport-local": "^1.0.0", 75 | "passport-twitter": "^1.0.4", 76 | "pm2": "^2.10.2", 77 | "redis-jwt": "^1.4.0", 78 | "role-calc": "^1.1.6", 79 | "serve-favicon": "^2.5.0", 80 | "socket.io": "^2.1.0", 81 | "socket.io-redis": "^5.2.0" 82 | }, 83 | "devDependencies": { 84 | "babel-cli": "^6.26.0", 85 | "babel-core": "^6.26.0", 86 | "babel-eslint": "^8.2.2", 87 | "babel-plugin-transform-flow-comments": "^6.22.0", 88 | "babel-plugin-transform-runtime": "^6.23.0", 89 | "babel-preset-env": "^1.6.1", 90 | "babel-preset-es2015": "^6.24.1", 91 | "babel-preset-es2016": "^6.24.1", 92 | "babel-preset-stage-0": "^6.24.1", 93 | "babel-preset-stage-2": "^6.24.1", 94 | "babel-register": "^6.26.0", 95 | "concurrently": "^3.5.1", 96 | "eslint": "^4.19.1", 97 | "gulp": "^3.9.1", 98 | "gulp-babel": "^7.0.1", 99 | "gulp-json-editor": "^2.3.0", 100 | "gulp-minifier": "^1.2.2", 101 | "gulp-rename": "^1.2.2", 102 | "gulp-rimraf": "^0.2.2", 103 | "mocha": "^5.0.5", 104 | "morgan": "^1.9.0", 105 | "nodemon": "^1.17.3", 106 | "npm-delay": "^1.0.4", 107 | "request": "^2.85.0", 108 | "run-sequence": "^2.2.1" 109 | }, 110 | "engines": { 111 | "node": "^6.2.2", 112 | "npm": "^3.9.5" 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /pm2.cluster.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "apps": [ 3 | { 4 | "name": "app", 5 | "script": "./server/app.js", 6 | "instances": 'max', 7 | "exec_mode": "cluster", 8 | "watch": false, 9 | "env": { 10 | "NODE_ENV": "development" 11 | }, 12 | "env_production": { 13 | "NODE_ENV": "production" 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /pm2.dev.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "apps": [ 3 | { 4 | "name": "app-dev", 5 | "script": "./src/app.js", 6 | "watch": ["src/**/*.{js,yaml}"], 7 | "exec_interpreter": "babel-node", 8 | "env": { 9 | "NODE_ENV": "development" 10 | }, 11 | "args": [ 12 | "--color" 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /pm2.simple.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "apps": [ 3 | { 4 | "name": "app", 5 | "script": "./server/app.js", 6 | "watch": false, 7 | "env": { 8 | "NODE_ENV": "development" 9 | }, 10 | "env_production": { 11 | "NODE_ENV": "production" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/api/controllers/example.controller.js: -------------------------------------------------------------------------------- 1 | import { result, notFound, error } from 'express-easy-helper'; 2 | import { emit } from '../sockets/example.socket'; 3 | import Example from '../models/example.model'; 4 | 5 | // List Example's 6 | export function list(req, res) { 7 | 8 | return Example.find().exec() 9 | .then(notFound(res)) 10 | .then(result(res)) 11 | .catch(error(res)); 12 | } 13 | 14 | // Create a Example 15 | export function create(req, res) { 16 | 17 | return Example.create(req.body) 18 | .then(result(res, 201)) 19 | .catch(error(res)); 20 | 21 | } 22 | 23 | // read a Example 24 | export function read(req, res) { 25 | 26 | return Example.findById(req.params.id).exec() 27 | .then(notFound(res)) 28 | .then(result(res)) 29 | .catch(error(res)); 30 | 31 | } 32 | 33 | // Update a Example 34 | export function update(req, res) { 35 | 36 | return Example.findByIdAndUpdate( 37 | req.params.id, { 38 | $set: { 39 | greet: req.body.greet, 40 | language: req.body.language, 41 | } 42 | }, { 43 | new: true 44 | }).exec() 45 | .then(notFound(res)) 46 | .then(result(res)) 47 | .catch(error(res)) 48 | 49 | } 50 | 51 | // Destroy a Example 52 | export function destroy(req, res) { 53 | 54 | return Example.deleteOne({ 55 | _id: req.params.id 56 | }).exec() 57 | .then(result(res)) 58 | .catch(error(res)); 59 | 60 | } 61 | 62 | // Emit animation with socket! 63 | export function animation(req, res) { 64 | try { 65 | emit('animation', req.params.id); 66 | return result(res, 'Socket emitted!'); 67 | } catch (err) { 68 | return error(res, 'No client with event...'); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/api/controllers/user.controller.js: -------------------------------------------------------------------------------- 1 | import { result, notFound, error } from 'express-easy-helper'; 2 | import User from '../models/user.model'; 3 | 4 | // Create a user 5 | export function create(req, res) { 6 | 7 | return User.create({ 8 | username: req.body.username, 9 | name: req.body.name, 10 | lastname: req.body.lastname, 11 | email: req.body.email, 12 | password: req.body.password 13 | }) 14 | .then(result(res, 201)) 15 | .catch(error(res)); 16 | 17 | } 18 | 19 | // Read a user 20 | export function read(req, res) { 21 | 22 | return User.findOne({ username: req.params.username }, { 23 | social: 0 24 | }).select('-email') 25 | .exec() 26 | .then(notFound(res)) 27 | .then(result(res)) 28 | .catch(error(res)); 29 | 30 | } 31 | 32 | // Update user 33 | export function update(req, res) { 34 | 35 | return User.findByIdAndUpdate( 36 | req.user._id, { 37 | $set: { 38 | username: req.body.username, 39 | name: req.body.name, 40 | lastname: req.body.lastname, 41 | email: req.body.email, 42 | photo: req.body.photo 43 | } 44 | }, { 45 | new: true, 46 | // req:req 47 | }).exec() 48 | .then(notFound(res)) 49 | .then(result(res)) 50 | .catch(error(res)) 51 | 52 | } 53 | 54 | // Get current user 55 | export function me(req, res) { 56 | 57 | let user = req.user; 58 | delete user.session.id; 59 | return result(res, user); 60 | 61 | } 62 | 63 | /* 64 | * Administrator 65 | */ 66 | 67 | // List of user's 68 | export function listAdmin(req, res) { 69 | 70 | return User.find({}) 71 | .select('-social') 72 | .exec() 73 | .then(notFound(res)) 74 | .then(result(res)) 75 | .catch(error(res)); 76 | 77 | } 78 | 79 | // Update a user 80 | export function updateAdmin(req, res) { 81 | 82 | return User.findByIdAndUpdate( 83 | req.params.id, { 84 | $set: { 85 | username: req.body.username, 86 | name: req.body.name, 87 | lastname: req.body.lastname, 88 | email: req.body.email, 89 | photo: req.body.photo, 90 | provider: req.body.provider, 91 | roles: req.body.roles, 92 | status: req.body.status, 93 | } 94 | }, { 95 | new: true 96 | }).exec() 97 | .then(notFound(res)) 98 | .then(result(res)) 99 | .catch(error(res)) 100 | 101 | } 102 | 103 | // Destroy a user 104 | export function destroyAdmin(req, res, next) { 105 | 106 | return User.findByIdAndRemove( 107 | req.params.id 108 | ).exec() 109 | .then(notFound(res)) 110 | .then(result(res)) 111 | .catch(error(res)) 112 | 113 | } -------------------------------------------------------------------------------- /src/api/models/example.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import mongoosePaginate from 'mongoose-paginate'; 3 | const Schema = mongoose.Schema; 4 | 5 | const ExampleSchema = new Schema({ 6 | greet: { 7 | type: String, 8 | required: [true, 'Greet is required.'] 9 | }, 10 | language: { 11 | type: String, 12 | required: [true, 'Language is required.'] 13 | } 14 | }); 15 | 16 | ExampleSchema.plugin(mongoosePaginate); 17 | 18 | export default mongoose.model('Example', ExampleSchema); -------------------------------------------------------------------------------- /src/api/models/hooks/user.hook.js: -------------------------------------------------------------------------------- 1 | import bcrypt from 'bcrypt'; 2 | 3 | export default (User) => { 4 | 5 | // Trigger method's before save 6 | User.pre('save', async function (next) { 7 | 8 | let user = this; 9 | 10 | // if username from social network exists then new username! 11 | if (user.provider !== 'local' && (user.isNew || user.isModified('username'))) { 12 | 13 | let username = await new Promise((resolve, reject) => { 14 | (function calc(username) { 15 | user.constructor.findOneByUsername(username) 16 | .then(exists => exists ? calc(`${username}1`) : resolve(username)) 17 | .catch(err => reject(err)); 18 | })(user.username); 19 | }); 20 | 21 | user.username = username; // set new username 22 | } 23 | 24 | // only hash the password if it has been modified (or is new) 25 | if (!user.isModified('password')) 26 | return next(); 27 | 28 | // generate a salt 29 | bcrypt.genSalt(10, (err, salt) => { 30 | if (err) 31 | return next(err); 32 | 33 | // hash the password using our new salt 34 | bcrypt.hash(user.password, salt, (err, hash) => { 35 | if (err) 36 | return next(err); 37 | 38 | // override the cleartext password with the hashed one 39 | user.password = hash; 40 | next(); 41 | }); 42 | }); 43 | 44 | }); 45 | 46 | // Trigger method's after save 47 | User.post('save', function (err, doc, next) { 48 | 49 | if (err.name === 'MongoError' && err.code === 11000) { 50 | return next(`'username "${doc.username}" not available.'`); 51 | } else { 52 | return next(err); 53 | } 54 | }); 55 | 56 | }; -------------------------------------------------------------------------------- /src/api/models/methods/user.method.js: -------------------------------------------------------------------------------- 1 | import bcrypt from 'bcrypt'; 2 | 3 | export default (User) => { 4 | 5 | // Methods 6 | User.methods = { 7 | // Compare password 8 | authenticate(candidatePassword) { 9 | return bcrypt.compare(candidatePassword, this.password); 10 | } 11 | 12 | }; 13 | 14 | }; -------------------------------------------------------------------------------- /src/api/models/seeds/example.seed.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | greet: 'Hello World', 4 | language: 'English' 5 | }, { 6 | greet: 'Hola Mundo', 7 | language: 'Spanish' 8 | }, { 9 | greet: 'salut monde', 10 | language: 'French' 11 | }, { 12 | greet: 'Hallo Welt', 13 | language: 'Germany' 14 | }, { 15 | greet: 'こんにちは', 16 | language: 'Japanese' 17 | }, { 18 | greet: '你好世界', 19 | language: 'Chinese' 20 | } 21 | ]; -------------------------------------------------------------------------------- /src/api/models/seeds/user.seed.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | username: 'admin', 4 | password: '$2a$10$f.iNtPrK9Y69p1KRWcQai.UIBdFoOBpeEGMRA2odDsKptgSi4a4xe', // 123 5 | email: 'floyd@admin.com', 6 | name: 'Pink', 7 | lastname: 'Floyd', 8 | roles: ['admin'] 9 | }, { 10 | username: 'user', 11 | password: '$2a$10$wQVXH2NGe8V.LRv5s7QopubLnQAo7jJpx52RdBnckbK1QnHxNi/YK', // 123 12 | email: 'peppers@user.com', 13 | name: 'Chilli', 14 | lastname: 'Peppers', 15 | roles: ['user'] 16 | } 17 | ]; -------------------------------------------------------------------------------- /src/api/models/statics/user.static.js: -------------------------------------------------------------------------------- 1 | 2 | export default (User) => { 3 | 4 | // Statics 5 | User.statics = { 6 | 7 | loginByLocal(username, password) { 8 | 9 | return new Promise((resolve, reject) => { 10 | 11 | const User = this; 12 | 13 | User.findOne({ 14 | username: username, 15 | provider: 'local' 16 | }).select("+password").exec().then(user => { 17 | 18 | if (!user) 19 | reject(`${username}' is not registered.`); // You can register user here 20 | 21 | user.authenticate(password).then(isMatch => { // validate password 22 | if (!isMatch) 23 | reject(`This password is not correct.`); 24 | 25 | user.lastLogin = Date.now(); 26 | 27 | user.save().then(_user => resolve(_user)).catch(err => reject(err)); 28 | 29 | }); 30 | 31 | }).catch(err => reject(err)); 32 | 33 | }); 34 | 35 | }, 36 | 37 | loginBySocial(provider, profile) { 38 | 39 | return new Promise((resolve, reject) => { 40 | 41 | const User = this; 42 | 43 | User.findOne({ 44 | provider, 45 | 'social.id': profile.id 46 | }).exec().then(user => { 47 | 48 | if (!user) { 49 | user = new User({ 50 | provider: provider, 51 | name: profile.displayName, 52 | username: profile.username, 53 | email: profile.email || '', 54 | photo: profile.photo || '', 55 | 'social.id': profile.id, 56 | 'social.info': profile._json 57 | }); 58 | } else { 59 | user.social.info = profile._json; 60 | user.photo = profile.photo || ''; 61 | } 62 | 63 | user.lastLogin = Date.now(); 64 | 65 | user.save().then(_user => resolve(_user)).catch(err => reject(err)); 66 | 67 | }).catch(err => reject(err)); 68 | 69 | }); 70 | 71 | }, 72 | 73 | findOneByUsername(username) { 74 | 75 | return new Promise((resolve, reject) => { 76 | 77 | this.findOne({ username }).count().exec().then(found => { 78 | resolve(found); 79 | }); 80 | 81 | }); 82 | 83 | } 84 | 85 | } 86 | 87 | }; -------------------------------------------------------------------------------- /src/api/models/user.model.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import mongoosePaginate from 'mongoose-paginate'; 3 | const Schema = mongoose.Schema; 4 | 5 | const UserSchema = new Schema({ 6 | username: { 7 | type: String, 8 | required: [ 9 | true, 'Username is required.' 10 | ], 11 | unique: true 12 | }, 13 | password: { 14 | type: String, 15 | select: false 16 | }, 17 | name: { 18 | type: String 19 | }, 20 | lastname: String, 21 | email: { 22 | type: String, 23 | lowercase: true 24 | }, 25 | photo: String, 26 | provider: { 27 | type: String, 28 | required: [ 29 | true, 'Provider is required.' 30 | ], 31 | default: 'local' 32 | }, 33 | roles: { 34 | type: Array, 35 | default: ['user'] 36 | }, 37 | status: { 38 | type: Number, 39 | default: 1, 40 | required: [true, 'Status is required.'] 41 | }, 42 | date: { 43 | type: Date, 44 | default: Date.now 45 | }, 46 | lastLogin: { 47 | type: Date 48 | }, 49 | social: { 50 | id: String, 51 | info: {} 52 | } 53 | }); 54 | 55 | UserSchema.path('username').index({ unique: true }); 56 | 57 | UserSchema.plugin(mongoosePaginate); 58 | 59 | require('./hooks/user.hook').default(UserSchema); 60 | require('./statics/user.static').default(UserSchema); 61 | require('./methods/user.method').default(UserSchema); 62 | 63 | export default mongoose.model('User', UserSchema); -------------------------------------------------------------------------------- /src/api/routers/example.router.js: -------------------------------------------------------------------------------- 1 | import * as controller from './../controllers/example.controller'; 2 | 3 | export default (app) => { 4 | app.get('/api/example/greeting', controller.list); 5 | app.post('/api/example/greeting', controller.create); 6 | app.put('/api/example/greeting/:id', controller.update); 7 | app.get('/api/example/greeting/:id', controller.read); 8 | app.delete('/api/example/greeting/:id', controller.destroy); 9 | app.get('/api/example/socket', controller.animation); 10 | } -------------------------------------------------------------------------------- /src/api/routers/user.router.js: -------------------------------------------------------------------------------- 1 | import * as controller from './../controllers/user.controller'; 2 | import { mw } from './../../auth/services/mw.service'; 3 | 4 | export default (app) => { 5 | app.post('/api/user', controller.create); 6 | app.put('/api/user', mw(), controller.update); 7 | app.get('/api/user/me', mw(), controller.me); 8 | app.get('/api/user/public/:username', controller.read); 9 | 10 | app.get('/api/user/admin', mw(['admin']), controller.listAdmin); 11 | app.put('/api/user/admin/:id', mw(['admin']), controller.updateAdmin); 12 | app.delete('/api/user/admin/:id', mw(['admin']), controller.destroyAdmin); 13 | } -------------------------------------------------------------------------------- /src/api/sockets/example.socket.js: -------------------------------------------------------------------------------- 1 | export let socket = null; 2 | export let io = null; 3 | 4 | // Constructor 5 | export default (_socket, _io) => { 6 | socket = _socket; 7 | io = _io; 8 | on(); 9 | } 10 | 11 | // Here should be all events 'on' 12 | export function on() { 13 | socket.on('example:add', data => emit('add', data)); 14 | socket.on('example:delete', data => emit('delete', data)); 15 | } 16 | 17 | // Emit events 18 | export function emit(event, data) { 19 | io.emit(event, data); 20 | } -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import chalk from 'chalk'; 3 | import config from './config'; 4 | const app = express(); 5 | 6 | (async function run() { 7 | 8 | // Express 9 | await require('./lib/express').index(app); 10 | 11 | // Mongoose 12 | await require('./lib/mongoose').connect(); 13 | 14 | // Redis-jwt 15 | await require('./lib/redis-jwt').connect(); 16 | 17 | // Socket.io 18 | await require('./lib/socket.io').connect(); 19 | 20 | // Passports 21 | await require('./auth/services/router.service').default(app); 22 | 23 | // Paths 24 | await require('./lib/express/client').default(app); 25 | 26 | // Server 27 | app.listen(config.server.port, config.server.ip, () => { 28 | // Info 29 | console.log(chalk.greenBright(`-------\nServer-> 30 | mode: [${chalk.magentaBright(`${config.mode}`)}] 31 | url: ${chalk.blueBright(`http://${config.server.ip}:${config.server.port}`)}\n-------`)); 32 | // Ready! 33 | console.log(chalk.black.bgGreenBright(`>>nodetomic-api ready!<<`)); 34 | }); 35 | 36 | })(); -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/auth/controllers/bitbucket.controller.js: -------------------------------------------------------------------------------- 1 | import passport from 'passport'; 2 | import { initialize } from '../services/session.service'; 3 | 4 | // Init passport 5 | export function index(req, res, next) { 6 | passport.authenticate('bitbucket')(req, res, next); 7 | } 8 | 9 | // Callback passport 10 | export function callback(req, res, next) { 11 | passport.authenticate('bitbucket', (err, user) => initialize(err, user, res))(req, res, next); 12 | } -------------------------------------------------------------------------------- /src/auth/controllers/facebook.controller.js: -------------------------------------------------------------------------------- 1 | import passport from 'passport'; 2 | import { initialize } from '../services/session.service'; 3 | 4 | // Init passport 5 | export function index(req, res, next) { 6 | passport.authenticate('facebook')(req, res, next); 7 | } 8 | 9 | // Callback passport 10 | export function callback(req, res, next) { 11 | passport.authenticate('facebook', (err, user) => initialize(err, user, res))(req, res, next); 12 | } -------------------------------------------------------------------------------- /src/auth/controllers/github.controller.js: -------------------------------------------------------------------------------- 1 | import passport from 'passport'; 2 | import { initialize } from '../services/session.service'; 3 | 4 | // Init passport 5 | export function index(req, res, next) { 6 | passport.authenticate('github')(req, res, next); 7 | } 8 | 9 | // Callback passport 10 | export function callback(req, res, next) { 11 | passport.authenticate('github', (err, user) => initialize(err, user, res))(req, res, next); 12 | } -------------------------------------------------------------------------------- /src/auth/controllers/google.controller.js: -------------------------------------------------------------------------------- 1 | import passport from 'passport'; 2 | import { initialize } from '../services/session.service'; 3 | 4 | // Init passport 5 | export function index(req, res, next) { 6 | passport.authenticate('google')(req, res, next); 7 | } 8 | 9 | // Callback passport 10 | export function callback(req, res, next) { 11 | passport.authenticate('google', (err, user) => initialize(err, user, res))(req, res, next); 12 | } -------------------------------------------------------------------------------- /src/auth/controllers/local.controller.js: -------------------------------------------------------------------------------- 1 | import passport from 'passport'; 2 | import { initialize } from '../services/session.service'; 3 | 4 | // Callback passport 5 | export function callback(req, res, next) { 6 | passport.authenticate('local', (err, user) => initialize(err, user, res))(req, res, next); 7 | } -------------------------------------------------------------------------------- /src/auth/controllers/redis.controller.js: -------------------------------------------------------------------------------- 1 | import { result, error } from 'express-easy-helper'; 2 | import { call } from '../../lib/redis-jwt'; 3 | 4 | /* Administrator */ 5 | 6 | // Get section 7 | export function section(req, res) { 8 | 9 | return call.getInfo(req.params.section).then(info => { 10 | res.send(info.toString()); 11 | }).catch(error(res)) 12 | 13 | } -------------------------------------------------------------------------------- /src/auth/controllers/session.controller.js: -------------------------------------------------------------------------------- 1 | import { result, notFound, error } from 'express-easy-helper'; 2 | import { call } from '../../lib/redis-jwt'; 3 | 4 | // List of sessions by user 5 | export function list(req, res) { 6 | 7 | return call.getValuesByPattern(req.user._id) 8 | .then(notFound(res)) 9 | .then(all => { 10 | for (let prop in all) 11 | all[prop] = JSON.parse(all[prop]); 12 | return result(res, all); 13 | }) 14 | .catch(error(res)) 15 | 16 | } 17 | 18 | // Destroy a session 19 | export function destroy(req, res) { 20 | 21 | return call.destroy(`${req.user._id}:${req.params.id.split(":")[1]}`) 22 | .then(notFound(res)) 23 | .then(result(res)) 24 | .catch(error(res)) 25 | 26 | } 27 | 28 | // Destroy a current session 29 | export function logout(req, res) { 30 | 31 | console.log(req.user.session.rjwt) 32 | return call.destroy(req.user.session.rjwt) 33 | .then(notFound(res)) 34 | .then(result(res)) 35 | .catch(error(res)) 36 | 37 | } 38 | 39 | /* Administrator */ 40 | 41 | // List of sessions by id 42 | export function listAdmin(req, res) { 43 | 44 | return call.getValuesByPattern(req.params.id) 45 | .then(notFound(res)) 46 | .then(all => { 47 | for (let prop in all) 48 | all[prop] = JSON.parse(all[prop]); 49 | return result(res, all); 50 | }) 51 | .catch(error(res)) 52 | 53 | } 54 | 55 | // Destroy a session by rjwt 56 | export function destroyAdmin(req, res) { 57 | 58 | return call.destroy(`${req.params.id}`) 59 | .then(notFound(res)) 60 | .then(result(res)) 61 | .catch(error(res)) 62 | 63 | } 64 | 65 | // Logout a user by id 66 | export function logoutAdmin(req, res) { 67 | 68 | return call.destroyMultiple(req.params.id) 69 | .then(notFound(res)) 70 | .then(result(res)) 71 | .catch(error(res)) 72 | 73 | } -------------------------------------------------------------------------------- /src/auth/controllers/twitter.controller.js: -------------------------------------------------------------------------------- 1 | import passport from 'passport'; 2 | import { initialize } from '../services/session.service'; 3 | 4 | // Init passport 5 | export function index(req, res, next) { 6 | passport.authenticate('twitter')(req, res, next); 7 | } 8 | 9 | // Callback passport 10 | export function callback(req, res, next) { 11 | passport.authenticate('twitter', (err, user) => initialize(err, user, res))(req, res, next); 12 | } -------------------------------------------------------------------------------- /src/auth/passports/bitbucket.passport.js: -------------------------------------------------------------------------------- 1 | import passport from 'passport'; 2 | import { Strategy as BitbucketStrategy } from 'passport-bitbucket'; 3 | import User from '../../api/models/user.model'; 4 | import config from '../../config'; 5 | 6 | passport.use(new BitbucketStrategy({ 7 | consumerKey: config.oAuth.bitbucket.clientID, 8 | consumerSecret: config.oAuth.bitbucket.clientSecret, 9 | callbackURL: config.oAuth.bitbucket.callbackURL 10 | }, (token, tokenSecret, profile, done) => { 11 | 12 | let social = profile; 13 | social.email = profile._json.email; 14 | social.photo = profile._json.links.avatar.href; 15 | 16 | User.loginBySocial('bitbucket', social) 17 | .then(user => done(null, user)) 18 | .catch(err => done(err)); 19 | 20 | })); -------------------------------------------------------------------------------- /src/auth/passports/facebook.passport.js: -------------------------------------------------------------------------------- 1 | import passport from 'passport'; 2 | import { Strategy as FacebookStrategy } from 'passport-facebook'; 3 | import User from '../../api/models/user.model'; 4 | import config from '../../config'; 5 | 6 | passport.use(new FacebookStrategy({ 7 | clientID: config.oAuth.facebook.clientID, 8 | clientSecret: config.oAuth.facebook.clientSecret, 9 | callbackURL: config.oAuth.facebook.callbackURL 10 | }, (accessToken, refreshToken, profile, done) => { 11 | 12 | let social = profile; 13 | social.email = profile.emails[0].value; 14 | social.photo = `http://graph.facebook.com/${profile.id}/picture?type=square`; 15 | 16 | User.loginBySocial('facebook', social) 17 | .then(user => done(null, user)) 18 | .catch(err => done(err)); 19 | 20 | })); -------------------------------------------------------------------------------- /src/auth/passports/github.passport.js: -------------------------------------------------------------------------------- 1 | import passport from 'passport'; 2 | import { Strategy as GitHubStrategy } from 'passport-github'; 3 | import User from '../../api/models/user.model'; 4 | import config from '../../config'; 5 | 6 | passport.use(new GitHubStrategy({ 7 | clientID: config.oAuth.github.clientID, 8 | clientSecret: config.oAuth.github.clientSecret, 9 | callbackURL: config.oAuth.github.callbackURL 10 | }, (accessToken, refreshToken, profile, done) => { 11 | 12 | let social = profile; 13 | social.email = profile._json.email; 14 | social.photo = profile._json.avatar_url; 15 | 16 | User.loginBySocial('github', social) 17 | .then(user => done(null, user)) 18 | .catch(err => done(err)); 19 | 20 | })); -------------------------------------------------------------------------------- /src/auth/passports/google.passport.js: -------------------------------------------------------------------------------- 1 | import passport from 'passport'; 2 | import { OAuth2Strategy as GoogleStrategy } from 'passport-google-oauth'; 3 | import User from '../../api/models/user.model'; 4 | import config from '../../config'; 5 | 6 | passport.use(new GoogleStrategy({ 7 | clientID: config.oAuth.google.clientID, 8 | clientSecret: config.oAuth.google.clientSecret, 9 | callbackURL: config.oAuth.google.callbackURL 10 | }, (accessToken, refreshToken, profile, done) => { 11 | 12 | let social = profile; 13 | social.photo = profile._json.image.url; 14 | 15 | User.loginBySocial('google', social) 16 | .then(user => done(null, user)) 17 | .catch(err => done(err)); 18 | 19 | })); -------------------------------------------------------------------------------- /src/auth/passports/local.passport.js: -------------------------------------------------------------------------------- 1 | import passport from 'passport'; 2 | import { Strategy as LocalStrategy } from 'passport-local'; 3 | import User from '../../api/models/user.model'; 4 | 5 | passport.use('local', new LocalStrategy({ 6 | usernameField: 'username', 7 | passwordField: 'password', 8 | //passReqToCallback: true 9 | }, function (username, password, done) { 10 | 11 | User.loginByLocal(username, password) 12 | .then(user => done(null, user)) 13 | .catch(err => done(err)); 14 | 15 | })); -------------------------------------------------------------------------------- /src/auth/passports/twitter.passport.js: -------------------------------------------------------------------------------- 1 | import passport from 'passport'; 2 | import { Strategy as TwitterStrategy } from 'passport-twitter'; 3 | import User from '../../api/models/user.model'; 4 | import config from '../../config'; 5 | 6 | passport.use(new TwitterStrategy({ 7 | consumerKey: config.oAuth.twitter.clientID, 8 | consumerSecret: config.oAuth.twitter.clientSecret, 9 | callbackURL: config.oAuth.twitter.callbackURL 10 | }, (token, tokenSecret, profile, done) => { 11 | 12 | let social = profile; 13 | social.email = profile.emails[0].value; 14 | social.photo = profile._json.profile_image_url; 15 | 16 | User.loginBySocial('twitter', social) 17 | .then(user => done(null, user)) 18 | .catch(err => done(err)); 19 | 20 | })); -------------------------------------------------------------------------------- /src/auth/routers/bitbucket.router.js: -------------------------------------------------------------------------------- 1 | import * as controller from './../controllers/bitbucket.controller'; 2 | 3 | export default (app) => { 4 | app.get('/auth/bitbucket', controller.index); 5 | app.get('/auth/bitbucket/callback', controller.callback); 6 | } -------------------------------------------------------------------------------- /src/auth/routers/facebook.router..js: -------------------------------------------------------------------------------- 1 | import * as controller from './../controllers/facebook.controller'; 2 | 3 | export default (app) => { 4 | app.get('/auth/facebook', controller.index); 5 | app.get('/auth/facebook/callback', controller.callback); 6 | } 7 | -------------------------------------------------------------------------------- /src/auth/routers/github.router.js: -------------------------------------------------------------------------------- 1 | import * as controller from './../controllers/github.controller'; 2 | 3 | export default (app) => { 4 | app.get('/auth/github', controller.index); 5 | app.get('/auth/github/callback', controller.callback); 6 | } 7 | -------------------------------------------------------------------------------- /src/auth/routers/google.router.js: -------------------------------------------------------------------------------- 1 | import * as controller from './../controllers/google.controller'; 2 | 3 | export default (app) => { 4 | app.get('/auth/google', controller.index); 5 | app.get('/auth/google/callback', controller.callback); 6 | } 7 | -------------------------------------------------------------------------------- /src/auth/routers/local.router.js: -------------------------------------------------------------------------------- 1 | import * as controller from './../controllers/local.controller'; 2 | 3 | export default (app) => { 4 | app.post('/auth/local', controller.callback); 5 | } 6 | -------------------------------------------------------------------------------- /src/auth/routers/redis.router.js: -------------------------------------------------------------------------------- 1 | 2 | import * as controller from './../controllers/redis.controller'; 3 | import { mw } from './../services/mw.service'; 4 | 5 | export default (app) => { 6 | app.get('/auth/redis/:section', mw(['admin']), controller.section); 7 | } 8 | -------------------------------------------------------------------------------- /src/auth/routers/session.router.js: -------------------------------------------------------------------------------- 1 | 2 | import * as controller from './../controllers/session.controller'; 3 | import { mw } from './../services/mw.service'; 4 | 5 | export default (app) => { 6 | app.get('/auth/session', mw(), controller.list); 7 | app.delete('/auth/logout', mw(), controller.logout); 8 | app.delete('/auth/session/:id', mw(), controller.destroy); 9 | 10 | app.get('/auth/session/admin/:id', mw(['admin']), controller.listAdmin); 11 | app.delete('/auth/session/admin/:id', mw(['admin']), controller.destroyAdmin); 12 | app.delete('/auth/session/admin/logout/:id', mw(['admin']), controller.logoutAdmin); 13 | } 14 | -------------------------------------------------------------------------------- /src/auth/routers/twitter.router.js: -------------------------------------------------------------------------------- 1 | import * as controller from './../controllers/twitter.controller'; 2 | 3 | export default (app) => { 4 | app.get('/auth/twitter', controller.index); 5 | app.get('/auth/twitter/callback', controller.callback); 6 | } 7 | -------------------------------------------------------------------------------- /src/auth/services/mw.service.js: -------------------------------------------------------------------------------- 1 | import { unauthorized, forbidden } from 'express-easy-helper'; 2 | import { has } from 'role-calc'; 3 | import { r } from '../../lib/redis-jwt'; 4 | import User from '../../api/models/user.model'; 5 | 6 | // VerifyToken 7 | export function mw(requiredRoles) { 8 | 9 | return async (req, res, next) => { 10 | 11 | // Extract Token 12 | let token = req.headers["authorization"]; 13 | 14 | if (token) { 15 | // Bearer 16 | req.headers.authorization = `Bearer ${token}`; 17 | 18 | // Verify Token with redis-jwt -> if you want to extract the data you should add true: r.verify(token, true); 19 | let session = await r.verify(token, true); 20 | if (!session) 21 | return next(forbidden(req.res)); 22 | 23 | // Extract info user from MongoDB 24 | let _user = await User.findById(session.id).select('-social').exec(); 25 | if (!_user) 26 | return next(unauthorized(req.res)); 27 | 28 | // If id's not equals 29 | if (_user._id.toString() !== session.id.toString()) 30 | return next(forbidden(req.res)); 31 | 32 | // User is enabled? 33 | if (!_user.status) 34 | return next(unauthorized(req.res)); 35 | 36 | // Verify Roles 37 | if (requiredRoles) 38 | if (!has(requiredRoles, _user.roles)) 39 | return next(forbidden(req.res)); 40 | 41 | // Success 42 | req.user = Object.assign({ session }, _user._doc); 43 | return next(); 44 | } else { 45 | return next(forbidden(req.res)); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/auth/services/router.service.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import config from '../../config'; 3 | 4 | // Passport's 5 | if ("local" in config.oAuth && config.oAuth.local.enabled) 6 | require('../passports/local.passport'); 7 | 8 | if ("github" in config.oAuth && config.oAuth.github.enabled) 9 | require('../passports/github.passport'); 10 | 11 | if ("twitter" in config.oAuth && config.oAuth.twitter.enabled) 12 | require('../passports/twitter.passport'); 13 | 14 | if ("facebook" in config.oAuth && config.oAuth.facebook.enabled) 15 | require('../passports/facebook.passport'); 16 | 17 | if ("google" in config.oAuth && config.oAuth.google.enabled) 18 | require('../passports/google.passport'); 19 | 20 | if ("bitbucket" in config.oAuth && config.oAuth.bitbucket.enabled) 21 | require('../passports/bitbucket.passport'); 22 | 23 | export default (app) => { 24 | // Controllers Api 25 | fs.readdirSync(`${config.base}/api/routers`).forEach(route => { 26 | require(`${config.base}/api/routers/${route}`).default(app); 27 | }); 28 | // Controllers Auth 29 | fs.readdirSync(`${config.base}/auth/routers`).forEach(route => { 30 | require(`${config.base}/auth/routers/${route}`).default(app); 31 | }); 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/auth/services/session.service.js: -------------------------------------------------------------------------------- 1 | import { result, invalid, error } from 'express-easy-helper'; 2 | import { calc, time } from 'role-calc'; 3 | import { r } from '../../lib/redis-jwt'; 4 | import config from '../../config'; 5 | 6 | // Initialize after login success 7 | export async function initialize(err, user, res) { 8 | 9 | try { 10 | // Errors 11 | if (err) 12 | return invalid(res, { message: err }); 13 | if (!user) 14 | return error(res, { message: 'Something went wrong, please try again.' }); 15 | 16 | // Calculate ttl by user roles, by default takes the role with the longest 'max' 17 | let ttl = calc(time(config.roles, user.roles), 'max'); 18 | 19 | // Create session in redis-jwt 20 | const token = await r.sign(user._id.toString(), { 21 | ttl, 22 | dataSession: {// save data in REDIS (Private) 23 | ip: res.req.headers['x-forwarded-for'] || res.req.connection.remoteAddress, 24 | agent: res.req.headers['user-agent'] 25 | }, 26 | dataToken: {// save data in Token (Public) 27 | example: 'I travel with the token!' 28 | } 29 | }); 30 | 31 | // Save token in cookies 32 | res.cookie('token', JSON.stringify(token)); 33 | 34 | // if local return token 35 | if (user.provider === 'local') 36 | return result(res, { token }); 37 | 38 | // if Social redirect to.. 39 | res.redirect(`/?token=${token}`); 40 | 41 | } catch (err) { 42 | return error(res, { message: err }); 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/config/index.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | const APP_NAME = `your-app-name`; 4 | const DB_NAME = `your-app-name-dev`; 5 | const CLIENT = '/client'; 6 | 7 | export default { 8 | secret: `your_secret_key`, // Secret Key 9 | server: { // Express 10 | ip: 'localhost', 11 | port: 8000, 12 | }, 13 | log: true, // show logs 14 | // Roles: if a user has multiple roles, will take the time of the greater role 15 | roles: [ 16 | { 17 | role: 'user', 18 | ttl: '7200 minutes', 19 | }, { 20 | role: 'admin', 21 | ttl: '5 days' 22 | } 23 | ], 24 | path: { 25 | disabled: '/:url(api|assets|auth|config|lib|views)/*' // paths 404 26 | }, 27 | "socket.io": { // Socket.io 28 | port: 8001, // public port listen, change also in views/default/demo.js 29 | example: true, // router -> http://localhost:8000/socket 30 | redis: { // Redis config 31 | host: '127.0.0.1', 32 | port: 6379 33 | } 34 | }, 35 | "redis-jwt": { // Sessions 36 | //host: '/tmp/redis.sock', //unix domain 37 | host: '127.0.0.1', //can be IP or hostname 38 | port: 6379, // port 39 | maxretries: 10, //reconnect retries, default 10 40 | //auth: '123', //optional password, if needed 41 | db: 0, //optional db selection 42 | secret: 'secret_key', // secret key for Tokens! 43 | multiple: true, // single or multiple sessions by user 44 | kea: false // Enable notify-keyspace-events KEA 45 | }, 46 | mongoose: { // MongoDB 47 | // uri: mongodb://username:password@host:port/database?options 48 | uri: `mongodb://localhost:27017/${DB_NAME}`, 49 | options: { 50 | }, 51 | seed: { 52 | path: '/api/models/seeds/', 53 | list: [ 54 | { 55 | file: 'user.seed', 56 | schema: 'User', 57 | plant: 'once' // once - always - never 58 | }, 59 | { 60 | file: 'example.seed', 61 | schema: 'Example', 62 | plant: 'once' 63 | } 64 | ] 65 | }, 66 | }, 67 | oAuth: { // oAuth 68 | local: { 69 | enabled: true 70 | }, 71 | facebook: { 72 | enabled: false, 73 | clientID: '', 74 | clientSecret: '', 75 | callbackURL: '/auth/facebook/callback' 76 | }, 77 | twitter: { 78 | enabled: false, 79 | clientID: '', 80 | clientSecret: '', 81 | callbackURL: '/auth/twitter/callback' 82 | }, 83 | google: { 84 | enabled: false, 85 | clientID: '', 86 | clientSecret: '', 87 | callbackURL: '/auth/google/callback' 88 | }, 89 | github: { 90 | enabled: true, 91 | clientID: '52be92c9a41f77a959eb', 92 | clientSecret: '76c9bb03c689d098506822fa80dba372a1fe29c8', 93 | callbackURL: '/auth/github/callback' 94 | }, 95 | bitbucket: { 96 | enabled: false, 97 | clientID: '', 98 | clientSecret: '', 99 | callbackURL: '/auth/bitbucket/callback' 100 | } 101 | }, 102 | // globals 103 | mode: process.env.NODE_ENV || 'development', // mode 104 | name: APP_NAME, // name 105 | node: parseInt(process.env.NODE_APP_INSTANCE) || 0, // node instance 106 | root: path.normalize(`${__dirname}/../..`), // root 107 | base: path.normalize(`${__dirname}/..`), // base 108 | client: `${path.normalize(`${__dirname}/../..`)}${CLIENT}`, // client 109 | }; -------------------------------------------------------------------------------- /src/config/production.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | const APP_NAME = `your-app-name`; 4 | const DB_NAME = `your-app-name`; 5 | const CLIENT = '/client'; 6 | 7 | export default { 8 | secret: `your_secret_key`, // Secret Key 9 | server: { // Express 10 | ip: 'localhost', 11 | port: 8000, 12 | }, 13 | log: false, // show logs 14 | // Roles: if a user has multiple roles, will take the time of the greater role 15 | roles: [ 16 | { 17 | role: 'user', 18 | ttl: '10080 minutes', 19 | }, { 20 | role: 'admin', 21 | ttl: '7 days' 22 | } 23 | ], 24 | path: { 25 | disabled: '/:url(api|assets|auth|config|lib|views)/*' // paths 404 26 | }, 27 | "socket.io": { // Socket.io 28 | port: 8001, // public port listen, change also in views/default/demo.js 29 | example: true, // router -> http://localhost:8000/socket 30 | redis: { // Redis config 31 | host: '127.0.0.1', 32 | port: 6379 33 | } 34 | }, 35 | "redis-jwt": { // Sessions 36 | //host: '/tmp/redis.sock', //unix domain 37 | host: '127.0.0.1', //can be IP or hostname 38 | port: 6379, // port 39 | maxretries: 10, //reconnect retries, default 10 40 | //auth: '123', //optional password, if needed 41 | db: 0, //optional db selection 42 | secret: 'secret_key', // secret key for Tokens! 43 | multiple: true, // single or multiple sessions by user 44 | kea: false // Enable notify-keyspace-events KEA 45 | }, 46 | mongoose: { // MongoDB 47 | // uri: mongodb://username:password@host:port/database?options 48 | uri: `mongodb://localhost:27017/${DB_NAME}`, 49 | options: { 50 | }, 51 | seed: { 52 | path: '/api/models/seeds/', 53 | list: [ 54 | { 55 | file: 'user.seed', 56 | schema: 'User', 57 | plant: 'once' // once - always - never 58 | }, 59 | { 60 | file: 'example.seed', 61 | schema: 'Example', 62 | plant: 'once' 63 | } 64 | ] 65 | }, 66 | }, 67 | oAuth: { // oAuth 68 | local: { 69 | enabled: true 70 | }, 71 | facebook: { 72 | enabled: false, 73 | clientID: '', 74 | clientSecret: '', 75 | callbackURL: '/auth/facebook/callback' 76 | }, 77 | twitter: { 78 | enabled: false, 79 | clientID: '', 80 | clientSecret: '', 81 | callbackURL: '/auth/twitter/callback' 82 | }, 83 | google: { 84 | enabled: false, 85 | clientID: '', 86 | clientSecret: '', 87 | callbackURL: '/auth/google/callback' 88 | }, 89 | github: { 90 | enabled: true, 91 | clientID: '52be92c9a41f77a959eb', 92 | clientSecret: '76c9bb03c689d098506822fa80dba372a1fe29c8', 93 | callbackURL: '/auth/github/callback' 94 | }, 95 | bitbucket: { 96 | enabled: false, 97 | clientID: '', 98 | clientSecret: '', 99 | callbackURL: '/auth/bitbucket/callback' 100 | } 101 | }, 102 | // globals 103 | mode: process.env.NODE_ENV || 'production', // mode 104 | name: APP_NAME, // name 105 | node: parseInt(process.env.NODE_APP_INSTANCE) || 0, // node instance 106 | root: path.normalize(`${__dirname}/../..`), // root 107 | base: path.normalize(`${__dirname}/..`), // base 108 | client: `${path.normalize(`${__dirname}/../..`)}${CLIENT}`, // client 109 | }; -------------------------------------------------------------------------------- /src/lib/express/client.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from "fs"; 3 | import express from 'express'; 4 | import favicon from 'serve-favicon'; 5 | import config from '../../config'; 6 | 7 | export default (app) => { 8 | 9 | // Paths 404 from url 10 | app.get(config.path.disabled, (req, res) => { 11 | res.status(404).sendFile(`${config.base}/views/404.html`); 12 | }); 13 | 14 | // Point static path to client by default 15 | let client = config.client; 16 | let file = 'index'; 17 | 18 | // If not exits client, when set internal default 19 | if (!fs.existsSync(config.client)) { 20 | client = `${config.base}/views/default`; 21 | file = config.mode; 22 | if (config['socket.io'].example) { 23 | app.use('/socket', express.static(`${client}/socket.html`)); 24 | app.use('/token', express.static(`${client}/token.html`)); 25 | } 26 | } 27 | 28 | app.use(express.static(client)); 29 | app.use(favicon(path.join(client, 'favicon.ico'))); 30 | 31 | // Folder client 32 | app.get('/*', (req, res) => { 33 | res.sendFile(`${client}/${file}.html`); 34 | }); 35 | 36 | 37 | // Examples 38 | 39 | // app.use('/bower_components', express.static(`${config.root}/bower_components`)); 40 | // app.get('/:url(admin)/*', (req, res) => { 41 | // res.sendFile(`${config.client2}/index.html`); 42 | // }); 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/lib/express/index.js: -------------------------------------------------------------------------------- 1 | import helmet from 'helmet'; 2 | import bodyParser from 'body-parser'; 3 | import compression from 'compression'; 4 | import methodOverride from 'method-override'; 5 | import cookieParser from 'cookie-parser'; 6 | import passport from 'passport'; 7 | import session from 'express-session'; 8 | import cors from 'cors'; 9 | import morgan from 'morgan'; 10 | import config from '../../config'; 11 | 12 | export function index(app) { 13 | 14 | return new Promise((resolve, reject) => { 15 | 16 | app.use(bodyParser.json({ limit: '5mb' })); 17 | app.use(bodyParser.urlencoded({ extended: false })); 18 | app.use(cookieParser()); 19 | app.use(methodOverride()); 20 | app.use(compression()); 21 | app.use(helmet()); 22 | app.use(cors({ origin: true, credentials: true })); 23 | 24 | if ("twitter" in config.oAuth && config.oAuth.twitter.enabled) 25 | app.use(session({ secret: config.secret, resave: false, saveUninitialized: false })); 26 | 27 | app.use(passport.initialize()); 28 | app.use(passport.session()); 29 | 30 | // Morgan 31 | if (config.log) 32 | app.use(morgan('dev')); 33 | 34 | resolve(); 35 | 36 | }) 37 | 38 | }; 39 | -------------------------------------------------------------------------------- /src/lib/mongoose/index.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import chalk from 'chalk'; 3 | import fs from 'fs'; 4 | import config from '../../config'; 5 | 6 | const uri = config.mongoose.uri; 7 | const opts = config.mongoose.options; 8 | 9 | mongoose.Promise = global.Promise; 10 | 11 | export async function connect() { 12 | 13 | try { 14 | const conn = await mongoose.connect(uri, opts); 15 | const db = mongoose.connection; 16 | 17 | // Events 18 | db.on('disconnected', (err) => { 19 | console.log(chalk.redBright(`MongoDB-> disconnected: ${uri}`)); 20 | connect(); 21 | }); 22 | 23 | db.on('reconnected', (err) => { 24 | console.log(chalk.greenBright(`MongoDB-> reconnected: ${uri}`)); 25 | }); 26 | 27 | // Success 28 | console.log(chalk.greenBright(`-------\nMongoDB-> connected on ${uri}\n-------`)); 29 | 30 | // get Models 31 | let models = fs.readdirSync(`${config.base}/api/models`); 32 | 33 | for (let i in models) { 34 | if (models[i].indexOf('.js') > -1) { 35 | require(`${config.base}/api/models/${models[i]}`) 36 | } 37 | } 38 | // Plant seed 39 | await require('./seed').default(db, config); 40 | 41 | } catch (err) { 42 | console.log(chalk.redBright(`MongoDB-> connection error: ${uri} details->${err}`)); 43 | process.exit(-1); 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/lib/mongoose/seed.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | export default (conn, config) => { 4 | 5 | return new Promise((resolve, reject) => { 6 | 7 | const length = config.mongoose.seed.list.length; 8 | 9 | if (length > 0) { 10 | 11 | (function exec(i) { 12 | // get seed 13 | let seed = config.mongoose.seed.list[i]; 14 | // get model 15 | let model = conn.model(seed.schema); 16 | // get data 17 | let data = require(`${config.base}${config.mongoose.seed.path}${seed.file}`).default; 18 | // plant seed 19 | switch (seed.plant) { 20 | 21 | case 'once': 22 | model.count({}, (err, count) => { 23 | if (count <= 0) { 24 | model.collection.drop(() => { 25 | plant(model, data).then(() => i >= length - 1 ? resolve() : exec(i + 1)); 26 | }); 27 | } else { 28 | i >= length - 1 ? resolve() : exec(i + 1); 29 | } 30 | }); 31 | break; 32 | 33 | case 'always': 34 | model.collection.drop(() => { 35 | plant(model, data).then(() => i >= length - 1 ? resolve() : exec(i + 1)); 36 | }); 37 | break; 38 | 39 | case 'never': 40 | i >= length - 1 ? resolve() : exec(i + 1); 41 | break; 42 | default: 43 | 44 | } 45 | 46 | })(0); 47 | 48 | } else { 49 | resolve(); 50 | } 51 | 52 | }); 53 | 54 | } 55 | 56 | function plant(model, data) { 57 | 58 | return new Promise((resolve, reject) => { 59 | console.log(chalk.cyan(`Seed: Sowing seed ${model.modelName}'s...`)); 60 | 61 | model.insertMany(data) 62 | .then(() => { 63 | console.log(chalk.cyanBright(`Seed: Published ${model.modelName}'s!`)); 64 | resolve(); 65 | }) 66 | .catch(err => { 67 | console.log({ err }); 68 | }); 69 | }); 70 | 71 | } -------------------------------------------------------------------------------- /src/lib/redis-jwt/index.js: -------------------------------------------------------------------------------- 1 | import RedisJWT from 'redis-jwt'; 2 | import chalk from 'chalk'; 3 | import config from '../../config'; 4 | 5 | export const r = new RedisJWT(config['redis-jwt']); 6 | 7 | export const exec = r.exec(); 8 | 9 | export const call = r.call(); 10 | 11 | export function connect() { 12 | 13 | return new Promise((resolve, reject) => { 14 | 15 | r.on('ready', () => { 16 | console.log(chalk.greenBright(`-------\nRedis-> connected on ${config['redis-jwt'].host}:${config['redis-jwt'].port}/${config['redis-jwt'].db}\n-------`)); 17 | resolve(); 18 | }); 19 | 20 | r.on('error', (err) => { 21 | console.log(chalk.redBright(err)); 22 | }); 23 | }); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/lib/socket.io/index.js: -------------------------------------------------------------------------------- 1 | import Redis from 'socket.io-redis'; 2 | import chalk from 'chalk'; 3 | import fs from "fs"; 4 | import config from '../../config'; 5 | 6 | const io = require('socket.io')(config['socket.io'].port); 7 | io.adapter(Redis(config['socket.io'].redis)); 8 | 9 | // Scan events 10 | const pathSocket = `${config.base}/api/sockets`; 11 | let events = []; 12 | fs.readdirSync(pathSocket).forEach(path => { 13 | events.push(`${pathSocket}/${path}`); 14 | }); 15 | 16 | // Total socket clients 17 | function total() { 18 | io.of('/').adapter.clients((err, clients) => { 19 | console.log(chalk.blueBright(`Socket-> total clients [${clients.length}]`)); 20 | }); 21 | } 22 | 23 | // Connect 24 | export async function connect() { 25 | 26 | return await io.on('connection', socket => { 27 | 28 | if (config.log) { 29 | console.log(chalk.blueBright(`Socket-> connected`)); 30 | total(); 31 | } 32 | 33 | // inject events to new socket... 34 | events.forEach(path => require(path).default(socket, io)); 35 | 36 | socket.on('disconnect', () => { 37 | if (config.log) { 38 | console.log(chalk.blueBright(`Socket-> disconnect`)); 39 | total(); 40 | } 41 | }); 42 | 43 | }); 44 | } -------------------------------------------------------------------------------- /src/views/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 404 Page Not Found 7 | 8 | 89 | 90 | 91 | 92 | 404 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/views/default/client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | API 12 | 13 | 14 | 15 |
16 |

NODETOMIC

17 | 18 |
19 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/views/default/demo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* global io, location, alert, document, Vue */ 3 | 4 | // Config 5 | var host = location.origin; 6 | var url = parseInt(host.split(':').reverse()[0]); 7 | var hostSocket = host.replace(url, 8001); 8 | 9 | // Socket.io 10 | var socket = io.connect(hostSocket, { 11 | 'transports': ['websocket', 'polling'] 12 | }); 13 | 14 | // Vue.js 15 | var app = new Vue({ 16 | el: "#app", 17 | data: { 18 | logo: 'rubberBand', 19 | igreet: '', 20 | ilanguage: '', 21 | greetings: [] 22 | }, 23 | created: function created() { 24 | this.getGreetings(); 25 | }, 26 | methods: { 27 | getGreetings: function getGreetings() { 28 | var _this = this; 29 | 30 | this.$http.get(host + '/api/example/greeting').then(function (response) { 31 | _this.greetings = response.body; 32 | }, function (response) { 33 | alert('error :('); 34 | }); 35 | }, 36 | addGreeting: function addGreeting() { 37 | var _this2 = this; 38 | 39 | this.$http.post(host + '/api/example/greeting', { greet: this.igreet, language: this.ilanguage }).then(function (response) { 40 | _this2.igreet = _this2.ilanguage = ''; 41 | socket.emit('example:add', response.body); 42 | }, function (response) { 43 | alert('error :('); 44 | }); 45 | }, 46 | deleteGreeting: function deleteGreeting(id) { 47 | this.$http.delete(host + '/api/example/greeting/' + id).then(function (response) { 48 | socket.emit('example:delete', { _id: id }); 49 | }, function (response) { 50 | alert('error :('); 51 | }); 52 | } 53 | } 54 | }); 55 | 56 | socket.on('add', function (data) { 57 | app.greetings.push(data); 58 | }); 59 | 60 | socket.on('delete', function (data) { 61 | app.greetings.forEach(function (element) { 62 | if (element._id === data._id) { 63 | var index = app.greetings.indexOf(element); 64 | app.greetings.splice(index, 1); 65 | } 66 | }); 67 | }); 68 | 69 | // Animation 70 | var logo = document.getElementById("logo"); 71 | 72 | logo.addEventListener("animationend", function () { 73 | app.logo = ''; 74 | }); 75 | 76 | socket.on('animation', function (data) { 77 | console.log('from socket :D', data); 78 | app.logo = data; 79 | }); -------------------------------------------------------------------------------- /src/views/default/development.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | API - Development 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | 21 |
22 | 23 | 28 | 29 |
30 | Development 31 |
32 | 33 | 34 |
35 |
36 | Socket.io 37 |
38 |
39 | 40 | 44 | 45 |
46 |
47 |
48 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/views/default/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytesleo/nodetomic-api/HEAD/src/views/default/favicon.ico -------------------------------------------------------------------------------- /src/views/default/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 10 | 14 | 15 | 17 | 20 | 21 | 23 | 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 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/views/default/production.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | API - Production 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | 21 |
22 | 23 | 28 | 29 |
Production
30 | 31 | 35 | 36 |
37 |
38 |
39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/views/default/socket.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | API - Socket 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | 21 |
22 | 23 | 28 | 29 |
Socket.io
30 |
You can open multiple browser tabs to see the changes in real time
31 | 32 |
33 |
34 |
35 | 36 |
37 |
38 | 39 |
40 |
41 | 45 |
46 |
47 | 48 |
49 |
50 |
51 |
52 |
53 |
54 | 55 |

{{ item.greet }}

56 |

{{ item.language }}

57 |
58 |
59 |
60 |
61 | Loading... 62 |
63 |
64 | 65 | 69 | 70 |
71 |
72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/views/default/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 10vh; 3 | font-family: 'Roboto', sans-serif; 4 | } 5 | 6 | .content .title { 7 | text-align: center; 8 | font-size: 20px; 9 | margin-top: 2px; 10 | } 11 | 12 | .content .title a { 13 | color: #FBB071; 14 | } 15 | 16 | .footer a { 17 | color: #FF835D; 18 | } 19 | 20 | .logo { 21 | text-align: center; 22 | } 23 | 24 | .logo img { 25 | width: 100px; 26 | } 27 | 28 | .footer { 29 | margin-top: 20px; 30 | text-align: center; 31 | color: white; 32 | } 33 | 34 | .section { 35 | text-align: center; 36 | color: white; 37 | font-size: 25px; 38 | margin: 25px 0; 39 | } 40 | 41 | .tk { 42 | display: none !important; 43 | } 44 | 45 | .btn-add { 46 | margin-top: 15px; 47 | } 48 | 49 | .hello .greet { 50 | font-size: 17px; 51 | } 52 | 53 | .hello .language { 54 | color: #FBB071; 55 | font-size: 11px; 56 | } 57 | 58 | .hello .rm { 59 | float: right; 60 | cursor: pointer; 61 | font-size: 11px; 62 | } 63 | 64 | .note { 65 | font-style: oblique; 66 | color: white; 67 | text-align: center; 68 | padding-bottom: 10px; 69 | } -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | import http from 'http'; 4 | import assert from 'assert'; 5 | import request from 'request'; 6 | import config from '../src/config'; 7 | 8 | describe('/', () => { 9 | 10 | const host = `http://${config.server.ip}:${config.server.port}`; 11 | 12 | // Server Online 13 | it('host should return 200 (Server Online)', done => { 14 | http.get(host, res => { 15 | assert.equal(200, res.statusCode); 16 | done(); 17 | }); 18 | }); 19 | 20 | /* Greeting */ 21 | 22 | // Create a new greeting 23 | let idGreeting = ''; 24 | it('/api/example/greeting should return 201 (Create)', done => { 25 | 26 | let options = { 27 | method: 'POST', 28 | url: `${host}/api/example/greeting`, 29 | json: { 30 | "greet": "Hi testing", 31 | "language": "Testing" 32 | } 33 | }; 34 | 35 | request(options, function (error, res, body) { 36 | if (error) 37 | throw new Error(error); 38 | idGreeting = body._id; 39 | assert.equal(201, res.statusCode); 40 | done(); 41 | }); 42 | 43 | }); 44 | 45 | // Get the list of greetings 46 | it('/api/example/greeting should return 200 (All)', done => { 47 | http.get(`${host}/api/example/greeting`, res => { 48 | assert.equal(200, res.statusCode); 49 | done(); 50 | }); 51 | }); 52 | 53 | // Get a greeting for id 54 | it('/api/example/greeting should return 200 (Read)', done => { 55 | http.get(`${host}/api/example/greeting/${idGreeting}`, res => { 56 | assert.equal(200, res.statusCode); 57 | done(); 58 | }); 59 | }); 60 | 61 | // Update a greeting by id 62 | it('/api/example/greeting should return 200 (Update)', done => { 63 | 64 | let options = { 65 | method: 'PUT', 66 | url: `${host}/api/example/greeting/${idGreeting}`, 67 | json: { 68 | "greet": "Hi again testing", 69 | "language": "Testing two" 70 | } 71 | }; 72 | request(options, function (error, res, body) { 73 | if (error) 74 | throw new Error(error); 75 | assert.equal(200, res.statusCode); 76 | done(); 77 | }); 78 | 79 | }); 80 | 81 | // Delete a greeting by id 82 | it('/api/example/greeting should return 200 (Delete)', done => { 83 | let options = { 84 | method: 'DELETE', 85 | url: `${host}/api/example/greeting/${idGreeting}` 86 | }; 87 | request(options, function (error, res, body) { 88 | if (error) 89 | throw new Error(error); 90 | assert.equal(200, res.statusCode); 91 | done(); 92 | }); 93 | }); 94 | 95 | /* Authentication */ 96 | 97 | // Test Middleware 98 | it('/api/user/me should return 403 (Forbidden)', done => { 99 | http.get(`${host}/api/user/me`, res => { 100 | assert.equal(403, res.statusCode); 101 | done(); 102 | }); 103 | }); 104 | 105 | // Register with duplicate username 106 | it('/api/user should return 500 (Duplicate username)', done => { 107 | 108 | let options = { 109 | method: 'POST', 110 | url: `${host}/api/user`, 111 | json: { 112 | "username": "admin", 113 | "password": "123", 114 | "email": "example@example.com", 115 | "name": "Hello", 116 | "lastname": "World" 117 | } 118 | }; 119 | 120 | request(options, function (error, res, body) { 121 | if (error) 122 | throw new Error(error); 123 | assert.equal(500, res.statusCode); 124 | done(); 125 | }); 126 | 127 | }); 128 | 129 | // Login 130 | let token = ''; 131 | it('/auth/local should return 200 (Login Success)', done => { 132 | 133 | let roles = ['admin', 'user']; 134 | let options = { 135 | method: 'POST', 136 | url: `${host}/auth/local`, 137 | headers: { 138 | 'cache-control': 'no-cache', 139 | 'content-type': 'application/x-www-form-urlencoded' 140 | }, 141 | form: { 142 | username: roles[Math.round(Math.random())], 143 | password: '123' 144 | } 145 | }; 146 | request(options, function (error, res, body) { 147 | if (error) 148 | throw new Error(error); 149 | token = JSON.parse(body).token; 150 | assert.equal(200, res.statusCode); 151 | done(); 152 | }); 153 | 154 | }); 155 | 156 | // Send Token 157 | it('/api/user/me should return 200 (Test Token)', done => { 158 | 159 | let options = { 160 | method: 'GET', 161 | url: `${host}/api/user/me`, 162 | headers: { 163 | 'authorization': `${token}` 164 | } 165 | }; 166 | request(options, function (error, res, body) { 167 | if (error) 168 | throw new Error(error); 169 | assert.equal(200, res.statusCode); 170 | done(); 171 | }); 172 | }); 173 | 174 | // Logout 175 | it('/auth/logout should return 200 (Logout)', done => { 176 | 177 | let options = { 178 | method: 'DELETE', 179 | url: `${host}/auth/logout`, 180 | headers: { 181 | 'authorization': `${token}` 182 | } 183 | }; 184 | request(options, function (error, res, body) { 185 | if (error) 186 | throw new Error(error); 187 | assert.equal(200, res.statusCode); 188 | done(); 189 | }); 190 | }); 191 | 192 | }); 193 | --------------------------------------------------------------------------------