├── .DS_Store ├── .gitignore ├── LICENSE ├── README.md ├── arch.sh ├── migrate.js ├── migration.babel.stub ├── osx-setup.sh └── ubuntu-setup.sh /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dg92/node-express-postgres-redis-starter-kit/ba21153590cc14d9c4cca10c3eef80d6ea128030/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Deepak Gupta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-express-postgres-redis-starter-kit 2 | This is a starter kit that help you get started with node, express, postgresql, redis, es6 with just one shell script to run. 3 | 4 | # osx-setup.sh : This will install. 5 | 1. Brew, 6 | 2. Node(latest) 7 | 3. Postgresql(latest) 8 | 4. Redis 9 | 10 | # ubuntu-setup.sh : This will install. 11 | 1. Node(latest) 12 | 2. Postgresql(latest) 13 | 3. Redis 14 | 15 | # arch.sh : This will setup 16 | 1. An express server 17 | 2. Basic authentication and its api's. 18 | 3. Setup for writing api's. 19 | 4. Setup for CLI task runner system. 20 | 21 | # It uses 22 | 1. Node.js 23 | 2. Express.js 24 | 3. Postgresql Latest 25 | 4. Redis 26 | 5. Tabel(An orm written on top knex.js thanks to : https://github.com/fractaltech/tabel) 27 | 28 | 29 | # How to uses it 30 | 1. Clone this repository 31 | 2. cd node-express-postgres-redis-starter-kit 32 | 3. (OPTIONAL) If you need to setup (Node, redis, postgresql) then run ./osx-setup.sh for MAC or ./ubuntu-setup.sh for ubuntu users. 33 | 4. Follow terminal 34 | 35 | # What you will have 36 | 1. Login, Logout and Signup Apis on the Go. 37 | 2. Full Fledged Backend Setup where you directly write APIS. 38 | 39 | Same Architecture Can expand to have a queue and a worker system to use that feature email me ideepak.jsd@gmail.com 40 | 41 | 💡 4-5 days of work, done in less than 1 minutes. 42 | -------------------------------------------------------------------------------- /arch.sh: -------------------------------------------------------------------------------- 1 | echo '******** lets name your project now ********' 2 | echo -n "Enter your project name > " 3 | read projectname 4 | mkdir $projectname 5 | cd $projectname 6 | 7 | echo 'We are ready now so lets setup project 8 | ---------------------------------------------------' 9 | npm init 10 | 11 | echo 'lets add git to it' 12 | git init 13 | echo 'Creating .gitignore' 14 | touch .gitignore 15 | echo " 16 | node_modules 17 | npm-debug.log 18 | /config.js 19 | /*.pid 20 | *.rdb 21 | .DS_Store 22 | " >> .gitignore 23 | 24 | cp ../migration.babel.stub migration.babel.stub 25 | 26 | echo 'Now let us install some dev dependencies 27 | ---------------------------------------------------' 28 | npm install --save-dev babel-cli@6.10.1 babel-core@6.10.4 babel-eslint@4.1.6 babel-plugin-add-module-exports@0.2.1 babel-polyfill@6.9.1 nodemon@1.8.1 29 | npm install --save-dev babel-preset-es2015@6.9.0 babel-preset-stage-0@6.5.0 eslint@1.10.3 eslint-plugin-babel@3.0.0 eslint-plugin-react@3.12.0 supertest@1.2.0 30 | echo 'and some more dependencies 31 | ---------------------------------------------------' 32 | npm install --save bcrypt body-parser cors errorhandler express lodash moment rand-token request-promise tabel pg 33 | echo '******** Dependencies installed successfully *********' 34 | 35 | echo 'creating .babelrc' 36 | touch .babelrc 37 | echo ' 38 | { 39 | "presets": ["stage-0", "es2015"], 40 | "plugins": [ 41 | "add-module-exports" 42 | ] 43 | } 44 | ' >> .babelrc 45 | 46 | echo 'Creating .eslintrc' 47 | touch .eslintrc 48 | 49 | echo ' 50 | { 51 | "ecmaFeatures": { 52 | "arrowFunctions": true, 53 | "binaryLiterals": false, 54 | "blockBindings": true, 55 | "classes": true, 56 | "defaultParams": true, 57 | "destructuring": true, 58 | "forOf": true, 59 | "generators": true, 60 | "modules": true, 61 | "objectLiteralComputedProperties": true, 62 | "objectLiteralDuplicateProperties": false, 63 | "objectLiteralShorthandMethods": true, 64 | "objectLiteralShorthandProperties": true, 65 | "octalLiterals": false, 66 | "regexUFlag": false, 67 | "regexYFlag": false, 68 | "restParams": true, 69 | "spread": true, 70 | "superInFunctions": true, 71 | "templateStrings": true, 72 | "unicodePointEscapes": true, 73 | "globalReturn": false, 74 | "jsx": true 75 | }, 76 | "env": { 77 | "browser": true, 78 | "node": true, 79 | "mocha": true 80 | }, 81 | "parser": "babel-eslint", 82 | "plugins": [ 83 | "babel", 84 | "react" 85 | ], 86 | "rules": { 87 | 88 | /*Possible Errors */ 89 | "comma-dangle": [1, "never"], 90 | "no-cond-assign": [1, "except-parens"], 91 | "no-console": 0, 92 | "no-constant-condition": 1, 93 | "no-control-regex": 1, 94 | "no-debugger": 1, 95 | "no-dupe-args": 1, 96 | "no-dupe-keys": 1, 97 | "no-duplicate-case": 0, 98 | "no-empty-character-class": 1, 99 | "no-empty": 1, 100 | "no-ex-assign": 1, 101 | "no-extra-boolean-cast": 1, 102 | "no-extra-parens": 0, 103 | "no-extra-semi": 1, 104 | "no-func-assign": 1, 105 | "no-inner-declarations": [1, "functions"], 106 | "no-invalid-regexp": 1, 107 | "no-irregular-whitespace": 1, 108 | "no-negated-in-lhs": 1, 109 | "no-obj-calls": 1, 110 | "no-regex-spaces": 1, 111 | "no-reserved-keys": 0, 112 | "no-sparse-arrays": 1, 113 | "no-unexpected-multiline": 1, 114 | "no-unreachable": 1, 115 | "use-isnan": 1, 116 | "valid-jsdoc": 1, 117 | "valid-typeof": 1, 118 | 119 | /* Best Practices */ 120 | "accessor-pairs": 0, 121 | "block-scoped-var": 0, // see Babel section 122 | "complexity": 0, 123 | "consistent-return": 1, 124 | "curly": [1, "all"], 125 | "default-case": 0, 126 | "dot-notation": [1, { "allowKeywords": true, "allowPattern": "" }], 127 | "dot-location": [1, "property"], 128 | "eqeqeq": 1, 129 | "guard-for-in": 0, 130 | "no-alert": 1, 131 | "no-caller": 1, 132 | "no-console": 1, 133 | "no-div-regex": 1, 134 | //"no-else-return": 1, 135 | "no-empty-label": 1, 136 | "no-eq-null": 0, 137 | "no-eval": 1, 138 | "no-extend-native": 1, 139 | "no-extra-bind": 1, 140 | "no-fallthrough": 0, 141 | "no-floating-decimal": 1, 142 | "no-implied-eval": 1, 143 | "no-iterator": 1, 144 | "no-labels": 1, 145 | "no-lone-blocks": 1, 146 | "no-loop-func": 1, 147 | "no-multi-spaces": 1, 148 | "no-multi-str": 1, 149 | "no-native-reassign": 1, 150 | "no-new-func": 1, 151 | "no-new-wrappers": 1, 152 | "no-new": 1, 153 | "no-octal-escape": 1, 154 | "no-octal": 1, 155 | "no-param-reassign": 0, 156 | "no-process-env": 0, 157 | "no-proto": 1, 158 | "no-redeclare": 1, 159 | "no-return-assign": 1, 160 | "no-script-url": 1, 161 | "no-self-compare": 1, 162 | "no-sequences": 1, 163 | "no-throw-literal": 1, 164 | "no-unused-expressions": 0, 165 | "no-void": 0, 166 | "no-warning-comments": [1, { "terms": ["todo", "tofix"], "location": "start" }], 167 | "no-with": 1, 168 | "radix": 1, 169 | "vars-on-top": 1, 170 | "wrap-iife": [1, "inside"], 171 | "yoda": [1, "never"], 172 | 173 | /* Strict Mode */ 174 | "strict": [1, "never"], 175 | 176 | /* Variables */ 177 | "no-catch-shadow": 0, 178 | "no-delete-var": 1, 179 | "no-label-var": 1, 180 | "no-shadow-restricted-names": 1, 181 | "no-shadow": 0, 182 | "no-undef-init": 1, 183 | "no-undef": 1, 184 | "no-undefined": 1, 185 | "no-unused-vars": [1, { "vars": "local", "args": "after-used" }], 186 | "no-use-before-define": 0, 187 | 188 | /* Node.js */ 189 | "handle-callback-err": 1, 190 | "no-mixed-requires": 1, 191 | "no-new-require": 1, 192 | "no-path-concat": 1, 193 | "no-process-exit": 1, 194 | "no-restricted-modules": [1, ""], // add any unwanted Node.js core modules 195 | "no-sync": 1, 196 | 197 | /* Stylistic Issues */ 198 | "array-bracket-spacing": [1, "never"], 199 | "brace-style": [1, "1tbs", { "allowSingleLine": true }], 200 | //"camelcase": [1, { "properties": "never" }], 201 | "comma-spacing": [1, { "before": false, "after": true }], 202 | "comma-style": [1, "last"], 203 | "computed-property-spacing": 0, 204 | "consistent-this": 0, 205 | "eol-last": 1, 206 | "func-names": 1, 207 | "func-style": 0, 208 | "indent": [1, 2, {"SwitchCase": 1}], 209 | "key-spacing": [1, { "beforeColon": false, "afterColon": true }], 210 | "linebreak-style": 0, 211 | "max-nested-callbacks": [0, 3], 212 | "new-cap": 0, // see Babel section 213 | "new-parens": 1, 214 | "newline-after-var": 0, 215 | "no-array-constructor": 1, 216 | "no-continue": 1, 217 | "no-inline-comments": 0, 218 | "no-lonely-if": 0, 219 | "no-mixed-spaces-and-tabs": 1, 220 | "no-multiple-empty-lines": [1, { "max": 1 }], 221 | "no-nested-ternary": 0, 222 | "no-new-object": 1, 223 | "no-spaced-func": 1, 224 | "no-ternary": 0, 225 | "no-trailing-spaces": 1, 226 | "no-underscore-dangle": 0, 227 | "no-unneeded-ternary": 1, 228 | "object-curly-spacing": 0, // see Babel section 229 | "one-var": [1, "never"], 230 | "operator-assignment": [1, "never"], 231 | "padded-blocks": [0, "never"], 232 | "quote-props": [0, "as-needed"], 233 | "quotes": [1, "single"], 234 | "semi-spacing": [1, { "before": false, "after": true }], 235 | "semi": [1, "always"], 236 | "sort-vars": 0, 237 | "space-after-keywords": 0, 238 | "space-before-blocks": [1, "always"], 239 | "space-before-function-paren": [1, "never"], 240 | "space-in-parens": [1, "never"], 241 | //"space-infix-ops": 1, 242 | "space-return-throw-case": 1, 243 | "space-unary-ops": 0, 244 | "spaced-comment": [1, "always"], 245 | "wrap-regex": 1, 246 | 247 | /* ECMAScript 6 */ 248 | "constructor-super": 1, 249 | "generator-star-spacing": 0, // see Babel section 250 | "no-this-before-super": 1, 251 | "no-var": 1, 252 | "object-shorthand": 0, // see Babel section 253 | "prefer-const": 1, 254 | 255 | /* Legacy */ 256 | "max-depth": [0, 3], 257 | "max-len": [1, 121, 2], 258 | "max-params": 0, 259 | "max-statements": 0, 260 | "no-bitwise": 1, 261 | "no-plusplus": 1, 262 | 263 | /* Babel */ 264 | // "babel/block-scoped-var": 1, 265 | "babel/object-shorthand": [1, "always"], 266 | // "babel/generator-star": 1, // deprecated 267 | "babel/generator-star-spacing": [1, "after"], 268 | "babel/new-cap": 0, 269 | "babel/object-curly-spacing": [1, "never"], 270 | // "babel/space-in-brackets": 1, // deprecated 271 | } 272 | } 273 | ' >> .eslintrc 274 | 275 | echo 'Creating config.js' 276 | touch config.js 277 | 278 | echo " 279 | export default { 280 | orm: { 281 | db: { 282 | client: 'postgresql', 283 | connection: { 284 | database: 'dev', 285 | host: 'localhost', 286 | port: 5432 287 | }, 288 | pool: { 289 | min: 2, 290 | max: 10 291 | }, 292 | migrations: 'knex_migrations' 293 | }, 294 | redis: { 295 | host: 'localhost', 296 | port: '6379' 297 | } 298 | }, 299 | 300 | auth: { 301 | lifetime: 2 * 24 * 3600 * 1000 302 | }, 303 | 304 | http: { 305 | host: '0.0.0.0', 306 | port: 3000 307 | } 308 | }; 309 | " >> config.js 310 | 311 | mkdir app 312 | cd app 313 | echo '********** Creating folders 314 | 1. http: where all your routes are present. 315 | 2. orm: where all your relationships and mapping with database occur. 316 | 3. reducer: which has a custom vaildator created. 317 | 4. tasks: for creating task triggered from cli 318 | 5. util: where all your utility files are saved. 319 | **********' 320 | 321 | mkdir auth http orm reducer tasks util 322 | cd util 323 | touch index.js 324 | 325 | echo " 326 | import { 327 | isObject, 328 | isArray, 329 | isFunction, 330 | isRegExp, 331 | isNumber, 332 | isString, 333 | isElement, 334 | isDate 335 | } from 'lodash'; 336 | 337 | export function handleAsyncExceptions() { 338 | if (handleAsyncExceptions.hooked === false) { 339 | process.on('unhandledRejection', (err) => { 340 | throw err; 341 | }); 342 | handleAsyncExceptions.hooked = true; 343 | } 344 | } 345 | 346 | export function isEmail(email) { 347 | const re = /\S+@\S+\.\S+/; 348 | return re.test(email); 349 | } 350 | 351 | handleAsyncExceptions.hooked = false; 352 | 353 | export function printIp() { 354 | const os = require('os'); 355 | const ifaces = os.networkInterfaces(); 356 | 357 | Object.keys(ifaces).forEach((ifname) => { 358 | let alias = 0; 359 | 360 | ifaces[ifname].forEach((iface) => { 361 | if (iface.family !== 'IPv4' || iface.internal !== false) { 362 | // skip over internal (i.e. 127.0.0.1) and non-ipv4 addresses 363 | return; 364 | } 365 | 366 | if (alias >= 1) { 367 | // this single interface has multiple ipv4 addresses 368 | console.log(ifname + ':' + alias, iface.address); 369 | } else { 370 | // this interface has only one ipv4 adress 371 | console.log(ifname, iface.address); 372 | } 373 | alias = alias+1; 374 | }); 375 | }); 376 | } 377 | 378 | export function isUsableObject(val) { 379 | return isObject(val) && ! ( 380 | isArray(val) || isFunction(val) || isRegExp(val) || isNumber(val) || isString(val) || 381 | isElement(val) || isDate(val) 382 | ); 383 | } 384 | " >> index.js 385 | echo '********** Util folder setup completed **********' 386 | cd ../tasks 387 | mkdir seeds 388 | touch index.js seed.js dev.js 389 | 390 | echo " 391 | import dev from './dev'; 392 | import migrate from './migrate'; 393 | import seed from './seed'; 394 | 395 | export default { 396 | dev, 397 | migrate, 398 | seed 399 | }; 400 | " >> index.js 401 | 402 | cp ../../../migrate.js migrate.js 403 | 404 | echo " 405 | import seeds from './seeds'; 406 | 407 | export default async function run() { 408 | await Object.keys(seeds).map((tableName) => seeds[tableName]).reduce( 409 | (chain, seed) => chain.then(() => seed.run()), 410 | Promise.resolve({}) 411 | ) 412 | ; 413 | } 414 | " >> seed.js 415 | 416 | echo " 417 | export default async function run() { 418 | console.log('dev'); 419 | } 420 | " >> dev.js 421 | 422 | cd seeds 423 | touch index.js 424 | 425 | echo " 426 | export default { 427 | }; 428 | " >> index.js 429 | 430 | echo '********** tasks setup successfully **********' 431 | 432 | cd ../../reducer 433 | touch index.js Validator.js 434 | 435 | echo " 436 | import Validator from './Validator'; 437 | 438 | export { 439 | Validator 440 | }; 441 | " >> index.js 442 | 443 | echo " 444 | import {assign, isArray, toPlainObject} from 'lodash'; 445 | 446 | import {isUsableObject} from 'app/util'; 447 | 448 | export default class Validator { 449 | constructor(validations=[]) { 450 | this.validations = new Map(); 451 | this.addValidations(validations); 452 | } 453 | 454 | addValidations(validations=[]) { 455 | if (isUsableObject(validations)) { 456 | validations = toPlainObject(validations); 457 | validations = Object.keys(validations).map((k) => ({key: k, validation: validations[k]})); 458 | } 459 | 460 | validations.forEach(({key, validation}) => { 461 | this.validations.set(key, validation); 462 | }); 463 | 464 | return this; 465 | } 466 | 467 | addValidation({key, validation}) { 468 | this.validations.set(key, validation); 469 | return this; 470 | } 471 | 472 | merge(validator) { 473 | Array.from(validator.validation.keys()).forEach((k) => { 474 | this.validations.set(k, validator.validations.get(k)); 475 | }); 476 | 477 | return this; 478 | } 479 | 480 | errors(input={}) { 481 | const keys = Array.from(this.validations.keys()); 482 | 483 | return Promise.all( 484 | keys.map((k) => { 485 | const validated = this.validations.get(k).bind(this)(input[k], input, k); 486 | if (validated instanceof Promise) { 487 | return validated.then((errors) => { 488 | return {key: k, errors}; 489 | }); 490 | } else if (validated instanceof Validator) { 491 | return validated.errors(input[k]).then((errors) => { 492 | return {key: k, errors}; 493 | }); 494 | } else { 495 | return {key: k, errors: validated}; 496 | } 497 | }) 498 | ) 499 | .then((errorMessages) => errorMessages.filter((msg) => { 500 | return isArray(msg.errors) ? msg.errors.length > 0 : !!msg.errors; 501 | })) 502 | .then((errorMessages) => { 503 | if (errorMessages.length === 0) { 504 | return null; 505 | } else { 506 | return errorMessages.reduce((allErrors, {key, errors}) => { 507 | return assign(allErrors, { 508 | [key]: isArray(errors) ? errors : ( 509 | isUsableObject(errors) ? toPlainObject(errors) : [errors] 510 | ) 511 | }); 512 | }, {}); 513 | } 514 | }); 515 | } 516 | 517 | run(input={}) { 518 | return this.errors(input).then((errors) => { 519 | if (errors) { 520 | throw errors; 521 | } else { 522 | return input; 523 | } 524 | }); 525 | } 526 | } 527 | " >> Validator.js 528 | 529 | cd ../orm 530 | mkdir tables 531 | touch config.js index.js 532 | 533 | echo " 534 | import {orm} from 'config'; 535 | 536 | export default orm; 537 | " >> config.js 538 | 539 | echo " 540 | import Tabel from 'tabel'; 541 | import config from './config'; 542 | import loadTables from './tables'; 543 | 544 | const orm = new Tabel(config); 545 | 546 | loadTables(orm); 547 | 548 | export default orm.exports; 549 | " >> index.js 550 | 551 | cd tables 552 | touch index.js 553 | 554 | echo " 555 | import users from './users'; 556 | export default function loadTables(orm) { 557 | users(orm); 558 | } 559 | " >> index.js 560 | 561 | touch users.js 562 | 563 | echo " 564 | export default function loadTables(orm) { 565 | orm.defineTable({ 566 | name: 'users', 567 | 568 | props: { 569 | autoId: true, 570 | timestamps: true 571 | } 572 | }) 573 | } 574 | " >> users.js 575 | 576 | echo "Orm setup successfully" 577 | 578 | cd ../../http 579 | mkdir auth 580 | touch config.js index.js 581 | 582 | echo " 583 | import {http} from 'config'; 584 | 585 | export default http; 586 | " >> config.js 587 | 588 | echo " 589 | import express from 'express'; 590 | import bodyParser from 'body-parser'; 591 | import errorhandler from 'errorhandler'; 592 | import cors from 'cors'; 593 | 594 | import {loggedIn} from 'app/auth/filters'; 595 | import auth from './auth'; 596 | 597 | const app = express(); 598 | 599 | // parse application/x-www-form-urlencoded 600 | app.use(bodyParser.urlencoded({extended: true})); 601 | // parse application/json 602 | app.use(bodyParser.json({limit: '50mb'})); 603 | // parse multipart-form-data 604 | app.use(cors()); 605 | // handle errors, should only be enabled in dev, but everything is dev 606 | // right now 607 | app.use(errorhandler()); 608 | 609 | if (process.env.NODE_ENV === 'production') { 610 | // trust proxy in production from local nginx front server 611 | app.set('trust proxy', 'loopback'); 612 | } 613 | app.use('/auth', auth); 614 | 615 | app.get('/', (req, res) => { 616 | res.send({msg: 'Nodejs is live. Lets go and chanage the world.'}); 617 | }); 618 | 619 | app.use(loggedIn); 620 | 621 | // catch all route 622 | app.all('*', (req, res) => { 623 | res.status(404).send({msg: 'not found'}); 624 | }); 625 | export default app; 626 | " >> index.js 627 | 628 | cd auth 629 | touch index.js 630 | 631 | echo " 632 | import express from 'express'; 633 | import {isString, omit, isUndefined} from 'lodash'; 634 | import bcrypt from 'bcrypt'; 635 | 636 | import {table} from 'app/orm'; 637 | import {login, logout} from 'app/auth'; 638 | import {Validator} from 'app/reducer'; 639 | import {isEmail} from 'app/util'; 640 | 641 | import { 642 | loggedIn, 643 | loggedOut, 644 | getAuthKeyFromRequest 645 | } from 'app/auth/filters'; 646 | 647 | const app = express(); 648 | 649 | app.post('/login', loggedOut, async (req, res) => { 650 | const {email, password} = req.body; 651 | const {success, token, user} = await login(email, password); 652 | if (success) { 653 | const teams = await table('users') 654 | .where({id: user.id}) 655 | .first() 656 | ; 657 | return res.status(200).send({msg: 'logged in', token, user: omit(user, ['password']), teams}); 658 | } else { 659 | return res.status(400).send({email: 'Email password combination is invalid.'}); 660 | } 661 | }); 662 | 663 | app.post('/logout', loggedIn, async (req, res) => { 664 | const authKey = getAuthKeyFromRequest(req); 665 | await logout(authKey); 666 | return res.send({msg: 'logged out'}); 667 | }); 668 | 669 | async function alreadySignedUp(req, res, next) { 670 | if (!req.body.email || req.body.email.length === 0) { 671 | return next(); 672 | } 673 | const existing = await table('users') 674 | .where({email: req.body.email}) 675 | .all() 676 | ; 677 | if(existing.length > 0) { 678 | return res.status(409).send({email: ['Email already taken.']}); 679 | } else { 680 | return next(); 681 | } 682 | } 683 | 684 | const signUpValidator = new Validator({ 685 | ['email'](email) { 686 | return (!isUndefined(email) && isEmail(email) && isString(email)) ? null : 'Email is invalid.'; 687 | }, 688 | ['name'](name) { 689 | return (!isUndefined(name) && name.length > 0 && isString(name)) ? null : 'Name is invalid.'; 690 | }, 691 | ['password'](password) { 692 | if(!isUndefined(password) && isString(password) && password.length > 4) { 693 | return null; 694 | } else { 695 | return 'Password should be atleast 5 characters long.'; 696 | } 697 | } 698 | }); 699 | 700 | app.post('/signup', alreadySignedUp, async (req, res) => { 701 | const errors = await signUpValidator.errors(req.body); 702 | if (errors) { 703 | return res.status(400).send(errors); 704 | } 705 | const {email, password, name} = req.body; 706 | 707 | const user = await table('users').insert({ 708 | email: email.trim(), 709 | password: await new Promise((resolve) => bcrypt.hash(password, 10, (_, hash) => resolve(hash))), 710 | name: name.trim() 711 | }); 712 | if (user) { 713 | return res.status(200).send({msg: 'Signup success.', user: omit(user, ['password'])}); 714 | } else { 715 | return res.status(400).send({msg: 'Signup failed.'}); 716 | } 717 | }); 718 | 719 | export default app; 720 | " >> index.js 721 | echo "********** Http setup successfully **********" 722 | 723 | cd ../../auth 724 | touch config.js filters.js index.js 725 | 726 | echo " 727 | import {auth} from 'config'; 728 | 729 | export default auth; 730 | " >> config.js 731 | 732 | echo " 733 | import {check} from './index'; 734 | 735 | export async function loggedIn(req, res, next) { 736 | const authKey = getAuthKeyFromRequest(req); 737 | const user = await check(authKey); 738 | 739 | if (user !== null) { 740 | req.user = user; 741 | req.authKey = authKey; 742 | next(); 743 | } else { 744 | res.status(401).send({msg: 'Unauthorized'}); 745 | } 746 | } 747 | 748 | export async function loggedOut(req, res, next) { 749 | const authKey = getAuthKeyFromRequest(req); 750 | const user = await check(authKey); 751 | 752 | if (user !== null) { 753 | res.status(400).send({msg: 'already logged in'}); 754 | } else { 755 | next(); 756 | } 757 | } 758 | 759 | export function getAuthKeyFromRequest(req) { 760 | return req.header('auth-token'); 761 | } 762 | " >> filters.js 763 | 764 | echo " 765 | import {isString, shuffle} from 'lodash'; 766 | import bcrypt from 'bcrypt'; 767 | import randToken from 'rand-token'; 768 | 769 | import {table, cache} from 'app/orm'; 770 | import {isUsableObject} from 'app/util'; 771 | import config from './config'; 772 | 773 | const authHash = cache.hash('auth'); 774 | 775 | export async function login(email, password) { 776 | const user = await findUserByCredentials(email, password); 777 | if (user === null) { 778 | return {success: false, token: null, user: null}; 779 | } 780 | const authKey = await generateUniqueAuthKey(user); 781 | await authHash.set(authKey, user.id, config.lifetime); 782 | 783 | return {success: true, token: authKey, user}; 784 | } 785 | 786 | export async function check(authKey) { 787 | const userId = await authHash.get(authKey); 788 | 789 | if (userId === null) { 790 | return null; 791 | } 792 | 793 | const user = await table('users').find({id: userId}); 794 | 795 | return isUsableObject(user) ? user : null; 796 | } 797 | 798 | export async function logout(authKey) { 799 | await authHash.del(authKey); 800 | 801 | return true; 802 | } 803 | 804 | async function findUserByCredentials(email, password) { 805 | if (! isString(email) || ! isString(password)) { 806 | return null; 807 | } 808 | const user = await table('users').find({email}); 809 | if (! isUsableObject(user)) { 810 | return null; 811 | } 812 | 813 | const isValidUser = await bcryptCheck(password, user.password); 814 | return isValidUser ? user : null; 815 | } 816 | 817 | async function bcryptCheck(password, hash) { 818 | return await new Promise((resolve) => ( 819 | bcrypt.compare(password, hash, (err, res) => { 820 | if (err) { 821 | return resolve(false); 822 | } 823 | 824 | return resolve(res); 825 | }) 826 | )); 827 | } 828 | 829 | async function generateUniqueAuthKey(user) { 830 | const key = shuffle(randToken.generate(72)).join(''); 831 | 832 | const existing = await authHash.get(key); 833 | 834 | if (existing === null) { 835 | return key; 836 | } else { 837 | return await generateUniqueAuthKey(user); 838 | } 839 | } 840 | " >> index.js 841 | 842 | echo "Auth setup successfully" 843 | 844 | cd ../../app 845 | touch cli.js server.js 846 | 847 | echo " 848 | import {orm} from 'app/orm'; 849 | import {handleAsyncExceptions} from 'app/util'; 850 | 851 | import tasks from './tasks'; 852 | 853 | // the task runner 854 | async function run(taskName, ...args) { 855 | const taskNames = Object.keys(tasks); 856 | if (taskNames.indexOf(taskName) === -1) { 857 | console.log('Tasks are', taskNames) 858 | return Promise.resolve(null); 859 | } 860 | 861 | try { 862 | return await tasks[taskName](...args); 863 | } catch (err) { 864 | throw err; 865 | } 866 | } 867 | 868 | if (require.main === module) { 869 | // handle async exceptions 870 | handleAsyncExceptions(); 871 | // run the task runner 872 | const [taskName, ...args] = process.argv.slice(2); 873 | 874 | (async () => { 875 | await run(taskName, ...args); 876 | await new Promise((resolve) => setTimeout(async () => { 877 | await orm.close(); 878 | resolve(); 879 | }, 2500)); 880 | })(); 881 | } 882 | 883 | export default run; 884 | " >> cli.js 885 | 886 | echo " 887 | import http from 'app/http'; 888 | import config from 'app/http/config'; 889 | import {printIp, handleAsyncExceptions} from 'app/util'; 890 | 891 | async function run() { 892 | http.listen(config.port, config.host, () => { 893 | console.log('server is running ......') 894 | printIp(); 895 | }); 896 | } 897 | 898 | export default run; 899 | 900 | if (require.main === module) { 901 | handleAsyncExceptions(); 902 | run(...process.argv.slice(2)); 903 | } 904 | " >> server.js 905 | 906 | echo 'Copy paste this in package.json remove the old scripts 907 | "scripts": { 908 | "task": "NODE_PATH=. babel-node ./app/cli.js", 909 | "server": "NODE_PATH=. babel-node ./app/server.js", 910 | "nodemon:server": "NODE_PATH=. nodemon --exec \"babel-node\" ./app/server.js" 911 | }, 912 | "babel": { 913 | "presets": [ 914 | "es2015" 915 | ] 916 | },' 917 | 918 | echo -n "****** Press enter when you have done that" 919 | read ok 920 | 921 | echo ">>>>Go and create database dev and then open terminal and reach to project directory and type : npm run task migrate make CreateUsersTable <<<<" 922 | echo -n "****** Press enter when you have done that" 923 | read ok 924 | 925 | echo "A migration directory at root of project is created copy paste or edit as required 926 | function up(knex, Promise) { 927 | return knex.schema.createTable('users', (t) => { 928 | t.uuid('id').primary(); 929 | t.string('email').unique(); 930 | t.string('name'); 931 | t.date('birthday'); 932 | t.string('gender'); 933 | t.string('phone_number'); 934 | t.string('password'); 935 | t.timestamps(); 936 | }); 937 | } 938 | 939 | function down(knex, Promise) { 940 | return knex.schema.dropTable('users'); 941 | } 942 | module.exports = {up, down}; 943 | " 944 | echo -n "****** Press enter when you have done that" 945 | read ok 946 | 947 | echo ">>>> Open the terminal again and reach to the project and then do : npm run task migrate latest <<<<<" 948 | 949 | echo "**************** successfully setup *****************" 950 | 951 | echo "Go inside project and type npm run nodemon:server once started Lets go to browser and type localhost:3000" -------------------------------------------------------------------------------- /migrate.js: -------------------------------------------------------------------------------- 1 | import {migrator} from 'app/orm'; 2 | 3 | export default async function run(...args) { 4 | await migrator.mount({ 5 | devDir: './migrations', 6 | distDir: './migrations', 7 | args, 8 | stub: `${process.cwd()}/migration.babel.stub` 9 | }); 10 | } 11 | 12 | -------------------------------------------------------------------------------- /migration.babel.stub: -------------------------------------------------------------------------------- 1 | function up(knex, Promise) { 2 | <% if (d.tableName) { %> 3 | return knex.schema.createTable("<%= d.tableName %>", (t) => { 4 | t.increments(); 5 | t.timestamp(); 6 | }); 7 | <% } %> 8 | } 9 | 10 | function down(knex, Promise) { 11 | <% if (d.tableName) { %> 12 | return knex.schema.dropTable("<%= d.tableName %>"); 13 | <% } %> 14 | } 15 | 16 | module.exports = {up, down}; 17 | -------------------------------------------------------------------------------- /osx-setup.sh: -------------------------------------------------------------------------------- 1 | echo '******** Welcome to the OSX Setup ********** 2 | --------------------------------------------------' 3 | 4 | echo 'This will install Brew, Nodejs(latest), Postgres(latest), Redis for your beautiful Mac for now and later project 5 | ---------------------------------------------------' 6 | 7 | echo 'Installing Brew for you 8 | ---------------------------------------------------' 9 | /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" -y 10 | echo 'Lets update your Brew now 11 | ---------------------------------------------------' 12 | brew update -y 13 | echo 'Is your brew need doctor so lets take him 14 | ---------------------------------------------------' 15 | brew doctor -y 16 | echo '******** Brew installed successfully *********' 17 | 18 | echo 'Installing Node now 19 | ---------------------------------------------------' 20 | brew install node -y 21 | brew update -y 22 | brew doctor -y 23 | echo '******** Node in your service ********' 24 | 25 | echo 'Installing Postgres now 26 | ---------------------------------------------------' 27 | brew install postgresql -y 28 | initdb /usr/local/var/postgres -E utf8 29 | mkdir -p ~/Library/LaunchAgents 30 | cp /usr/local/Cellar/postgresql/9.2.1/homebrew.mxcl.postgresql.plist ~/Library/LaunchAgents/ 31 | launchctl load -w ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist 32 | createdb dev 33 | echo '******** Postgres installed successfully and a db dev is created *********' 34 | 35 | echo 'Installing Redis now 36 | ---------------------------------------------------' 37 | brew install redis -y 38 | brew update -y 39 | brew doctor -y 40 | echo '******** Redis installed ********' -------------------------------------------------------------------------------- /ubuntu-setup.sh: -------------------------------------------------------------------------------- 1 | cd $HOME 2 | sudo apt-get update -y 3 | sudo apt-get install git -y 4 | sudo apt-get install build-essential -y 5 | sudo apt-get install -y redis-server 6 | 7 | if [ -d "$HOME/node" ]; then 8 | rm -rf $HOME/node 9 | fi 10 | 11 | cd $HOME 12 | wget https://nodejs.org/dist/v6.5.0/node-v6.5.0-linux-x64.tar.gz -O node.tar.gz 13 | tar -xzvf node.tar.gz 14 | echo "now moving path" 15 | mv node-v6.5.0-linux-x64 node 16 | rm node.tar.gz 17 | echo "PATH=\"\$HOME/node/bin:\$PATH\"" | tee -a .profile 18 | 19 | cd $HOME 20 | sudo rm /usr/bin/{node,npm} 21 | sudo ln -s $HOME/node/bin/node /usr/bin/node 22 | sudo ln -s $HOME/node/bin/npm /usr/bin/npm 23 | 24 | ################################################# 25 | # postgres 26 | sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" >> /etc/apt/sources.list.d/pgdg.list' 27 | wget -q https://www.postgresql.org/media/keys/ACCC4CF8.asc -O - | sudo apt-key add - 28 | sudo apt-get update 29 | sudo apt-get install postgresql postgresql-contrib -y 30 | sudo -i -u postgres createdb dev 31 | # sudo su postgres 32 | # psql 33 | # CREATE USER dev WITH SUPERUSER PASSWORD 'dev'; 34 | # \q 35 | # exit 36 | sudo service postgresql restart 37 | ################################################# 38 | # redis 39 | sudo service redis-server restart 40 | --------------------------------------------------------------------------------