├── .gitignore ├── .sailsrc ├── .travis.yml ├── LICENSE ├── README.md ├── api ├── base │ ├── Controller.js │ └── Model.js ├── controllers │ ├── .gitkeep │ ├── AuthController.js │ ├── AuthorController.js │ ├── BookController.js │ ├── MessageController.js │ ├── UserController.js │ └── UserLoginController.js ├── hooks │ └── load-db.js ├── models │ ├── .gitkeep │ ├── Author.js │ ├── Book.js │ ├── Message.js │ ├── Passport.js │ ├── RequestLog.js │ ├── User.js │ └── UserLogin.js ├── policies │ ├── Passport.js │ ├── addDataCreate.js │ ├── addDataUpdate.js │ ├── authenticated.js │ └── isAdmin.js ├── responses │ ├── badRequest.js │ ├── created.js │ ├── forbidden.js │ ├── notFound.js │ ├── ok.js │ └── serverError.js └── services │ ├── Date.js │ ├── Logger.js │ ├── Passport.js │ ├── Token.js │ └── protocols │ ├── index.js │ ├── local.js │ ├── oauth.js │ ├── oauth2.js │ └── openid.js ├── app.js ├── config ├── apianalytics.js ├── application.js ├── blueprints.js ├── bootstrap.js ├── connections.js ├── cors.js ├── csrf.js ├── env │ ├── development.js │ └── production.js ├── globals.js ├── http.js ├── i18n.js ├── jwt.js ├── local_example.js ├── locales │ ├── _README.md │ └── en.json ├── log.js ├── models.js ├── passport.js ├── policies.js ├── routes.js ├── session.js ├── sockets.js └── views.js ├── dummy.js ├── package.json └── test ├── bootstrap.test.js ├── fixtures ├── Author.json ├── Book.json ├── Message.json ├── Passport.json ├── User.json └── UserLogin.json ├── functional ├── common │ └── controller.test.js └── controllers │ └── AuthController.test.js ├── helpers ├── README.MD └── login.js └── mocha.opts /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################ 2 | ############### .gitignore ################## 3 | ################################################ 4 | # 5 | # This file is only relevant if you are using git. 6 | # 7 | # Files which match the splat patterns below will 8 | # be ignored by git. This keeps random crap and 9 | # and sensitive credentials from being uploaded to 10 | # your repository. It allows you to configure your 11 | # app for your machine without accidentally 12 | # committing settings which will smash the local 13 | # settings of other developers on your team. 14 | # 15 | # Some reasonable defaults are included below, 16 | # but, of course, you should modify/extend/prune 17 | # to fit your needs! 18 | ################################################ 19 | 20 | 21 | 22 | 23 | ################################################ 24 | # Local Configuration 25 | # 26 | # Explicitly ignore files which contain: 27 | # 28 | # 1. Sensitive information you'd rather not push to 29 | # your git repository. 30 | # e.g., your personal API keys or passwords. 31 | # 32 | # 2. Environment-specific configuration 33 | # Basically, anything that would be annoying 34 | # to have to change every time you do a 35 | # `git pull` 36 | # e.g., your local development database, or 37 | # the S3 bucket you're using for file uploads 38 | # development. 39 | # 40 | ################################################ 41 | 42 | config/local.js 43 | 44 | 45 | 46 | 47 | 48 | ################################################ 49 | # Dependencies 50 | # 51 | # When releasing a production app, you may 52 | # consider including your node_modules and 53 | # bower_components directory in your git repo, 54 | # but during development, its best to exclude it, 55 | # since different developers may be working on 56 | # different kernels, where dependencies would 57 | # need to be recompiled anyway. 58 | # 59 | # More on that here about node_modules dir: 60 | # http://www.futurealoof.com/posts/nodemodules-in-git.html 61 | # (credit Mikeal Rogers, @mikeal) 62 | # 63 | # About bower_components dir, you can see this: 64 | # http://addyosmani.com/blog/checking-in-front-end-dependencies/ 65 | # (credit Addy Osmani, @addyosmani) 66 | # 67 | ################################################ 68 | 69 | node_modules 70 | bower_components 71 | 72 | 73 | 74 | 75 | 76 | ################################################ 77 | # Sails.js / Waterline / Grunt 78 | # 79 | # Files generated by Sails and Grunt. 80 | ################################################ 81 | .tmp 82 | 83 | 84 | 85 | 86 | 87 | ################################################ 88 | # Node.js / NPM 89 | # 90 | # Common files generated by Node, NPM, and the 91 | # related ecosystem. 92 | ################################################ 93 | lib-cov 94 | *.seed 95 | *.log 96 | *.out 97 | *.pid 98 | npm-debug.log 99 | 100 | 101 | 102 | 103 | 104 | ################################################ 105 | # Miscellaneous 106 | # 107 | # Common files generated by text editors, 108 | # operating systems, file systems, etc. 109 | ################################################ 110 | 111 | *~ 112 | *# 113 | .DS_STORE 114 | .netbeans 115 | nbproject 116 | .idea 117 | .node_history 118 | -------------------------------------------------------------------------------- /.sailsrc: -------------------------------------------------------------------------------- 1 | { 2 | "generators": { 3 | "modules": {} 4 | }, 5 | "hooks": { 6 | "grunt": false, 7 | "session": false, 8 | "csrf": false 9 | } 10 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "0.10" 5 | - "0.11" 6 | - "0.12" 7 | - "4.0" 8 | - "4.1" 9 | - "4.2" 10 | - "5.0" 11 | 12 | services: 13 | - mysql 14 | 15 | before_script: 16 | 17 | notifications: 18 | email: true 19 | 20 | # whitelisted branches 21 | branches: 22 | only: 23 | - master -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright (c) 2015 Tarmo Leppänen 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Backend for angular-sailsjs-boilerplate 2 | [![GitHub version](https://badge.fury.io/gh/tarlepp%2Fangular-sailsjs-boilerplate-backend.svg)](https://badge.fury.io/gh/tarlepp%2Fangular-sailsjs-boilerplate-backend) 3 | [![Build Status](https://travis-ci.org/tarlepp/angular-sailsjs-boilerplate-backend.png?branch=master)](https://travis-ci.org/tarlepp/angular-sailsjs-boilerplate-backend) 4 | [![Dependency Status](https://david-dm.org/tarlepp/angular-sailsjs-boilerplate-backend.svg)](https://david-dm.org/tarlepp/angular-sailsjs-boilerplate-backend) 5 | [![devDependency Status](https://david-dm.org/tarlepp/angular-sailsjs-boilerplate-backend/dev-status.svg)](https://david-dm.org/tarlepp/angular-sailsjs-boilerplate-backend#info=devDependencies) 6 | 7 | Backend is a [Sails.js](http://sailsjs.org) application without frontend. See more info at http://sailsjs.org/ I have 8 | just done some small tweaks to generic workflow of sails nothing else. Basically this only serves an API and 9 | user authentication services - nothing else. So the main difference withing sails normal workflow is that sails isn't 10 | serving any "views", backend serves only JSON and nothing else. 11 | 12 | This backend code is part of [angular-sailsjs-boilerplate](https://github.com/tarlepp/angular-sailsjs-boilerplate) project. 13 | 14 | ## Installation 15 | First of all you have to install npm, node.js / io.js and sails to your box. Installation instructions can be 16 | found [here](http://sailsjs.org/get-started). 17 | 18 | After that make sure that you have all necessary components installed by running following commands in your shell: 19 | 20 | ``` 21 | npm --version 22 | node --version 23 | sails --version 24 | ``` 25 | 26 | And after that you can run actual backend install by following command in source root folder: 27 | 28 | ``` 29 | npm install 30 | ``` 31 | 32 | ## Configuration 33 | Backend needs some configurations before you can actually run it properly. Although you can skip this section if you 34 | want to, in this case sails will use its defaults to run application. 35 | 36 | There is an example of backend configuration file on following path: 37 | 38 | ``` 39 | /config/local_example.js 40 | ``` 41 | 42 | Just copy this file to ```/config/local.js``` and make necessary changes to it. Note that this ```local.js``` file is 43 | in ```.gitignore``` so it won't go to VCS at any point. 44 | 45 | ## Application start 46 | You can start this backend application as the same way as any sails / node application. This can be done by following 47 | commands: 48 | 49 | ``` 50 | sails lift 51 | ``` 52 | OR 53 | ``` 54 | node app.js 55 | ``` 56 | 57 | This will start sails.js server on defined port. By default this is accessible from http://localhost:1337 url. If you 58 | try that with your browser you should only see page that contains ```Not Found message``` on it. This means that 59 | everything is ok. 60 | 61 | ## Possible failures 62 | Below is small list of possible failures that can occur while trying this. 63 | 64 |
    65 |
  1. Sails won't lift and you get error message like: Fatal error: watch ENOSPC 66 | 70 |
  2. 71 |
72 | 73 | And if _you_ have some problems, please add solutions to this list... 74 | 75 | ## Author 76 | Tarmo Leppänen 77 | 78 | ## License 79 | The MIT License (MIT) 80 | 81 | Copyright (c) 2015 Tarmo Leppänen 82 | -------------------------------------------------------------------------------- /api/base/Controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var actionUtil = require('sails/lib/hooks/blueprints/actionUtil'); 4 | 5 | /** 6 | * BaseController.js 7 | * 8 | * Base controller for all sails.js controllers. This just contains some common code 9 | * that every controller uses 10 | */ 11 | module.exports = { 12 | /** 13 | * Generic count action for controller. 14 | * 15 | * @param {Request} request 16 | * @param {Response} response 17 | */ 18 | count: function count(request, response) { 19 | var Model = actionUtil.parseModel(request); 20 | 21 | Model 22 | .count(actionUtil.parseCriteria(request)) 23 | .exec(function found(error, count) { 24 | if (error) { 25 | response.negotiate(error); 26 | } else { 27 | response.ok({count: count}); 28 | } 29 | }) 30 | ; 31 | } 32 | }; -------------------------------------------------------------------------------- /api/base/Model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * api/base/model.js 5 | * 6 | * Base model for all sails.js models. This just contains some common code that every "nearly" every model uses. 7 | */ 8 | module.exports = { 9 | schema: true, 10 | 11 | attributes: { 12 | // Relation to User object via created user id 13 | createdUser: { 14 | model: 'User', 15 | columnName: 'createdUserId', 16 | defaultsTo: null 17 | }, 18 | // Relation to User object via updated user id 19 | updatedUser: { 20 | model: 'User', 21 | columnName: 'updatedUserId', 22 | defaultsTo: null 23 | }, 24 | 25 | // Dynamic model data attributes 26 | 27 | // Created timestamp as moment object 28 | createdAtObject: function() { 29 | return (this.createdAt && this.createdAt != '0000-00-00 00:00:00') 30 | ? sails.services['date'].convertDateObjectToUtc(this.createdAt) : null; 31 | }, 32 | // Updated timestamp as moment object 33 | updatedAtObject: function() { 34 | return (this.updatedAt && this.updatedAt != '0000-00-00 00:00:00') 35 | ? sails.services['date'].convertDateObjectToUtc(this.updatedAt) : null; 36 | } 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /api/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarlepp/angular-sailsjs-boilerplate-backend/84874d4df870dceb81b62a2b8dc0e6f3b46bfa2b/api/controllers/.gitkeep -------------------------------------------------------------------------------- /api/controllers/AuthController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var async = require('async'); 4 | var _ = require('lodash'); 5 | 6 | /** 7 | * Authentication Controller 8 | * 9 | * This is merely meant as an example of how your Authentication controller should look. It currently 10 | * includes the minimum amount of functionality for the basics of Passport.js to work. 11 | */ 12 | var AuthController = { 13 | /** 14 | * Log out a user and return them to the homepage 15 | * 16 | * Passport exposes a logout() function on request (also aliased as logOut()) that can be 17 | * called from any route handler which needs to terminate a login session. Invoking logout() 18 | * will remove the request.user property and clear the login session (if any). 19 | * 20 | * For more information on logging out users in Passport.js, check out: 21 | * http://passportjs.org/guide/logout/ 22 | * 23 | * @param {Request} request Request object 24 | * @param {Response} response Response object 25 | */ 26 | logout: function logout(request, response) { 27 | request.logout(); 28 | 29 | response.json(200, true); 30 | }, 31 | 32 | /** 33 | * Create a third-party authentication endpoint 34 | * 35 | * @param {Request} request Request object 36 | * @param {Response} response Response object 37 | */ 38 | provider: function provider(request, response) { 39 | sails.services['passport'].endpoint(request, response); 40 | }, 41 | 42 | /** 43 | * Simple action to check current auth status of user. Note that this will always send 44 | * HTTP status 200 and actual data will contain either user object or boolean false in 45 | * cases that user is not authenticated. 46 | * 47 | * @todo Hmmm, I think that this will return always false, because of missing of 48 | * actual sessions here... 49 | * 50 | * @param {Request} request Request object 51 | * @param {Response} response Response object 52 | */ 53 | authenticated: function authenticated(request, response) { 54 | if (request.isAuthenticated()) { 55 | response.json(200, request.user); 56 | } else { 57 | response.json(200, false); 58 | } 59 | }, 60 | 61 | /** 62 | * Create a authentication callback endpoint 63 | * 64 | * This endpoint handles everything related to creating and verifying Passports 65 | * and users, both locally and from third-party providers. 66 | * 67 | * Passport exposes a login() function on request (also aliased as logIn()) that 68 | * can be used to establish a login session. When the login operation completes, 69 | * user will be assigned to request.user. 70 | * 71 | * For more information on logging in users in Passport.js, check out: 72 | * http://passportjs.org/guide/login/ 73 | * 74 | * @param {Request} request Request object 75 | * @param {Response} response Response object 76 | */ 77 | callback: function callback(request, response) { 78 | sails.services['passport'].callback(request, response, function callback(error, user) { 79 | request.login(user, function callback(error) { 80 | // If an error was thrown, redirect the user to the login which should 81 | // take care of rendering the error messages. 82 | if (error) { 83 | sails.log.verbose('User authentication failed'); 84 | sails.log.verbose(error); 85 | 86 | response.json(401, error); 87 | } else { // Upon successful login, send back user data and JWT token 88 | sails.services['logger'].login(user, request); 89 | 90 | response.json(200, { 91 | user: user, 92 | token: sails.services['token'].issue(_.isObject(user.id) ? JSON.stringify(user.id) : user.id) 93 | }); 94 | } 95 | }); 96 | }); 97 | }, 98 | 99 | /** 100 | * Action to check if given password is same as current user password. Note that 101 | * this action is only allowed authenticated users. And by default given password 102 | * is checked against to current user. 103 | * 104 | * @param {Request} request Request object 105 | * @param {Response} response Response object 106 | */ 107 | checkPassword: function checkPassword(request, response) { 108 | /** 109 | * Job to fetch current user local passport data. This is needed 110 | * to validate given password. 111 | * 112 | * @param {Function} next Callback function 113 | */ 114 | var findPassport = function findPassport(next) { 115 | var where = { 116 | user: request.token, 117 | protocol: 'local' 118 | }; 119 | 120 | sails.models['passport'] 121 | .findOne(where) 122 | .exec(function callback(error, passport) { 123 | if (error) { 124 | next(error); 125 | } else if (!passport) { 126 | next({message: 'Given authorization token is not valid'}); 127 | } else { 128 | next(null, passport); 129 | } 130 | }) 131 | ; 132 | }; 133 | 134 | /** 135 | * Job to validate given password against user passport object. 136 | * 137 | * @param {sails.model.passport} passport Passport object 138 | * @param {Function} next Callback function 139 | */ 140 | var validatePassword = function validatePassword(passport, next) { 141 | var password = request.param('password'); 142 | 143 | passport.validatePassword(password, function callback(error, matched) { 144 | if (error) { 145 | next({message: 'Invalid password'}); 146 | } else { 147 | next(null, matched); 148 | } 149 | }); 150 | }; 151 | 152 | /** 153 | * Main callback function which is called when all specified jobs are 154 | * processed or an error has occurred while processing. 155 | * 156 | * @param {null|Error} error Possible error 157 | * @param {null|boolean} result If passport was valid or not 158 | */ 159 | var callback = function callback(error, result) { 160 | if (error) { 161 | response.json(401, error); 162 | } else if (result) { 163 | response.json(200, result); 164 | } else { 165 | response.json(400, {message: 'Given password does not match.'}); 166 | } 167 | }; 168 | 169 | // Run necessary tasks and handle results 170 | async.waterfall([findPassport, validatePassword], callback); 171 | } 172 | }; 173 | 174 | module.exports = AuthController; 175 | -------------------------------------------------------------------------------- /api/controllers/AuthorController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | /** 6 | * AuthorController 7 | * 8 | * @description :: Server-side logic for managing Authors 9 | * @help :: See http://links.sailsjs.org/docs/controllers 10 | */ 11 | module.exports = _.merge(_.cloneDeep(require('../base/Controller')), { 12 | }); 13 | -------------------------------------------------------------------------------- /api/controllers/BookController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | /** 6 | * BookController 7 | * 8 | * @description :: Server-side logic for managing Books 9 | * @help :: See http://links.sailsjs.org/docs/controllers 10 | */ 11 | module.exports = _.merge(_.cloneDeep(require('../base/Controller')), { 12 | }); 13 | -------------------------------------------------------------------------------- /api/controllers/MessageController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | /** 6 | * MessageController 7 | * 8 | * @description :: Server-side logic for managing messages 9 | * @help :: See http://links.sailsjs.org/docs/controllers 10 | */ 11 | module.exports = _.merge(_.cloneDeep(require('../base/Controller')), { 12 | }); 13 | -------------------------------------------------------------------------------- /api/controllers/UserController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | /** 6 | * UserController 7 | * 8 | * @description :: Server-side logic for managing Users 9 | * @help :: See http://links.sailsjs.org/docs/controllers 10 | */ 11 | module.exports = _.merge(_.cloneDeep(require('../base/Controller')), { 12 | }); 13 | -------------------------------------------------------------------------------- /api/controllers/UserLoginController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var Promise = require('bluebird'); 5 | 6 | /** 7 | * UserLoginController 8 | * 9 | * @description :: Server-side logic for managing user login history data 10 | * @help :: See http://links.sailsjs.org/docs/controllers 11 | */ 12 | module.exports = _.merge(_.cloneDeep(require('../base/Controller')), { 13 | 'statistics': function statistics(request, response) { 14 | var type = request.param('type'); 15 | var groupBy; 16 | 17 | switch (type) { 18 | case 'Browser': 19 | groupBy = 'browser'; 20 | break; 21 | case 'OS': 22 | groupBy = 'os'; 23 | break; 24 | case 'User': 25 | default: 26 | groupBy = 'userId'; 27 | break; 28 | } 29 | 30 | /** 31 | * Helper function to fetch main statistics data. 32 | * 33 | * @returns {Promise} 34 | */ 35 | var fetchStatistics = function fetchStatistics() { 36 | return sails.models['userlogin'] 37 | .find().sum('count') 38 | .groupBy(groupBy) 39 | ; 40 | }; 41 | 42 | /** 43 | * Helper function to fetch user data. Note that this is only applied if action is 'User' 44 | * 45 | * @returns {Promise|Array} 46 | */ 47 | var fetchUsers = function fetchUsers() { 48 | return (groupBy === 'userId') ? sails.models.user.find() : []; 49 | }; 50 | 51 | /** 52 | * Helper function to format current statistics data for high charts 53 | * 54 | * @param {{ 55 | * stats: {}[], 56 | * users: {}[] 57 | * }} data 58 | * @returns {Array} 59 | */ 60 | var formatData = function formatData(data) { 61 | return _.map(data.stats, function iterator(item) { 62 | return [ 63 | (groupBy === 'userId') ? _findUser(item['user']) : item[groupBy], 64 | item.count 65 | ]; 66 | }); 67 | 68 | /** 69 | * Helper function to find user name. 70 | * 71 | * @param {number} userId 72 | * @returns {string} 73 | * @private 74 | */ 75 | function _findUser(userId) { 76 | var user = _.find(data.users, function iterator(user) { 77 | return user.id === userId; 78 | }); 79 | 80 | return user ? user.lastName + ', ' + user.firstName + ' (' + user.username + ')' : 'Unknown user'; 81 | } 82 | }; 83 | 84 | /** 85 | * Generic success handler which is triggered when all jobs are done and data is ready to sent to client. 86 | * 87 | * @param {Array} data Data array to send to client 88 | */ 89 | var handlerSuccess = function handlerSuccess(data) { 90 | response.ok(data); 91 | }; 92 | 93 | /** 94 | * Generic error handler which is triggered whenever some error happens in jobs. 95 | * 96 | * @param {*} error Error object 97 | */ 98 | var handlerError = function handlerError(error) { 99 | response.negotiate(error); 100 | }; 101 | 102 | // Fetch data and do the all necessary tricks :D 103 | Promise 104 | .props({ 105 | stats: fetchStatistics(), 106 | users: fetchUsers() 107 | }) 108 | .then(formatData) 109 | .then(handlerSuccess) 110 | .catch(handlerError) 111 | ; 112 | } 113 | }); 114 | -------------------------------------------------------------------------------- /api/hooks/load-db.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | /** 6 | * load-db.js 7 | * 8 | * This file contains a custom hook, that will be run after sails.js orm hook is loaded. Purpose of this hook is to 9 | * check that database contains necessary initial data for application. 10 | */ 11 | module.exports = function hook(sails) { 12 | return { 13 | /** 14 | * Private hook method to do actual database data population. Note that fixture data are only loaded if there 15 | * isn't any users in current database. 16 | * 17 | * @param {Function} next Callback function to call after all is done 18 | */ 19 | process: function process(next) { 20 | sails.models.user 21 | .find() 22 | .exec(function callback(error, users) { 23 | if (error) { 24 | next(error); 25 | } else if (users.length !== 0 && JSON.stringify(users[0]) !== '{}') { 26 | next(); 27 | } else { 28 | sails.log.verbose(__filename + ':' + __line + ' [Hook.load-db] Populating database with fixture data...'); 29 | 30 | var _ = require('lodash'); 31 | var Barrels = require('barrels'); 32 | var barrels = new Barrels(); 33 | var fixtures = _.keys(barrels.data); 34 | 35 | barrels.populate(['user'], function(error) { 36 | if (error) { 37 | next(error); 38 | } 39 | 40 | fixtures = _.without(fixtures, 'user'); 41 | 42 | barrels.populate(fixtures, next, false); 43 | }, false); 44 | } 45 | }) 46 | ; 47 | }, 48 | 49 | /** 50 | * Method that runs automatically when the hook initializes itself. 51 | * 52 | * @param {Function} next Callback function to call after all is done 53 | */ 54 | initialize: function initialize(next) { 55 | var self = this; 56 | 57 | // Wait for sails orm hook to be loaded 58 | sails.after('hook:orm:loaded', function onAfter() { 59 | self.process(next); 60 | }); 61 | } 62 | }; 63 | }; 64 | -------------------------------------------------------------------------------- /api/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarlepp/angular-sailsjs-boilerplate-backend/84874d4df870dceb81b62a2b8dc0e6f3b46bfa2b/api/models/.gitkeep -------------------------------------------------------------------------------- /api/models/Author.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | /** 6 | * Author.js 7 | * 8 | * @description :: TODO: You might write a short summary of how this model works and what it represents here. 9 | * @docs :: http://sailsjs.org/#!documentation/models 10 | */ 11 | module.exports = _.merge(_.cloneDeep(require('../base/Model')), { 12 | attributes: { 13 | // Name of the author 14 | name: { 15 | type: 'string', 16 | required: true 17 | }, 18 | // Author description 19 | description: { 20 | type: 'text' 21 | }, 22 | 23 | // Below is all specification for relations to another models 24 | 25 | // Books that area attached to author 26 | books: { 27 | collection: 'book', 28 | via: 'author' 29 | } 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /api/models/Book.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | /** 6 | * Book.js 7 | * 8 | * @description :: TODO: You might write a short summary of how this model works and what it represents here. 9 | * @docs :: http://sailsjs.org/#!documentation/models 10 | */ 11 | module.exports = _.merge(_.cloneDeep(require('../base/Model')), { 12 | attributes: { 13 | // Book title 14 | title: { 15 | type: 'string', 16 | required: true 17 | }, 18 | // Book description 19 | description: { 20 | type: 'text', 21 | defaultsTo: '' 22 | }, 23 | // Book release date 24 | releaseDate: { 25 | type: 'date', 26 | required: true 27 | }, 28 | 29 | // Below is all specification for relations to another models 30 | 31 | // Author of the book 32 | author: { 33 | model: 'author' 34 | } 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /api/models/Message.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Message.js 5 | * 6 | * @description :: TODO: You might write a short summary of how this model works and what it represents here. 7 | * @docs :: http://sailsjs.org/#!documentation/models 8 | */ 9 | module.exports = { 10 | schema: true, 11 | 12 | attributes: { 13 | nick: { 14 | type: 'text', 15 | required: true 16 | }, 17 | message: { 18 | type: 'text', 19 | required: true 20 | }, 21 | 22 | // Below is all specification for relations to another models 23 | 24 | // User object that is attached to message 25 | user: { 26 | model: 'user' 27 | } 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /api/models/Passport.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var bcrypt = require('bcryptjs'); 4 | 5 | /** 6 | * Passport Model 7 | * 8 | * The Passport model handles associating authenticators with users. An authenticator can 9 | * be either local (password) or third-party (provider). A single user can have multiple 10 | * passports, allowing them to connect and use several third-party strategies in optional 11 | * conjunction with a password. 12 | * 13 | * Since an application will only need to authenticate a user once per session, 14 | * it makes sense to encapsulate the data specific to the authentication process 15 | * in a model of its own. This allows us to keep the session itself as light- 16 | * weight as possible as the application only needs to serialize and deserialize 17 | * the user, but not the authentication data, to and from the session. 18 | */ 19 | var Passport = { 20 | schema: true, 21 | 22 | attributes: { 23 | /** 24 | * Required field: Protocol 25 | * 26 | * Defines the protocol to use for the passport. When employing the local 27 | * strategy, the protocol will be set to 'local'. When using a third-party 28 | * strategy, the protocol will be set to the standard used by the third- 29 | * party service (e.g. 'oauth', 'oauth2', 'openid'). 30 | */ 31 | protocol: { 32 | type: 'alphanumeric', 33 | required: true 34 | }, 35 | 36 | /** 37 | * Local field: Password 38 | * 39 | * When the local strategy is employed, a password will be used as the 40 | * means of authentication along with either a username or an email. 41 | */ 42 | password: { 43 | type: 'string', 44 | minLength: 8 45 | }, 46 | 47 | /** 48 | * Provider fields: Provider, identifier and tokens 49 | * 50 | * "provider" is the name of the third-party auth service in all lowercase 51 | * (e.g. 'github', 'facebook') whereas "identifier" is a provider-specific 52 | * key, typically an ID. These two fields are used as the main means of 53 | * identifying a passport and tying it to a local user. 54 | * 55 | * The "tokens" field is a JSON object used in the case of the OAuth stan- 56 | * dards. When using OAuth 1.0, a `token` as well as a `tokenSecret` will 57 | * be issued by the provider. In the case of OAuth 2.0, an `accessToken` 58 | * and a `refreshToken` will be issued. 59 | */ 60 | provider: { 61 | type: 'alphanumericdashed' 62 | }, 63 | 64 | identifier: { 65 | type: 'string' 66 | }, 67 | 68 | tokens: { 69 | type: 'json' 70 | }, 71 | 72 | /** 73 | * Associations 74 | * 75 | * Associate every passport with one, and only one, user. This requires an 76 | * adapter compatible with associations. 77 | * 78 | * For more information on associations in Waterline, check out: 79 | * https://github.com/balderdashy/waterline 80 | */ 81 | user: { 82 | model: 'User' 83 | }, 84 | 85 | /** 86 | * Validate password used by the local strategy. 87 | * 88 | * @param {string} password The password to validate 89 | * @param {Function} next 90 | */ 91 | validatePassword: function validatePassword(password, next) { 92 | bcrypt.compare(password, this.password, next); 93 | } 94 | }, 95 | 96 | /** 97 | * Callback to be run before creating a Passport. 98 | * 99 | * @param {Object} passport The soon-to-be-created Passport 100 | * @param {Function} next 101 | */ 102 | beforeCreate: function beforeCreate(passport, next) { 103 | if (passport.hasOwnProperty('password')) { 104 | bcrypt.hash(passport.password, 10, function callback(error, hash) { 105 | passport.password = hash; 106 | 107 | next(error, passport); 108 | }); 109 | } else { 110 | next(null, passport); 111 | } 112 | }, 113 | 114 | /** 115 | * Callback to be run before updating a Passport. 116 | * 117 | * @param {Object} passport Values to be updated 118 | * @param {Function} next 119 | */ 120 | beforeUpdate: function beforeUpdate(passport, next) { 121 | if (passport.hasOwnProperty('password')) { 122 | bcrypt.hash(passport.password, 10, function callback(error, hash) { 123 | passport.password = hash; 124 | 125 | next(error, passport); 126 | }); 127 | } else { 128 | next(null, passport); 129 | } 130 | } 131 | }; 132 | 133 | module.exports = Passport; 134 | -------------------------------------------------------------------------------- /api/models/RequestLog.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * RequestLog.js 5 | * 6 | * @description :: TODO: You might write a short summary of how this model works and what it represents here. 7 | * @docs :: http://sailsjs.org/#!documentation/models 8 | */ 9 | module.exports = { 10 | schema: true, 11 | 12 | attributes: { 13 | // Request method 14 | method: { 15 | type: 'string', 16 | required: true 17 | }, 18 | // Request URL 19 | url: { 20 | type: 'string', 21 | required: true 22 | }, 23 | // Request headers 24 | headers: { 25 | type: 'json' 26 | }, 27 | // Used parameters 28 | parameters: { 29 | type: 'json' 30 | }, 31 | // Request query 32 | query: { 33 | type: 'json' 34 | }, 35 | // Request body 36 | body: { 37 | type: 'json' 38 | }, 39 | // Request protocol 40 | protocol: { 41 | type: 'string' 42 | }, 43 | // Request IP address 44 | ip: { 45 | type: 'string' 46 | }, 47 | // Request response time 48 | responseTime: { 49 | type: 'integer' 50 | }, 51 | // Middleware latency 52 | middlewareLatency: { 53 | type: 'integer' 54 | }, 55 | 56 | // Below is all specification for relations to another models 57 | 58 | // User object 59 | user: { 60 | model: 'User', 61 | columnName: 'userId', 62 | required: true 63 | } 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /api/models/User.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | /** 6 | * User.js 7 | * 8 | * @description :: TODO: You might write a short summary of how this model works and what it represents here. 9 | * @docs :: http://sailsjs.org/#!documentation/models 10 | */ 11 | module.exports = _.merge(_.cloneDeep(require('../base/Model')), { 12 | attributes: { 13 | username: { 14 | type: 'string', 15 | unique: true 16 | }, 17 | email: { 18 | type: 'email', 19 | unique: true 20 | }, 21 | firstName: { 22 | type: 'string', 23 | required: true 24 | }, 25 | lastName: { 26 | type: 'string', 27 | required: true 28 | }, 29 | admin: { 30 | type: 'boolean', 31 | defaultsTo: false 32 | }, 33 | 34 | // Below is all specification for relations to another models 35 | 36 | // Passport configurations 37 | passports: { 38 | collection: 'Passport', 39 | via: 'user' 40 | }, 41 | // Message objects that user has sent 42 | messages: { 43 | collection: 'Message', 44 | via: 'user' 45 | }, 46 | // Login objects that are attached to user 47 | logins: { 48 | collection: 'UserLogin', 49 | via: 'user' 50 | }, 51 | requestLogs: { 52 | collection: 'RequestLog', 53 | via: 'user' 54 | }, 55 | 56 | // Below are relations to another objects via generic 'createdUser' and 'updatedUser' properties 57 | 58 | // Authors 59 | createdAuthors: { 60 | collection: 'Author', 61 | via: 'createdUser' 62 | }, 63 | updatedAuthors: { 64 | collection: 'Author', 65 | via: 'updatedUser' 66 | }, 67 | 68 | // Books 69 | createdBooks: { 70 | collection: 'Book', 71 | via: 'createdUser' 72 | }, 73 | updatedBooks: { 74 | collection: 'Book', 75 | via: 'updatedUser' 76 | } 77 | } 78 | }); 79 | -------------------------------------------------------------------------------- /api/models/UserLogin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * UserLogin.js 5 | * 6 | * @description :: TODO: You might write a short summary of how this model works and what it represents here. 7 | * @docs :: http://sailsjs.org/#!documentation/models 8 | */ 9 | module.exports = { 10 | schema: true, 11 | 12 | attributes: { 13 | // User IP-address 14 | ip: { 15 | type: 'string', 16 | required: true 17 | }, 18 | // User hostname 19 | host: { 20 | type: 'string', 21 | required: true 22 | }, 23 | // User browser user-agent 24 | agent: { 25 | type: 'text', 26 | required: true 27 | }, 28 | // User browser 29 | browser: { 30 | type: 'string', 31 | defaultsTo: 'Unknown' 32 | }, 33 | // User browser version 34 | browserVersion: { 35 | type: 'string', 36 | defaultsTo: 'Unknown' 37 | }, 38 | // User browser family 39 | browserFamily: { 40 | type: 'string', 41 | defaultsTo: 'Unknown' 42 | }, 43 | // User operation system 44 | os: { 45 | type: 'string', 46 | defaultsTo: 'Unknown' 47 | }, 48 | // User operation system version 49 | osVersion: { 50 | type: 'string', 51 | defaultsTo: 'Unknown' 52 | }, 53 | // User operation system family 54 | osFamily: { 55 | type: 'string', 56 | defaultsTo: 'Unknown' 57 | }, 58 | // This is needed for login summary data, dummy but no other choice atm... 59 | count: { 60 | type: 'integer', 61 | defaultsTo: 1 62 | }, 63 | 64 | // Below is all specification for relations to another models 65 | 66 | // Attached User object of this UserLogin 67 | user: { 68 | model: 'User', 69 | columnName: 'userId', 70 | required: true 71 | } 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /api/policies/Passport.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var passport = require('passport'); 4 | 5 | /** 6 | * Policy for Sails that initializes Passport.js to use. 7 | * 8 | * @param {Request} request Request object 9 | * @param {Response} response Response object 10 | * @param {Function} next Callback function 11 | */ 12 | module.exports = function(request, response, next) { 13 | sails.log.verbose(__filename + ':' + __line + ' [Policy.Passport() called]'); 14 | 15 | // Initialize Passport 16 | passport.initialize()(request, response, next); 17 | }; 18 | -------------------------------------------------------------------------------- /api/policies/addDataCreate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Policy to set necessary create data to body. 5 | * 6 | * @param {Request} request Request object 7 | * @param {Response} response Response object 8 | * @param {Function} next Callback function 9 | */ 10 | module.exports = function addDataCreate(request, response, next) { 11 | sails.log.verbose(__filename + ':' + __line + ' [Policy.addDataCreate() called]'); 12 | 13 | if (request.token) { 14 | request.body.createdUser = request.token; 15 | request.body.updatedUser = request.token; 16 | 17 | next(); 18 | } else { 19 | var error = new Error(); 20 | 21 | error.message = 'Request token not present.'; 22 | error.status = 403; 23 | 24 | next(error); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /api/policies/addDataUpdate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | /** 6 | * Policy to set necessary update data to body. Note that this policy will also remove some body items. 7 | * 8 | * @param {Request} request Request object 9 | * @param {Response} response Response object 10 | * @param {Function} next Callback function 11 | */ 12 | module.exports = function addDataUpdate(request, response, next) { 13 | sails.log.verbose(__filename + ':' + __line + ' [Policy.addDataUpdate() called]'); 14 | 15 | if (request.token) { 16 | var itemsToRemove = [ 17 | 'id', 18 | 'createdUser', 19 | 'updatedUser', 20 | 'createdAt', 21 | 'updatedAt' 22 | ]; 23 | 24 | // Remove not needed attributes from body 25 | _.forEach(itemsToRemove, function iterator(item) { 26 | delete request.body[item]; 27 | }); 28 | 29 | request.body.updatedUser = request.token; 30 | 31 | next(); 32 | } else { 33 | var error = new Error(); 34 | 35 | error.message = 'Request token not present.'; 36 | error.status = 403; 37 | 38 | next(error); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /api/policies/authenticated.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | 5 | /** 6 | * Policy to check that request is done via authenticated user. This policy uses existing 7 | * JWT tokens to validate that user is authenticated. If use is not authenticate policy 8 | * sends 401 response back to client. 9 | * 10 | * @param {Request} request Request object 11 | * @param {Response} response Response object 12 | * @param {Function} next Callback function 13 | * 14 | * @returns {*} 15 | */ 16 | module.exports = function authenticated(request, response, next) { 17 | sails.log.verbose(__filename + ':' + __line + ' [Policy.Authenticated() called]'); 18 | 19 | /** 20 | * Helper function to process possible error and actual token after it is decoded. 21 | * 22 | * @param {{}} error Possible error 23 | * @param {Number} token Decoded JWT token data 24 | * @returns {*} 25 | */ 26 | var verify = function verify(error, token) { 27 | if (!(_.isEmpty(error) && token !== -1)) { 28 | return response.json(401, {message: 'Given authorization token is not valid'}); 29 | } else { 30 | // Store user id to request object 31 | request.token = token; 32 | 33 | // We delete the token from query and body to not mess with blueprints 34 | request.query && delete request.query.token; 35 | request.body && delete request.body.token; 36 | 37 | return next(); 38 | } 39 | }; 40 | 41 | // Get and verify JWT via service 42 | try { 43 | sails.services.token.getToken(request, verify, true); 44 | } catch (error) { 45 | return response.json(401, {message: error.message}); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /api/policies/isAdmin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Policy to check if current user is admin or not. 5 | * 6 | * @param {Request} request Request object 7 | * @param {Response} response Response object 8 | * @param {Function} next Callback function 9 | * 10 | * @returns {*} 11 | */ 12 | module.exports = function isAdmin(request, response, next) { 13 | sails.log.verbose(__filename + ':' + __line + ' [Policy.isAdmin() called]'); 14 | 15 | // Fetch current user by the token 16 | sails.models['user'] 17 | .findOne(request.token) 18 | .exec(function exec(error, user) { 19 | if (error) { 20 | next(error); 21 | } else if (!user) { 22 | error = new Error(); 23 | 24 | error.status = 401; 25 | error.message = 'User not found - Please login.'; 26 | 27 | next(error); 28 | } else if (user.admin) { 29 | next(); 30 | } else { 31 | error = new Error(); 32 | 33 | error.status = 403; 34 | error.message = 'Forbidden - You are not administrator user.'; 35 | 36 | next(error); 37 | } 38 | }) 39 | }; 40 | -------------------------------------------------------------------------------- /api/responses/badRequest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * 400 (Bad Request) Handler 5 | * 6 | * Usage: 7 | * return res.badRequest(); 8 | * return res.badRequest(data); 9 | * return res.badRequest(data, 'some/specific/badRequest/view'); 10 | * 11 | * e.g.: 12 | * ``` 13 | * return res.badRequest( 14 | * 'Please choose a valid `password` (6-12 characters)', 15 | * 'trial/signup' 16 | * ); 17 | * ``` 18 | * 19 | * @param {{}} data Data for response 20 | * @param {{}} options Response options 21 | * @returns {*} 22 | */ 23 | module.exports = function badRequest(data, options) { 24 | // Get access to `req`, `res`, & `sails` 25 | var request = this.req; 26 | var response = this.res; 27 | var sails = request._sails; 28 | 29 | /** 30 | * If second argument is a string, we take that to mean it refers to a view. 31 | * If it was omitted, use an empty object (`{}`) 32 | */ 33 | options = (typeof options === 'string') ? { view: options } : options || {}; 34 | 35 | // Set status code 36 | response.status(400); 37 | 38 | // Log error to console 39 | if (data !== undefined) { 40 | sails.log.verbose('Sending 400 ("Bad Request") response: \n', data); 41 | } else { 42 | sails.log.verbose('Sending 400 ("Bad Request") response'); 43 | } 44 | 45 | // Backend will always response JSON 46 | return response.jsonx(data); 47 | }; 48 | -------------------------------------------------------------------------------- /api/responses/created.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * 201 (CREATED) Response 5 | * 6 | * Usage: 7 | * return res.created(); 8 | * return res.created(data); 9 | * return res.created(data, 'auth/login'); 10 | * 11 | * @param {{}} data Data for response 12 | * @param {{}} options Response options 13 | * @returns {*} 14 | */ 15 | module.exports = function created(data, options) { 16 | // Get access to `req`, `res`, & `sails` 17 | var request = this.req; 18 | var response = this.res; 19 | var sails = request._sails; 20 | 21 | /** 22 | * If second argument is a string, we take that to mean it refers to a view. 23 | * If it was omitted, use an empty object (`{}`) 24 | */ 25 | options = (typeof options === 'string') ? {view: options} : options || {}; 26 | 27 | sails.log.silly('res.created() :: Sending 201 ("CREATED") response'); 28 | 29 | // Set status code 30 | response.status(201); 31 | 32 | // Backend will always response JSON 33 | return response.jsonx(data); 34 | }; 35 | -------------------------------------------------------------------------------- /api/responses/forbidden.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * 403 (Forbidden) Handler 5 | * 6 | * Usage: 7 | * return res.forbidden(); 8 | * return res.forbidden(err); 9 | * return res.forbidden(err, 'some/specific/forbidden/view'); 10 | * 11 | * e.g.: 12 | * ``` 13 | * return res.forbidden('Access denied.'); 14 | * ``` 15 | * 16 | * @param {{}} data Data for response 17 | * @param {{}} options Response options 18 | * @returns {*} 19 | */ 20 | module.exports = function forbidden(data, options) { 21 | // Get access to `req`, `res`, & `sails` 22 | var request = this.req; 23 | var response = this.res; 24 | var sails = request._sails; 25 | 26 | /** 27 | * If second argument is a string, we take that to mean it refers to a view. 28 | * If it was omitted, use an empty object (`{}`) 29 | */ 30 | options = (typeof options === 'string') ? { view: options } : options || {}; 31 | 32 | // Set status code 33 | response.status(403); 34 | 35 | // Log error to console 36 | if (data !== undefined) { 37 | sails.log.verbose('Sending 403 ("Forbidden") response: \n', data); 38 | } else { 39 | sails.log.verbose('Sending 403 ("Forbidden") response'); 40 | } 41 | 42 | // Backend will always response JSON 43 | return response.jsonx(data); 44 | }; 45 | -------------------------------------------------------------------------------- /api/responses/notFound.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * 404 (Not Found) Handler 5 | * 6 | * Usage: 7 | * return res.notFound(); 8 | * return res.notFound(err); 9 | * return res.notFound(err, 'some/specific/notfound/view'); 10 | * 11 | * e.g.: 12 | * ``` 13 | * return res.notFound(); 14 | * ``` 15 | * 16 | * NOTE: 17 | * If a request doesn't match any explicit routes (i.e. `config/routes.js`) 18 | * or route blueprints (i.e. "shadow routes", Sails will call `res.notFound()` 19 | * automatically. 20 | * 21 | * @param {{}} data Data for response 22 | * @param {{}} options Response options 23 | * @returns {*} 24 | */ 25 | module.exports = function notFound(data, options) { 26 | // Get access to `req`, `res`, & `sails` 27 | var request = this.req; 28 | var response = this.res; 29 | var sails = request._sails; 30 | 31 | /** 32 | * If second argument is a string, we take that to mean it refers to a view. 33 | * If it was omitted, use an empty object (`{}`) 34 | */ 35 | options = (typeof options === 'string') ? {view: options} : options || {}; 36 | 37 | // Set status code 38 | response.status(404); 39 | 40 | // Log error to console 41 | if (data !== undefined) { 42 | sails.log.verbose('Sending 404 ("Not Found") response: \n', data); 43 | } else { 44 | sails.log.verbose('Sending 404 ("Not Found") response'); 45 | } 46 | 47 | // Backend will always response JSON 48 | return response.jsonx(data); 49 | }; 50 | -------------------------------------------------------------------------------- /api/responses/ok.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * 200 (OK) Response 5 | * 6 | * Usage: 7 | * return res.ok(); 8 | * return res.ok(data); 9 | * return res.ok(data, 'auth/login'); 10 | * 11 | * @param {{}} data Data for response 12 | * @param {{}} options Response options 13 | * @returns {*} 14 | */ 15 | module.exports = function sendOK(data, options) { 16 | // Get access to `req`, `res`, & `sails` 17 | var request = this.req; 18 | var response = this.res; 19 | var sails = request._sails; 20 | 21 | /** 22 | * If second argument is a string, we take that to mean it refers to a view. 23 | * If it was omitted, use an empty object (`{}`) 24 | */ 25 | options = (typeof options === 'string') ? {view: options} : options || {}; 26 | 27 | sails.log.silly('res.ok() :: Sending 200 ("OK") response'); 28 | 29 | // Set status code 30 | response.status(200); 31 | 32 | // Backend will always response JSON 33 | return response.jsonx(data); 34 | }; 35 | -------------------------------------------------------------------------------- /api/responses/serverError.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * 500 (Server Error) Response 5 | * 6 | * Usage: 7 | * return res.serverError(); 8 | * return res.serverError(err); 9 | * return res.serverError(err, 'some/specific/error/view'); 10 | * 11 | * NOTE: 12 | * If something throws in a policy or controller, or an internal 13 | * error is encountered, Sails will call `res.serverError()` 14 | * automatically. 15 | * 16 | * @param {{}} data Data for response 17 | * @param {{}} options Response options 18 | * @returns {*} 19 | */ 20 | module.exports = function serverError(data, options) { 21 | // Get access to `req`, `res`, & `sails` 22 | var req = this.req; 23 | var res = this.res; 24 | var sails = req._sails; 25 | 26 | /** 27 | * If second argument is a string, we take that to mean it refers to a view. 28 | * If it was omitted, use an empty object (`{}`) 29 | */ 30 | options = (typeof options === 'string') ? {view: options} : options || {}; 31 | 32 | // Set status code 33 | res.status(500); 34 | 35 | // Log error to console 36 | if (data !== undefined) { 37 | sails.log.error('Sending 500 ("Server Error") response: \n', data); 38 | } else { 39 | sails.log.error('Sending empty 500 ("Server Error") response'); 40 | } 41 | 42 | // Backend will always response JSON 43 | return res.jsonx(data); 44 | }; 45 | -------------------------------------------------------------------------------- /api/services/Date.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * /api/services/Date.js 5 | * 6 | * Generic date service which contains helper methods for date and time handling. 7 | */ 8 | 9 | // Module dependencies 10 | var moment = require('moment-timezone'); 11 | var fs = require('fs'); 12 | var _ = require('lodash'); 13 | 14 | /** 15 | * Method converts given date object to real UTC date (actually moment) object. 16 | * 17 | * @param {Date} date Date object to convert 18 | * 19 | * @returns {moment} 20 | */ 21 | exports.convertDateObjectToUtc = function convertDateObjectToUtc(date) { 22 | return moment(date).tz('Etc/Universal'); 23 | }; 24 | 25 | /** 26 | * Helper method to return current time as an UTC time. 27 | * 28 | * @returns {Date} 29 | */ 30 | exports.getCurrentDateAsUtc = function getCurrentDateAsUtc() { 31 | var now = new Date(); 32 | 33 | return new Date( 34 | now.getUTCFullYear(), 35 | now.getUTCMonth(), 36 | now.getUTCDate(), 37 | now.getUTCHours(), 38 | now.getUTCMinutes(), 39 | now.getUTCSeconds() 40 | ); 41 | }; 42 | 43 | /** 44 | * Method returns available timezones. Data is read from moment-timezone npm package and parsed to array of objects 45 | * which can be used easily everywhere in application. 46 | * 47 | * @returns {[]} Array of timezone data 48 | */ 49 | exports.getTimezones = function getTimezones() { 50 | var timezoneData = JSON.parse(fs.readFileSync('node_modules/moment-timezone/data/unpacked/latest.json', 'utf8')); 51 | var timezones = []; 52 | 53 | _.forEach(timezoneData.zones, function iterator(value, key) { 54 | timezones.push({ 55 | id: key, 56 | name: value 57 | }); 58 | }); 59 | 60 | return _.uniq(_.sortBy(timezones, 'name'), false, function iterator(timezone) { 61 | return timezone.name; 62 | }); 63 | }; 64 | -------------------------------------------------------------------------------- /api/services/Logger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Generic logger service which backend uses. Currently this service contains following methods: 5 | * login(user, request) 6 | * request(log, request, response) 7 | * 8 | * You can find detailed information about these below. 9 | */ 10 | 11 | // Module dependencies 12 | var _ = require('lodash'); 13 | 14 | /** 15 | * Service method to add user login information to database. Note that this is called 16 | * only from AuthController after successfully user login action. 17 | * 18 | * @param {sails.model.user} user User object 19 | * @param {Request} request Request object 20 | */ 21 | exports.login = function login(user, request) { 22 | sails.log.verbose(__filename + ':' + __line + ' [Service.Logger.login() called]'); 23 | 24 | // Parse detailed information from user-agent string 25 | var r = require('ua-parser').parse(request.headers['user-agent']); 26 | 27 | // Create new UserLogin row to database 28 | sails.models.userlogin 29 | .create({ 30 | ip: request.ip, 31 | host: request.host, 32 | agent: request.headers['user-agent'], 33 | browser: (r.ua.toString() || 'Unknown'), 34 | browserVersion: (r.ua.toVersionString() || 'Unknown'), 35 | browserFamily: (r.ua.family || 'Unknown'), 36 | os: (r.os.toString() || 'Unknown'), 37 | osVersion: (r.os.toVersionString() || 'Unknown'), 38 | osFamily: (r.os.family || 'Unknown'), 39 | user: user.id 40 | }) 41 | .exec(function callback(error, record) { 42 | if (error) { 43 | sails.log.error(__filename + ':' + __line + ' [Failed to write user login data to database]'); 44 | sails.log.error(error); 45 | } else { 46 | if (request._sails.hooks.pubsub) { 47 | sails.models.userlogin.publishCreate(record); 48 | } 49 | } 50 | }) 51 | ; 52 | }; 53 | 54 | /** 55 | * Service method to create request log. This is fired from APIAnalytics hook see /backend/config/apianalytics.js file 56 | * for real usage. 57 | * 58 | * Note that this method is called "silently" and if error occurs those are just added to sails error log. Also note 59 | * that this also needs to determine user id from authentication token (JWT) within some cases. 60 | * 61 | * @todo Make clean of this collection, because this will be a huge one :D 62 | * 63 | * @param {{}} log Log object from APIAnalytics 64 | * @param {Request} request Request object 65 | * @param {Response} response Request object 66 | */ 67 | exports.request = function request(log, request, response) { 68 | sails.log.verbose(__filename + ':' + __line + ' [Service.Logger.request() called]'); 69 | 70 | var userId; 71 | 72 | // Token is found on request object (this means that it has been already verified) 73 | if (request.token) { 74 | userId = request.token; 75 | 76 | writeLog(); 77 | } else { // Otherwise we need to determine token for log data 78 | sails.services['token'].getToken(request, function verify(error, token) { 79 | if (_.isEmpty(error) && token !== -1) { 80 | userId = token; 81 | } else { 82 | userId = -1; 83 | } 84 | 85 | writeLog(); 86 | }, false); 87 | } 88 | 89 | // Create new log entry 90 | function writeLog() { 91 | sails.models['requestlog'] 92 | .create({ 93 | method: log.method, 94 | url: log.diagnostic.url, 95 | headers: request.headers || {}, 96 | parameters: log.diagnostic.routeParams, 97 | query: log.diagnostic.queryParams, 98 | body: log.diagnostic.bodyParams, 99 | protocol: log.protocol, 100 | ip: log.ip, 101 | responseTime: log.responseTime, 102 | middlewareLatency: log.diagnostic.middlewareLatency, 103 | user: userId 104 | }) 105 | .exec(function exec(error) { 106 | if (error) { 107 | sails.log.error(__filename + ':' + __line + ' [Failed to write request data to database]'); 108 | sails.log.error(error); 109 | } 110 | }) 111 | ; 112 | } 113 | }; 114 | -------------------------------------------------------------------------------- /api/services/Passport.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Passport Service 5 | * 6 | * A painless Passport.js service for your Sails app that is guaranteed to 7 | * Rock Your Socks™. It takes all the hassle out of setting up Passport.js by 8 | * encapsulating all the boring stuff in two functions: 9 | * 10 | * passport.endpoint() 11 | * passport.callback() 12 | * 13 | * The former sets up an endpoint (/auth/:provider) for redirecting a user to a 14 | * third-party provider for authentication, while the latter sets up a callback 15 | * endpoint (/auth/:provider/callback) for receiving the response from the 16 | * third-party provider. All you have to do is define in the configuration which 17 | * third-party providers you'd like to support. It's that easy! 18 | * 19 | * Behind the scenes, the service stores all the data it needs within "Passports". 20 | * These contain all the information required to associate a local user with a 21 | * profile from a third-party provider. This even holds true for the good ol' 22 | * password authentication scheme – the Authentication Service takes care of 23 | * encrypting passwords and storing them in Passports, allowing you to keep your 24 | * User model free of bloat. 25 | */ 26 | 27 | // Module dependencies 28 | var passport = require('passport'); 29 | var path = require('path'); 30 | var url = require('url'); 31 | var _ = require('lodash'); 32 | 33 | // Load authentication protocols 34 | passport.protocols = require('./protocols'); 35 | 36 | /** 37 | * Connect a third-party profile to a local user 38 | * 39 | * This is where most of the magic happens when a user is authenticating with a 40 | * third-party provider. What it does, is the following: 41 | * 42 | * 1. Given a provider and an identifier, find a matching Passport. 43 | * 2. From here, the logic branches into two paths. 44 | * 45 | * - A user is not currently logged in: 46 | * 1. If a Passport wasn't found, just return 401 47 | * 2. If a Passport was found, get the user associated with the passport. 48 | * 49 | * - A user is currently logged in: 50 | * 1. If a Passport wasn't found, just return 401 51 | * 2. If a Passport was found, nothing needs to happen. 52 | * 53 | * As you can see, this function handles both "authentication" and "authori- 54 | * zation" at the same time. This is due to the fact that we pass in 55 | * `passReqToCallback: true` when loading the strategies, allowing us to look 56 | * for an existing session in the request and taking action based on that. 57 | * 58 | * For more information on auth(entication|rization) in Passport.js, check out: 59 | * http://passportjs.org/guide/authenticate/ 60 | * http://passportjs.org/guide/authorize/ 61 | * 62 | * @param {Request} request 63 | * @param {*} query 64 | * @param {{}} profile 65 | * @param {Function} next 66 | */ 67 | passport.connect = function connect(request, query, profile, next) { 68 | sails.log.verbose(__filename + ':' + __line + ' [Service.Passport.connect() called]'); 69 | 70 | var user = {}; 71 | 72 | // Set the authentication provider. 73 | query.provider = request.param('provider'); 74 | 75 | // If the profile object contains a list of emails, grab the first one and add it to the user. 76 | if (profile.hasOwnProperty('emails')) { 77 | user.email = profile.emails[0].value; 78 | } 79 | 80 | // If the profile object contains a username, add it to the user. 81 | if (profile.hasOwnProperty('username')) { 82 | user.username = profile.username; 83 | } 84 | 85 | /** 86 | * If neither an email or a username was available in the profile, we don't 87 | * have a way of identifying the user in the future. Throw an error and let 88 | * whoever is next in the line take care of it. 89 | */ 90 | if (!Object.keys(user).length) { 91 | next(new Error('Neither a username or email was available', null)); 92 | } else { 93 | sails.models.passport 94 | .findOne({ 95 | provider: profile.provider, 96 | identifier: query.identifier.toString() 97 | }) 98 | .exec(function callback(error, passport) { 99 | if (error) { 100 | next(error); 101 | } else if (!request.user) { 102 | if (!passport) { 103 | // Scenario: A new user is attempting to sign up using a third-party authentication provider. 104 | // Action: Create a new user and assign them a passport. 105 | 106 | next("user not found") 107 | } else { 108 | // Scenario: An existing user is trying to log in using an already connected passport. 109 | // Action: Get the user associated with the passport. 110 | 111 | // If the tokens have changed since the last session, update them 112 | if (query.hasOwnProperty('tokens') && query.tokens !== passport.tokens) { 113 | passport.tokens = query.tokens; 114 | } 115 | 116 | // Save any updates to the Passport before moving on 117 | passport 118 | .save(function callback(error, passport) { 119 | if (error) { 120 | next(error); 121 | } else { 122 | // Fetch the user associated with the Passport 123 | sails.models.user.findOne(passport.user, next); 124 | } 125 | }); 126 | } 127 | } else { 128 | // Scenario: A user is currently logged in and trying to connect a new passport. 129 | // Action: Create and assign a new passport to the user. 130 | if (!passport) { 131 | query.user = request.user.id; 132 | 133 | sails.models.passport 134 | .create(query) 135 | .exec(function callback(error) { 136 | // If a passport wasn't created, bail out 137 | if (error) { 138 | next(error); 139 | } else { 140 | next(error, request.user); 141 | } 142 | }); 143 | } else { 144 | // Scenario: The user is a nutjob or spammed the back-button. 145 | // Action: Simply pass along the already established session. 146 | next(null, request.user); 147 | } 148 | } 149 | }); 150 | } 151 | }; 152 | 153 | /** 154 | * Create an authentication endpoint 155 | * 156 | * For more information on authentication in Passport.js, check out: 157 | * http://passportjs.org/guide/authenticate/ 158 | * 159 | * @param {Request} request 160 | * @param {Response} response 161 | */ 162 | passport.endpoint = function endpoint(request, response) { 163 | sails.log.verbose(__filename + ':' + __line + ' [Service.Passport.endpoint() called]'); 164 | 165 | var strategies = sails.config.passport; 166 | var provider = request.param('provider'); 167 | var options = {}; 168 | 169 | // If a provider doesn't exist for this endpoint, send the user back to the login page 170 | if (!strategies.hasOwnProperty(provider)) { 171 | response.json(401, 'Unknown auth provider'); 172 | } else { 173 | // Attach scope if it has been set in the config 174 | if (strategies[provider].hasOwnProperty('scope')) { 175 | options.scope = strategies[provider].scope; 176 | } 177 | 178 | // Load authentication strategies 179 | this.loadStrategies(request); 180 | 181 | // Redirect the user to the provider for authentication. When complete, 182 | // the provider will redirect the user back to the application at 183 | // /auth/:provider/callback 184 | this.authenticate(provider, options)(request, response, request.next); 185 | } 186 | }; 187 | 188 | /** 189 | * Create an authentication callback endpoint 190 | * 191 | * For more information on authentication in Passport.js, check out: 192 | * http://passportjs.org/guide/authenticate/ 193 | * 194 | * @param {Request} request 195 | * @param {Response} response 196 | * @param {Function} next 197 | */ 198 | passport.callback = function callback(request, response, next) { 199 | sails.log.verbose(__filename + ':' + __line + ' [Service.Passport.callback() called]'); 200 | 201 | var provider = request.param('provider', 'local'); 202 | var action = request.param('action'); 203 | 204 | if (provider === 'local' && action !== undefined) { 205 | if (action === 'connect' && request.user) { 206 | this.protocols.local.connect(request, response, next); 207 | } else { 208 | next(new Error('Invalid action')); 209 | } 210 | } else { 211 | // Load authentication strategies 212 | this.loadStrategies(request); 213 | 214 | // The provider will redirect the user to this URL after approval. Finish 215 | // the authentication process by attempting to obtain an access token. If 216 | // access was granted, the user will be logged in. Otherwise, authentication 217 | // has failed. 218 | this.authenticate(provider, next)(request, response, request.next); 219 | } 220 | }; 221 | 222 | /** 223 | * Load all strategies defined in the Passport configuration 224 | * 225 | * For example, we could add this to our config to use the GitHub strategy 226 | * with permission to access a users email address (even if it's marked as 227 | * private) as well as permission to add and update a user's Gists: 228 | * 229 | github: { 230 | name: 'GitHub', 231 | protocol: 'oauth2', 232 | scope: [ 'user', 'gist' ] 233 | options: { 234 | clientID: 'CLIENT_ID', 235 | clientSecret: 'CLIENT_SECRET' 236 | } 237 | } 238 | * 239 | * For more information on the providers supported by Passport.js, check out: 240 | * http://passportjs.org/guide/providers/ 241 | * 242 | * @param {Request} request 243 | */ 244 | passport.loadStrategies = function loadStrategies(request) { 245 | sails.log.verbose(__filename + ':' + __line + ' [Service.Passport.loadStrategies() called]'); 246 | 247 | var self = this; 248 | var strategies = sails.config.passport; 249 | 250 | Object.keys(strategies).forEach(function(key) { 251 | var options = {passReqToCallback: true}; 252 | var Strategy; 253 | 254 | if (key === 'local') { 255 | /** 256 | * Since we need to allow users to login using both usernames as well as 257 | * emails, we'll set the username field to something more generic. 258 | */ 259 | _.extend(options, {usernameField: 'identifier'}); 260 | 261 | // Only load the local strategy if it's enabled in the config 262 | if (strategies.local) { 263 | Strategy = strategies[key].strategy; 264 | 265 | self.use(new Strategy(options, self.protocols.local.login)); 266 | } 267 | } else { 268 | var protocol = strategies[key].protocol; 269 | var callback = strategies[key].callback; 270 | 271 | if (!callback) { 272 | callback = path.join('auth', key, 'callback'); 273 | } 274 | 275 | Strategy = strategies[key].strategy; 276 | 277 | var baseUrl = sails.getBaseurl(); 278 | 279 | switch (protocol) { 280 | case 'oauth': 281 | case 'oauth2': 282 | options.callbackURL = url.resolve(baseUrl, callback); 283 | break; 284 | case 'openid': 285 | options.returnURL = url.resolve(baseUrl, callback); 286 | options.realm = baseUrl; 287 | options.profile = true; 288 | break; 289 | } 290 | 291 | /** 292 | * Merge the default options with any options defined in the config. All 293 | * defaults can be overridden, but I don't see a reason why you'd want to 294 | * do that. 295 | */ 296 | _.extend(options, strategies[key].options); 297 | 298 | self.use(new Strategy(options, self.protocols[protocol])); 299 | } 300 | }); 301 | }; 302 | 303 | passport.serializeUser(function serializeUser(user, next) { 304 | sails.log.verbose(__filename + ':' + __line + ' [Service.Passport.serializeUser() called]'); 305 | 306 | if (!user) { 307 | next({message: 'Invalid user.'}, null); 308 | } else { 309 | next(null, user.id); 310 | } 311 | }); 312 | 313 | passport.deserializeUser(function deserializeUser(id, next) { 314 | sails.log.verbose(__filename + ':' + __line + ' [Service.Passport.deserializeUser() called]'); 315 | 316 | sails.models['user'].findOne(id, next); 317 | }); 318 | 319 | module.exports = passport; 320 | -------------------------------------------------------------------------------- /api/services/Token.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Token.js 5 | * 6 | * JWT token service which handles issuing and verifying tokens. 7 | */ 8 | var jwt = require('jsonwebtoken'); 9 | 10 | /** 11 | * Service method to generate a new token based on payload we want to put on it. 12 | * 13 | * @param {String} payload 14 | * 15 | * @returns {*} 16 | */ 17 | module.exports.issue = function issue(payload) { 18 | sails.log.verbose(__filename + ':' + __line + ' [Service.Token.issue() called]'); 19 | 20 | return jwt.sign( 21 | payload, // This is the payload we want to put inside the token 22 | process.env.TOKEN_SECRET || "oursecret" // Secret string which will be used to sign the token 23 | ); 24 | }; 25 | 26 | /** 27 | * Service method to verify that the token we received on a request hasn't be tampered with. 28 | * 29 | * @param {String} token Token to validate 30 | * @param {Function} next Callback function 31 | * 32 | * @returns {*} 33 | */ 34 | module.exports.verify = function verify(token, next) { 35 | sails.log.verbose(__filename + ':' + __line + ' [Service.Token.verify() called]'); 36 | 37 | return jwt.verify( 38 | token, // The token to be verified 39 | process.env.TOKEN_SECRET || "oursecret", // The secret we used to sign it. 40 | {}, // Options, none in this case 41 | next // The callback to be call when the verification is done. 42 | ); 43 | }; 44 | 45 | /** 46 | * Service method to get current user token. Note that this will also verify actual token value. 47 | * 48 | * @param {Request} request Request object 49 | * @param {Function} next Callback function 50 | * @param {Boolean} throwError Throw error from invalid token specification 51 | * 52 | * @return {*} 53 | */ 54 | module.exports.getToken = function getToken(request, next, throwError) { 55 | sails.log.verbose(__filename + ':' + __line + ' [Service.Token.getToken() called]'); 56 | 57 | var token = ''; 58 | 59 | // Yeah we got required 'authorization' header 60 | if (request.headers && request.headers.authorization) { 61 | var parts = request.headers.authorization.split(' '); 62 | 63 | if (parts.length === 2) { 64 | var scheme = parts[0]; 65 | var credentials = parts[1]; 66 | 67 | if (/^Bearer$/i.test(scheme)) { 68 | token = credentials; 69 | } 70 | } else if (throwError) { 71 | throw new Error('Invalid authorization header format. Format is Authorization: Bearer [token]'); 72 | } 73 | } else if (request.param('token')) { // JWT token sent by parameter 74 | token = request.param('token'); 75 | } else if (throwError) { // Otherwise request didn't contain required JWT token 76 | throw new Error('No authorization header was found'); 77 | } 78 | 79 | return sails.services.token.verify(token, next); 80 | }; 81 | -------------------------------------------------------------------------------- /api/services/protocols/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Authentication Protocols 5 | * 6 | * Protocols where introduced to patch all the little inconsistencies between 7 | * the different authentication APIs. While the local authentication strategy 8 | * is as straight-forward as it gets, there are some differences between the 9 | * services that expose an API for authentication. 10 | * 11 | * For example, OAuth 1.0 and OAuth 2.0 both handle delegated authentication 12 | * using tokens, but the tokens have changed between the two versions. This 13 | * is accommodated by having a single `token` object in the Passport model that 14 | * can contain any combination of tokens issued by the authentication API. 15 | */ 16 | module.exports = { 17 | local: require('./local'), 18 | oauth: require('./oauth'), 19 | oauth2: require('./oauth2'), 20 | openid: require('./openid') 21 | }; 22 | -------------------------------------------------------------------------------- /api/services/protocols/local.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var validator = require('validator'); 4 | 5 | /** 6 | * Local Authentication Protocol 7 | * 8 | * The most widely used way for websites to authenticate users is via a username 9 | * and/or email as well as a password. This module provides functions both for 10 | * registering entirely new users, assigning passwords to already registered 11 | * users and validating login requesting. 12 | * 13 | * For more information on local authentication in Passport.js, check out: 14 | * http://passportjs.org/guide/username-password/ 15 | */ 16 | 17 | /** 18 | * Assign local Passport to user 19 | * 20 | * This function can be used to assign a local Passport to a user who doens't 21 | * have one already. This would be the case if the user registered using a 22 | * third-party service and therefore never set a password. 23 | * 24 | * @param {Request} request 25 | * @param {Response} response 26 | * @param {Function} next 27 | */ 28 | exports.connect = function connect(request, response, next) { 29 | var user = request.user; 30 | var password = request.param('password'); 31 | 32 | sails.models.passport 33 | .findOne({ 34 | protocol: 'local', 35 | user: user.id 36 | }) 37 | .exec(function onExec(error, passport) { 38 | if (error) { 39 | next(error); 40 | } else { 41 | if (!passport) { 42 | sails.models['passport'] 43 | .create({ 44 | protocol: 'local', 45 | password: password, 46 | user: user.id 47 | }) 48 | .exec(function onExec(error) { 49 | next(error, user); 50 | }); 51 | } else { 52 | next(null, user); 53 | } 54 | } 55 | }) 56 | ; 57 | }; 58 | 59 | /** 60 | * Validate a login request 61 | * 62 | * Looks up a user using the supplied identifier (email or username) and then 63 | * attempts to find a local Passport associated with the user. If a Passport is 64 | * found, its password is checked against the password supplied in the form. 65 | * 66 | * @param {Request} request 67 | * @param {string} identifier 68 | * @param {string} password 69 | * @param {Function} next 70 | */ 71 | exports.login = function login(request, identifier, password, next) { 72 | var isEmail = validator.isEmail(identifier); 73 | var query = {}; 74 | 75 | if (isEmail) { 76 | query.email = identifier; 77 | } else { 78 | query.username = identifier; 79 | } 80 | 81 | sails.models.user 82 | .findOne(query) 83 | .exec(function onExec(error, user) { 84 | if (error) { 85 | next(error); 86 | } else if (!user) { 87 | next(null, false); 88 | } else { 89 | sails.models.passport 90 | .findOne({ 91 | protocol: 'local', 92 | user: user.id 93 | }) 94 | .exec(function onExec(error, passport) { 95 | if (passport) { 96 | passport.validatePassword(password, function callback(error, response) { 97 | if (error) { 98 | next(error); 99 | } else if (!response) { 100 | next(null, false); 101 | } else { 102 | next(null, user); 103 | } 104 | }); 105 | } else { 106 | next(null, false); 107 | } 108 | }) 109 | ; 110 | } 111 | }) 112 | ; 113 | }; 114 | -------------------------------------------------------------------------------- /api/services/protocols/oauth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * OAuth Authentication Protocol 5 | * 6 | * OAuth 1.0 is a delegated authentication strategy that involves multiple 7 | * steps. First, a request token must be obtained. Next, the user is redirected 8 | * to the service provider to authorize access. Finally, after authorization has 9 | * been granted, the user is redirected back to the application and the request 10 | * token can be exchanged for an access token. The application requesting access, 11 | * known as a consumer, is identified by a consumer key and consumer secret. 12 | * 13 | * For more information on OAuth in Passport.js, check out: 14 | * http://passportjs.org/guide/oauth/ 15 | * 16 | * @param {Request} request 17 | * @param {string} token 18 | * @param {string} tokenSecret 19 | * @param {{}} profile 20 | * @param {Function} next 21 | */ 22 | module.exports = function oauth(request, token, tokenSecret, profile, next) { 23 | var query = { 24 | identifier: profile.id, 25 | protocol: 'oauth', 26 | tokens: { 27 | token: token 28 | } 29 | }; 30 | 31 | if (tokenSecret !== undefined) { 32 | query.tokens.tokenSecret = tokenSecret; 33 | } 34 | 35 | sails.services.passport.connect(request, query, profile, next); 36 | }; 37 | -------------------------------------------------------------------------------- /api/services/protocols/oauth2.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * OAuth 2.0 Authentication Protocol 5 | * 6 | * OAuth 2.0 is the successor to OAuth 1.0, and is designed to overcome 7 | * perceived shortcomings in the earlier version. The authentication flow is 8 | * essentially the same. The user is first redirected to the service provider 9 | * to authorize access. After authorization has been granted, the user is 10 | * redirected back to the application with a code that can be exchanged for an 11 | * access token. The application requesting access, known as a client, is 12 | * identified by an ID and secret. 13 | * 14 | * For more information on OAuth in Passport.js, check out: 15 | * http://passportjs.org/guide/oauth/ 16 | * 17 | * @param {Request} request 18 | * @param {string} accessToken 19 | * @param {string} refreshToken 20 | * @param {{}} profile 21 | * @param {Function} next 22 | */ 23 | module.exports = function oauth2(request, accessToken, refreshToken, profile, next) { 24 | var query = { 25 | identifier: profile.id, 26 | protocol: 'oauth2', 27 | tokens: { 28 | accessToken: accessToken 29 | } 30 | }; 31 | 32 | if (refreshToken !== undefined) { 33 | query.tokens.refreshToken = refreshToken; 34 | } 35 | 36 | sails.services.passport.connect(request, query, profile, next); 37 | }; 38 | -------------------------------------------------------------------------------- /api/services/protocols/openid.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * OpenID Authentication Protocol 5 | * 6 | * OpenID is an open standard for federated authentication. When visiting a 7 | * website, users present their OpenID to sign in. The user then authenticates 8 | * with their chosen OpenID provider, which issues an assertion to confirm the 9 | * user's identity. The website verifies this assertion in order to sign the 10 | * user in. 11 | * 12 | * For more information on OpenID in Passport.js, check out: 13 | * http://passportjs.org/guide/openid/ 14 | * 15 | * @param {Request} request 16 | * @param {string} identifier 17 | * @param {{}} profile 18 | * @param {Function} next 19 | */ 20 | module.exports = function openid(request, identifier, profile, next) { 21 | var query = { 22 | identifier: identifier, 23 | protocol: 'openid' 24 | }; 25 | 26 | sails.services.passport.connect(request, query, profile, next); 27 | }; 28 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * app.js 5 | * 6 | * Use `app.js` to run your app without `sails lift`. 7 | * To start the server, run: `node app.js`. 8 | * 9 | * This is handy in situations where the sails CLI is not relevant or useful. 10 | * 11 | * For example: 12 | * => `node app.js` 13 | * => `forever start app.js` 14 | * => `node debug app.js` 15 | * => `modulus deploy` 16 | * => `heroku scale` 17 | * 18 | * 19 | * The same command-line arguments are supported, e.g.: 20 | * `node app.js --silent --port=80 --prod` 21 | */ 22 | 23 | // Ensure a "sails" can be located: 24 | var sails; 25 | 26 | try { 27 | sails = require('sails'); 28 | } catch (e) { 29 | console.error('To run an app using `node app.js`, you usually need to have a version of `sails` installed in the same directory as your app.'); 30 | console.error('To do that, run `npm install sails`'); 31 | console.error(''); 32 | console.error('Alternatively, if you have sails installed globally (i.e. you did `npm install -g sails`), you can use `sails lift`.'); 33 | console.error('When you run `sails lift`, your app will still use a local `./node_modules/sails` dependency if it exists,'); 34 | console.error('but if it doesn\'t, the app will run with the global sails instead!'); 35 | return; 36 | } 37 | 38 | // Try to get `rc` dependency 39 | var rc; 40 | 41 | try { 42 | rc = require('rc'); 43 | } catch (e0) { 44 | try { 45 | rc = require('sails/node_modules/rc'); 46 | } catch (e1) { 47 | console.error('Could not find dependency: `rc`.'); 48 | console.error('Your `.sailsrc` file(s) will be ignored.'); 49 | console.error('To resolve this, run:'); 50 | console.error('npm install rc --save'); 51 | rc = function () { 52 | return {}; 53 | }; 54 | } 55 | } 56 | 57 | // Start server 58 | sails.lift(rc('sails')); 59 | -------------------------------------------------------------------------------- /config/apianalytics.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Sails APIAnalytics hook configuration. 5 | * 6 | * @see https://github.com/mikermcneil/sails-hook-apianalytics 7 | * 8 | * @type {{ 9 | * apianalytics: { 10 | * routesToLog: string[], 11 | * dontLogParams: string[], 12 | * onRequest: Function, 13 | * onResponse: Function 14 | * } 15 | * }} 16 | */ 17 | module.exports = { 18 | apianalytics: { 19 | /** 20 | * The types of requests to log 21 | * (e.g. ["get /foo/bar", "post /foo", "/*"]) 22 | * Defaults to all routes. 23 | */ 24 | routesToLog: [ 25 | '/*' 26 | ], 27 | 28 | /** 29 | * Parameters which should NEVER be logged 30 | * (e.g. "password") 31 | * If seen, they will be replaced with "*PROTECTED*" 32 | */ 33 | dontLogParams: ['password', 'token'], 34 | 35 | // When request starts 36 | onRequest: function onRequest(log, req, res) { 37 | // Defaults to doing nothing 38 | }, 39 | 40 | // When request is done 41 | onResponse: function onResponse(log, req, res) { 42 | sails.services.logger.request(log, req, res); 43 | } 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /config/application.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Application configuration + some helpers for debug / log purposes. 3 | * 4 | * Note that don't use 'use strict' with this file, it will broke those helpers... 5 | */ 6 | 7 | module.exports = { 8 | appName: 'angular-sailsjs-boilerplate' 9 | }; 10 | 11 | Object.defineProperty(global, '__stack', { 12 | get: function() { 13 | var orig = Error.prepareStackTrace; 14 | 15 | Error.prepareStackTrace = function(_, stack) { 16 | return stack; 17 | }; 18 | 19 | var err = new Error; 20 | 21 | Error.captureStackTrace(err, arguments.callee); 22 | 23 | var stack = err.stack; 24 | 25 | Error.prepareStackTrace = orig; 26 | 27 | return stack; 28 | } 29 | }); 30 | 31 | Object.defineProperty(global, '__line', { 32 | get: function() { 33 | return __stack[1].getLineNumber(); 34 | } 35 | }); 36 | 37 | Object.defineProperty(global, '__function', { 38 | get: function() { 39 | return __stack[1].getFunctionName(); 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /config/blueprints.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Blueprint API Configuration 5 | * (sails.config.blueprints) 6 | * 7 | * These settings are for the global configuration of blueprint routes and 8 | * request options (which impact the behavior of blueprint actions). 9 | * 10 | * You may also override any of these settings on a per-controller basis 11 | * by defining a '_config' key in your controller defintion, and assigning it 12 | * a configuration object with overrides for the settings in this file. 13 | * A lot of the configuration options below affect so-called "CRUD methods", 14 | * or your controllers' `find`, `create`, `update`, and `destroy` actions. 15 | * 16 | * It's important to realize that, even if you haven't defined these yourself, as long as 17 | * a model exists with the same name as the controller, Sails will respond with built-in CRUD 18 | * logic in the form of a JSON API, including support for sort, pagination, and filtering. 19 | * 20 | * For more information on the blueprint API, check out: 21 | * http://sailsjs.org/#/documentation/reference/blueprint-api 22 | * 23 | * For more information on the settings in this file, see: 24 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.blueprints.html 25 | * 26 | */ 27 | module.exports.blueprints = { 28 | /*************************************************************************** 29 | * * 30 | * Action routes speed up the backend development workflow by * 31 | * eliminating the need to manually bind routes. When enabled, GET, POST, * 32 | * PUT, and DELETE routes will be generated for every one of a controller's * 33 | * actions. * 34 | * * 35 | * If an `index` action exists, additional naked routes will be created for * 36 | * it. Finally, all `actions` blueprints support an optional path * 37 | * parameter, `id`, for convenience. * 38 | * * 39 | * `actions` are enabled by default, and can be OK for production-- * 40 | * however, if you'd like to continue to use controller/action autorouting * 41 | * in a production deployment, you must take great care not to * 42 | * inadvertently expose unsafe/unintentional controller logic to GET * 43 | * requests. * 44 | * * 45 | ***************************************************************************/ 46 | actions: true, 47 | 48 | /*************************************************************************** 49 | * * 50 | * RESTful routes (`sails.config.blueprints.rest`) * 51 | * * 52 | * REST blueprints are the automatically generated routes Sails uses to * 53 | * expose a conventional REST API on top of a controller's `find`, * 54 | * `create`, `update`, and `destroy` actions. * 55 | * * 56 | * For example, a BoatController with `rest` enabled generates the * 57 | * following routes: * 58 | * ::::::::::::::::::::::::::::::::::::::::::::::::::::::: * 59 | * GET /boat/:id? -> BoatController.find * 60 | * POST /boat -> BoatController.create * 61 | * PUT /boat/:id -> BoatController.update * 62 | * DELETE /boat/:id -> BoatController.destroy * 63 | * * 64 | * `rest` blueprint routes are enabled by default, and are suitable for use * 65 | * in a production scenario, as long you take standard security precautions * 66 | * (combine w/ policies, etc.) * 67 | * * 68 | ***************************************************************************/ 69 | rest: true, 70 | 71 | /*************************************************************************** 72 | * * 73 | * Shortcut routes are simple helpers to provide access to a * 74 | * controller's CRUD methods from your browser's URL bar. When enabled, * 75 | * GET, POST, PUT, and DELETE routes will be generated for the * 76 | * controller's`find`, `create`, `update`, and `destroy` actions. * 77 | * * 78 | * `shortcuts` are enabled by default, but should be disabled in * 79 | * production. * 80 | * * 81 | ***************************************************************************/ 82 | shortcuts: false, 83 | 84 | /*************************************************************************** 85 | * * 86 | * An optional mount path for all blueprint routes on a controller, * 87 | * including `rest`, `actions`, and `shortcuts`. This allows you to take * 88 | * advantage of blueprint routing, even if you need to namespace your API * 89 | * methods. * 90 | * * 91 | * (NOTE: This only applies to blueprint autoroutes, not manual routes from * 92 | * `sails.config.routes`) * 93 | * * 94 | ***************************************************************************/ 95 | prefix: '', 96 | 97 | /*************************************************************************** 98 | * * 99 | * Whether to pluralize controller names in blueprint routes. * 100 | * * 101 | * (NOTE: This only applies to blueprint autoroutes, not manual routes from * 102 | * `sails.config.routes`) * 103 | * * 104 | * For example, REST blueprints for `FooController` with `pluralize` * 105 | * enabled: * 106 | * GET /foos/:id? * 107 | * POST /foos * 108 | * PUT /foos/:id? * 109 | * DELETE /foos/:id? * 110 | * * 111 | ***************************************************************************/ 112 | pluralize: false, 113 | 114 | /*************************************************************************** 115 | * * 116 | * Whether the blueprint controllers should populate model fetches with * 117 | * data from other models which are linked by associations * 118 | * * 119 | * If you have a lot of data in one-to-many associations, leaving this on * 120 | * may result in very heavy api calls * 121 | * * 122 | ***************************************************************************/ 123 | populate: false, 124 | 125 | /**************************************************************************** 126 | * * 127 | * Whether to run Model.watch() in the find and findOne blueprint actions. * 128 | * Can be overridden on a per-model basis. * 129 | * * 130 | ****************************************************************************/ 131 | autoWatch: true, 132 | 133 | /** 134 | * We want to mirror all socket events also to request maker itself, this simplifies some frontend side data 135 | * handling logic. 136 | */ 137 | mirror: true, 138 | 139 | /**************************************************************************** 140 | * * 141 | * The default number of records to show in the response from a "find" * 142 | * action. Doubles as the default size of populated arrays if populate is * 143 | * true. * 144 | * * 145 | ****************************************************************************/ 146 | defaultLimit: 4294967295 147 | }; 148 | -------------------------------------------------------------------------------- /config/bootstrap.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Bootstrap 5 | * (sails.config.bootstrap) 6 | * 7 | * An asynchronous bootstrap function that runs before your Sails app gets lifted. 8 | * This gives you an opportunity to set up your data model, run jobs, or perform some special logic. 9 | * 10 | * For more information on bootstrapping your app, check out: 11 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.bootstrap.html 12 | */ 13 | module.exports.bootstrap = function bootstrap(next) { 14 | /** 15 | * It's very important to trigger this 'next' method when you are finished with the bootstrap! 16 | * (otherwise your server will never lift, since it's waiting on the bootstrap) 17 | */ 18 | sails.services.passport.loadStrategies(); 19 | 20 | next(); 21 | }; 22 | -------------------------------------------------------------------------------- /config/connections.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Connections 5 | * (sails.config.connections) 6 | * 7 | * `Connections` are like "saved settings" for your adapters. What's the difference between 8 | * a connection and an adapter, you might ask? An adapter (e.g. `sails-mysql`) is generic-- 9 | * it needs some additional information to work (e.g. your database host, password, user, etc.) 10 | * A `connection` is that additional information. 11 | * 12 | * Each model must have a `connection` property (a string) which is references the name of one 13 | * of these connections. If it doesn't, the default `connection` configured in `config/models.js` 14 | * will be applied. Of course, a connection can (and usually is) shared by multiple models. 15 | * . 16 | * Note: If you're using version control, you should put your passwords/api keys 17 | * in `config/local.js`, environment variables, or use another strategy. 18 | * (this is to prevent you inadvertently sensitive credentials up to your repository.) 19 | * 20 | * For more information on configuration, check out: 21 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.connections.html 22 | */ 23 | module.exports.connections = { 24 | /** 25 | * Local disk storage for DEVELOPMENT ONLY 26 | * 27 | * Installed by default. 28 | */ 29 | localDiskDb: { 30 | adapter: 'sails-disk' 31 | }, 32 | 33 | /** 34 | * MySQL is the world's most popular relational database. 35 | * http://en.wikipedia.org/wiki/MySQL 36 | * 37 | * Run: 38 | * npm install sails-mysql 39 | */ 40 | someMysqlServer: { 41 | adapter: 'sails-mysql', 42 | host: 'YOUR_MYSQL_SERVER_HOSTNAME_OR_IP_ADDRESS', 43 | user: 'YOUR_MYSQL_USER', 44 | password: 'YOUR_MYSQL_PASSWORD', 45 | database: 'YOUR_MYSQL_DB' 46 | }, 47 | 48 | /** 49 | * MongoDB is the leading NoSQL database. 50 | * http://en.wikipedia.org/wiki/MongoDB 51 | * 52 | * Run: 53 | * npm install sails-mongo 54 | */ 55 | someMongodbServer: { 56 | adapter: 'sails-mongo', 57 | host: 'localhost', 58 | port: 27017, 59 | user: 'username', 60 | password: 'password', 61 | database: 'your_mongo_db_name_here' 62 | }, 63 | 64 | /** 65 | * PostgreSQL is another officially supported relational database. 66 | * http://en.wikipedia.org/wiki/PostgreSQL 67 | * 68 | * Run: 69 | * npm install sails-postgresql 70 | */ 71 | somePostgresqlServer: { 72 | adapter: 'sails-postgresql', 73 | host: 'YOUR_POSTGRES_SERVER_HOSTNAME_OR_IP_ADDRESS', 74 | user: 'YOUR_POSTGRES_USER', 75 | password: 'YOUR_POSTGRES_PASSWORD', 76 | database: 'YOUR_POSTGRES_DB' 77 | } 78 | 79 | /** 80 | * More adapters: 81 | * https://github.com/balderdashy/sails 82 | */ 83 | }; 84 | -------------------------------------------------------------------------------- /config/cors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Cross-Origin Resource Sharing (CORS) Settings 5 | * (sails.config.cors) 6 | * 7 | * CORS is like a more modern version of JSONP-- it allows your server/API 8 | * to successfully respond to requests from client-side JavaScript code 9 | * running on some other domain (e.g. google.com) 10 | * Unlike JSONP, it works with POST, PUT, and DELETE requests 11 | * 12 | * For more information on CORS, check out: 13 | * http://en.wikipedia.org/wiki/Cross-origin_resource_sharing 14 | * 15 | * Note that any of these settings (besides 'allRoutes') can be changed on a per-route basis 16 | * by adding a "cors" object to the route configuration: 17 | * 18 | * '/get foo': { 19 | * controller: 'foo', 20 | * action: 'bar', 21 | * cors: { 22 | * origin: 'http://foobar.com,https://owlhoot.com' 23 | * } 24 | * } 25 | * 26 | * For more information on this configuration file, see: 27 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.cors.html 28 | * 29 | */ 30 | module.exports.cors = { 31 | /*************************************************************************** 32 | * * 33 | * Allow CORS on all routes by default? If not, you must enable CORS on a * 34 | * per-route basis by either adding a "cors" configuration object to the * 35 | * route config, or setting "cors:true" in the route config to use the * 36 | * default settings below. * 37 | * * 38 | ***************************************************************************/ 39 | allRoutes: true, 40 | 41 | /*************************************************************************** 42 | * * 43 | * Which domains which are allowed CORS access? This can be a * 44 | * comma-delimited list of hosts (beginning with http:// or https://) or * 45 | * "*" to allow all domains CORS access. * 46 | * * 47 | ***************************************************************************/ 48 | origin: '*', 49 | 50 | /*************************************************************************** 51 | * * 52 | * Allow cookies to be shared for CORS requests? * 53 | * * 54 | ***************************************************************************/ 55 | credentials: true, 56 | 57 | /*************************************************************************** 58 | * * 59 | * Which methods should be allowed for CORS requests? This is only used in * 60 | * response to preflight requests (see article linked above for more info) * 61 | * * 62 | ***************************************************************************/ 63 | methods: 'GET, POST, PUT, DELETE, OPTIONS, HEAD', 64 | 65 | /*************************************************************************** 66 | * * 67 | * Which headers should be allowed for CORS requests? This is only used in * 68 | * response to preflight requests. * 69 | * * 70 | ***************************************************************************/ 71 | headers: 'content-type, access-control-allow-origin, authorization' 72 | }; 73 | -------------------------------------------------------------------------------- /config/csrf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Cross-Site Request Forgery Protection Settings 5 | * (sails.config.csrf) 6 | * 7 | * CSRF tokens are like a tracking chip. While a session tells the server that a user 8 | * "is who they say they are", a csrf token tells the server "you are where you say you are". 9 | * 10 | * When enabled, all non-GET requests to the Sails server must be accompanied by 11 | * a special token, identified as the '_csrf' parameter. 12 | * 13 | * This option protects your Sails app against cross-site request forgery (or CSRF) attacks. 14 | * A would-be attacker needs not only a user's session cookie, but also this timestamped, 15 | * secret CSRF token, which is refreshed/granted when the user visits a URL on your app's domain. 16 | * 17 | * This allows us to have certainty that our users' requests haven't been hijacked, 18 | * and that the requests they're making are intentional and legitimate. 19 | * 20 | * This token has a short-lived expiration timeline, and must be acquired by either: 21 | * 22 | * (a) For traditional view-driven web apps: 23 | * Fetching it from one of your views, where it may be accessed as 24 | * a local variable, e.g.: 25 | *
26 | * 27 | *
28 | * 29 | * or (b) For AJAX/Socket-heavy and/or single-page apps: 30 | * Sending a GET request to the `/csrfToken` route, where it will be returned 31 | * as JSON, e.g.: 32 | * { _csrf: 'ajg4JD(JGdajhLJALHDa' } 33 | * 34 | * 35 | * Enabling this option requires managing the token in your front-end app. 36 | * For traditional web apps, it's as easy as passing the data from a view into a form action. 37 | * In AJAX/Socket-heavy apps, just send a GET request to the /csrfToken route to get a valid token. 38 | * 39 | * For more information on CSRF, check out: 40 | * http://en.wikipedia.org/wiki/Cross-site_request_forgery 41 | * 42 | * For more information on this configuration file, including info on CSRF + CORS, see: 43 | * http://beta.sailsjs.org/#/documentation/reference/sails.config/sails.config.csrf.html 44 | * 45 | */ 46 | 47 | /**************************************************************************** 48 | * * 49 | * Enabled CSRF protection for your site? * 50 | * * 51 | ****************************************************************************/ 52 | 53 | module.exports.csrf = false; 54 | 55 | /**************************************************************************** 56 | * * 57 | * You may also specify more fine-grained settings for CSRF, including the * 58 | * domains which are allowed to request the CSRF token via AJAX. These * 59 | * settings override the general CORS settings in your config/cors.js file. * 60 | * * 61 | ****************************************************************************/ 62 | 63 | // module.exports.csrf = { 64 | // grantTokenViaAjax: true, 65 | // origin: '' 66 | // } 67 | -------------------------------------------------------------------------------- /config/env/development.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Development environment settings 4 | * 5 | * This file can include shared settings for a development team, 6 | * such as API keys or remote database passwords. If you're using 7 | * a version control solution for your Sails app, this file will 8 | * be committed to your repository unless you add it to your .gitignore 9 | * file. If your repository will be publicly viewable, don't add 10 | * any private information to this file! 11 | * 12 | */ 13 | module.exports = { 14 | /*************************************************************************** 15 | * Set the default database connection for models in the development * 16 | * environment (see config/connections.js and config/models.js ) * 17 | ***************************************************************************/ 18 | 19 | // models: { 20 | // connection: 'someMongodbServer' 21 | // } 22 | }; 23 | -------------------------------------------------------------------------------- /config/env/production.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Production environment settings 4 | * 5 | * This file can include shared settings for a production environment, 6 | * such as API keys or remote database passwords. If you're using 7 | * a version control solution for your Sails app, this file will 8 | * be committed to your repository unless you add it to your .gitignore 9 | * file. If your repository will be publicly viewable, don't add 10 | * any private information to this file! 11 | * 12 | */ 13 | module.exports = { 14 | /*************************************************************************** 15 | * Set the default database connection for models in the production * 16 | * environment (see config/connections.js and config/models.js ) * 17 | ***************************************************************************/ 18 | 19 | // models: { 20 | // connection: 'someMysqlServer' 21 | // }, 22 | 23 | /*************************************************************************** 24 | * Set the port in the production environment to 80 * 25 | ***************************************************************************/ 26 | 27 | // port: 80, 28 | 29 | /*************************************************************************** 30 | * Set the log level in production environment to "silent" * 31 | ***************************************************************************/ 32 | 33 | // log: { 34 | // level: "silent" 35 | // } 36 | }; 37 | -------------------------------------------------------------------------------- /config/globals.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Global Variable Configuration 5 | * (sails.config.globals) 6 | * 7 | * Configure which global variables which will be exposed 8 | * automatically by Sails. 9 | * 10 | * For more information on configuration, check out: 11 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.globals.html 12 | */ 13 | module.exports.globals = { 14 | /**************************************************************************** 15 | * * 16 | * Expose the lodash installed in Sails core as a global variable. If this * 17 | * is disabled, like any other node module you can always run npm install * 18 | * lodash --save, then var _ = require('lodash') at the top of any file. * 19 | * * 20 | ****************************************************************************/ 21 | _: false, 22 | 23 | /**************************************************************************** 24 | * * 25 | * Expose the async installed in Sails core as a global variable. If this is * 26 | * disabled, like any other node module you can always run npm install async * 27 | * --save, then var async = require('async') at the top of any file. * 28 | * * 29 | ****************************************************************************/ 30 | async: false, 31 | 32 | /**************************************************************************** 33 | * * 34 | * Expose the sails instance representing your app. If this is disabled, you * 35 | * can still get access via req._sails. * 36 | * * 37 | ****************************************************************************/ 38 | sails: true, 39 | 40 | /**************************************************************************** 41 | * * 42 | * Expose each of your app's services as global variables (using their * 43 | * "globalId"). E.g. a service defined in api/models/NaturalLanguage.js * 44 | * would have a globalId of NaturalLanguage by default. If this is disabled, * 45 | * you can still access your services via sails.services.* * 46 | * * 47 | ****************************************************************************/ 48 | services: false, 49 | 50 | /**************************************************************************** 51 | * * 52 | * Expose each of your app's models as global variables (using their * 53 | * "globalId"). E.g. a model defined in api/models/User.js would have a * 54 | * globalId of User by default. If this is disabled, you can still access * 55 | * your models via sails.models.*. * 56 | * * 57 | ****************************************************************************/ 58 | models: false 59 | }; 60 | -------------------------------------------------------------------------------- /config/http.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * HTTP Server Settings 5 | * (sails.config.http) 6 | * 7 | * Configuration for the underlying HTTP server in Sails. 8 | * Only applies to HTTP requests (not WebSockets) 9 | * 10 | * For more information on configuration, check out: 11 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.http.html 12 | */ 13 | 14 | module.exports.http = { 15 | /**************************************************************************** 16 | * * 17 | * Express middleware to use for every Sails request. To add custom * 18 | * middleware to the mix, add a function to the middleware config object and * 19 | * add its key to the "order" array. The $custom key is reserved for * 20 | * backwards-compatibility with Sails v0.9.x apps that use the * 21 | * `customMiddleware` config option. * 22 | * * 23 | ****************************************************************************/ 24 | middleware: { 25 | /*************************************************************************** 26 | * * 27 | * The order in which middleware should be run for HTTP request. (the Sails * 28 | * router is invoked by the "router" middleware below.) * 29 | * * 30 | ***************************************************************************/ 31 | order: [ 32 | 'startRequestTimer', 33 | 'cookieParser', 34 | 'session', 35 | 'bodyParser', 36 | 'handleBodyParserError', 37 | 'compress', 38 | 'methodOverride', 39 | 'poweredBy', 40 | '$custom', 41 | 'router', 42 | 'www', 43 | 'favicon', 44 | '404', 45 | '500' 46 | ] 47 | 48 | /*************************************************************************** 49 | * * 50 | * The body parser that will handle incoming multipart HTTP requests. By * 51 | * default as of v0.10, Sails uses * 52 | * [skipper](http://github.com/balderdashy/skipper). See * 53 | * http://www.senchalabs.org/connect/multipart.html for other options. * 54 | * * 55 | ***************************************************************************/ 56 | //bodyParser: require('skipper') 57 | }, 58 | 59 | /*************************************************************************** 60 | * * 61 | * The number of seconds to cache flat files on disk being served by * 62 | * Express static middleware (by default, these files are in `.tmp/public`) * 63 | * * 64 | * The HTTP static cache is only active in a 'production' environment, * 65 | * since that's the only time Express will cache flat-files. * 66 | * * 67 | ***************************************************************************/ 68 | cache: 31557600000 69 | }; 70 | -------------------------------------------------------------------------------- /config/i18n.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Internationalization / Localization Settings 5 | * (sails.config.i18n) 6 | * 7 | * If your app will touch people from all over the world, i18n (or internationalization) 8 | * may be an important part of your international strategy. 9 | * 10 | * 11 | * For more informationom i18n in Sails, check out: 12 | * http://sailsjs.org/#/documentation/concepts/Internationalization 13 | * 14 | * For a complete list of i18n options, see: 15 | * https://github.com/mashpie/i18n-node#list-of-configuration-options 16 | */ 17 | module.exports.i18n = { 18 | /*************************************************************************** 19 | * * 20 | * Which locales are supported? * 21 | * * 22 | ***************************************************************************/ 23 | locales: ['en'], 24 | 25 | /**************************************************************************** 26 | * * 27 | * What is the default locale for the site? Note that this setting will be * 28 | * overridden for any request that sends an "Accept-Language" header (i.e. * 29 | * most browsers), but it's still useful if you need to localize the * 30 | * response for requests made by non-browser clients (e.g. cURL). * 31 | * * 32 | ****************************************************************************/ 33 | defaultLocale: 'en', 34 | 35 | /**************************************************************************** 36 | * * 37 | * Automatically add new keys to locale (translation) files when they are * 38 | * encountered during a request? * 39 | * * 40 | ****************************************************************************/ 41 | updateFiles: false, 42 | 43 | /**************************************************************************** 44 | * * 45 | * Path (relative to app root) of directory to store locale (translation) * 46 | * files in. * 47 | * * 48 | ****************************************************************************/ 49 | localesDirectory: '/config/locales' 50 | }; 51 | 52 | -------------------------------------------------------------------------------- /config/jwt.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | jwt: { 5 | secret: '8fe171f3-0046-4df5-9216-14099434339f', 6 | sign: { 7 | algorithm: 'HS512', 8 | expiresInMinutes: 1, 9 | noTimestamp: false 10 | }, 11 | verify: { 12 | ignoreExpiration: false 13 | } 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /config/local_example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Local environment settings 5 | * 6 | * While you're DEVELOPING your app, this config file should include 7 | * any settings specifically for your development computer (db passwords, etc.) 8 | * 9 | * When you're ready to deploy your app in PRODUCTION, you can always use this file 10 | * for configuration options specific to the server where the app will be deployed. 11 | * But environment variables are usually the best way to handle production settings. 12 | * 13 | * PLEASE NOTE: 14 | * This file is included in your .gitignore, so if you're using git 15 | * as a version control solution for your Sails app, keep in mind that 16 | * this file won't be committed to your repository! 17 | * 18 | * Good news is, that means you can specify configuration for your local 19 | * machine in this file without inadvertently committing personal information 20 | * (like database passwords) to the repo. Plus, this prevents other members 21 | * of your team from committing their local configuration changes on top of yours. 22 | * 23 | * For more information, check out: 24 | * http://links.sailsjs.org/docs/config/local 25 | */ 26 | module.exports = { 27 | connections: { 28 | // Add your connections here, remember install that adapter also 29 | 30 | // MySQL is the world's most popular relational database. 31 | // Learn more: http://en.wikipedia.org/wiki/MySQL 32 | mysql: { 33 | adapter: 'sails-mysql', 34 | host: 'localhost', 35 | user: '', 36 | password: '', 37 | database: '', 38 | charset: 'utf8', 39 | collation: 'utf8_swedish_ci' 40 | } 41 | }, 42 | models: { 43 | connection: 'mysql' // Change this to be one of your connections key 44 | }, 45 | session: { 46 | secret: '' // Add your own SECRET string here 47 | }, 48 | port: 1337, 49 | environment: 'development', 50 | log: { 51 | level: 'info' 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /config/locales/_README.md: -------------------------------------------------------------------------------- 1 | # Internationalization / Localization Settings 2 | 3 | > Also see the official docs on internationalization/localization: 4 | > http://links.sailsjs.org/docs/config/locales 5 | 6 | ## Locales 7 | All locale files live under `config/locales`. Here is where you can add translations 8 | as JSON key-value pairs. The name of the file should match the language that you are supporting, which allows for automatic language detection based on request headers. 9 | 10 | Here is an example locale stringfile for the Spanish language (`config/locales/es.json`): 11 | ```json 12 | { 13 | "Hello!": "Hola!", 14 | "Hello %s, how are you today?": "¿Hola %s, como estas?", 15 | } 16 | ``` 17 | ## Usage 18 | Locales can be accessed in controllers/policies through `res.i18n()`, or in views through the `__(key)` or `i18n(key)` functions. 19 | Remember that the keys are case sensitive and require exact key matches, e.g. 20 | 21 | ```ejs 22 |

<%= __('Welcome to PencilPals!') %>

23 |

<%= i18n('Hello %s, how are you today?', 'Pencil Maven') %>

24 |

<%= i18n('That\'s right-- you can use either i18n() or __()') %>

25 | ``` 26 | 27 | ## Configuration 28 | Localization/internationalization config can be found in `config/i18n.js`, from where you can set your supported locales. 29 | -------------------------------------------------------------------------------- /config/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Error.Passport.Password.Wrong": "Whoa, that password wasn't quite right!", 3 | "Error.Passport.Password.NotSet": "Oh no, you haven't set a password yet!", 4 | "Error.Passport.Username.NotFound": "Uhm, what's your name again?", 5 | "Error.Passport.Email.NotFound": "That email doesn't seem right" 6 | } 7 | -------------------------------------------------------------------------------- /config/log.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Built-in Log Configuration 5 | * (sails.config.log) 6 | * 7 | * Configure the log level for your app, as well as the transport 8 | * (Underneath the covers, Sails uses Winston for logging, which 9 | * allows for some pretty neat custom transports/adapters for log messages) 10 | * 11 | * For more information on the Sails logger, check out: 12 | * http://sailsjs.org/#/documentation/concepts/Logging 13 | */ 14 | module.exports = { 15 | /*************************************************************************** 16 | * * 17 | * Valid `level` configs: i.e. the minimum log level to capture with * 18 | * sails.log.*() * 19 | * * 20 | * The order of precedence for log levels from lowest to highest is: * 21 | * silly, verbose, info, debug, warn, error * 22 | * * 23 | * You may also set the level to "silent" to suppress all logs. * 24 | * * 25 | ***************************************************************************/ 26 | log: { 27 | level: 'info', 28 | filePath: 'logs/application.log' 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /config/models.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Default model configuration 5 | * (sails.config.models) 6 | * 7 | * Unless you override them, the following properties will be included 8 | * in each of your models. 9 | * 10 | * For more info on Sails models, see: 11 | * http://sailsjs.org/#/documentation/concepts/ORM 12 | */ 13 | module.exports.models = { 14 | /*************************************************************************** 15 | * * 16 | * Your app's default connection. i.e. the name of one of your app's * 17 | * connections (see `config/connections.js`) * 18 | * * 19 | ***************************************************************************/ 20 | connection: 'localDiskDb', 21 | migrate: 'alter' 22 | }; 23 | -------------------------------------------------------------------------------- /config/passport.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Passport configuration 5 | * 6 | * This if the configuration for your Passport.js setup and it where you'd 7 | * define the authentication strategies you want your application to employ. 8 | * 9 | * I have tested the service with all of the providers listed below - if you 10 | * come across a provider that for some reason doesn't work, feel free to open 11 | * an issue on GitHub. 12 | * 13 | * Also, authentication scopes can be set through the `scope` property. 14 | * 15 | * For more information on the available providers, check out: 16 | * http://passportjs.org/guide/providers/ 17 | */ 18 | module.exports.passport = { 19 | local: { 20 | strategy: require('passport-local').Strategy 21 | }, 22 | 23 | twitter: { 24 | name: 'Twitter', 25 | protocol: 'oauth', 26 | strategy: require('passport-twitter').Strategy, 27 | options: { 28 | consumerKey: 'your-consumer-key', 29 | consumerSecret: 'your-consumer-secret' 30 | } 31 | }, 32 | 33 | github: { 34 | name: 'GitHub', 35 | protocol: 'oauth2', 36 | strategy: require('passport-github').Strategy, 37 | options: { 38 | clientID: 'your-client-id', 39 | clientSecret: 'your-client-secret' 40 | } 41 | }, 42 | 43 | facebook: { 44 | name: 'Facebook', 45 | protocol: 'oauth2', 46 | strategy: require('passport-facebook').Strategy, 47 | options: { 48 | clientID: 'your-client-id', 49 | clientSecret: 'your-client-secret' 50 | } 51 | }, 52 | 53 | google: { 54 | name: 'Google', 55 | protocol: 'openid', 56 | strategy: require('passport-google').Strategy 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /config/policies.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Policy Mappings 5 | * (sails.config.policies) 6 | * 7 | * Policies are simple functions which run **before** your controllers. 8 | * You can apply one or more policies to a given controller, or protect 9 | * its actions individually. 10 | * 11 | * Any policy file (e.g. `api/policies/authenticated.js`) can be accessed 12 | * below by its filename, minus the extension, (e.g. "authenticated") 13 | * 14 | * For more information on how policies work, see: 15 | * http://sailsjs.org/#/documentation/concepts/Policies 16 | * 17 | * For more information on configuring policies, check out: 18 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.policies.html 19 | */ 20 | module.exports.policies = { 21 | // Default policy for all controllers and actions 22 | '*': ['authenticated'], 23 | 24 | // Author controller 25 | AuthController: { 26 | '*': ['passport'], 27 | 'checkPassword': ['authenticated'] 28 | }, 29 | 30 | // Author controller 31 | AuthorController: { 32 | '*': ['authenticated'], 33 | 'count': ['authenticated'], 34 | 'find': ['authenticated'], 35 | 'findOne': ['authenticated'], 36 | 'create': ['authenticated', 'isAdmin', 'addDataCreate'], 37 | 'update': ['authenticated', 'isAdmin', 'addDataUpdate'], 38 | 'destroy': ['authenticated', 'isAdmin'], 39 | 'add': ['authenticated', 'isAdmin'], 40 | 'remove': ['authenticated', 'isAdmin'] 41 | }, 42 | 43 | // Book controller 44 | BookController: { 45 | '*': ['authenticated'], 46 | 'count': ['authenticated'], 47 | 'find': ['authenticated'], 48 | 'findOne': ['authenticated'], 49 | 'create': ['authenticated', 'isAdmin', 'addDataCreate'], 50 | 'update': ['authenticated', 'isAdmin', 'addDataUpdate'], 51 | 'destroy': ['authenticated', 'isAdmin'], 52 | 'add': ['authenticated', 'isAdmin'], 53 | 'remove': ['authenticated', 'isAdmin'] 54 | }, 55 | 56 | // User controller 57 | UserController: { 58 | '*': ['authenticated'], 59 | 'count': ['authenticated'], 60 | 'find': ['authenticated'], 61 | 'findOne': ['authenticated'], 62 | 'create': ['authenticated', 'isAdmin', 'addDataCreate'], 63 | 'update': ['authenticated', 'isAdmin', 'addDataUpdate'], 64 | 'destroy': ['authenticated', 'isAdmin'], 65 | 'add': ['authenticated', 'isAdmin'], 66 | 'remove': ['authenticated', 'isAdmin'] 67 | }, 68 | 69 | // UserLogin controller 70 | UserLoginController: { 71 | '*': false, 72 | 'statistics': ['authenticated', 'isAdmin'], 73 | 'count': ['authenticated', 'isAdmin'], 74 | 'find': ['authenticated', 'isAdmin'], 75 | 'findOne': ['authenticated', 'isAdmin'], 76 | 'create': false, 77 | 'update': false, 78 | 'destroy': false, 79 | 'add': false, 80 | 'remove': false 81 | } 82 | }; 83 | -------------------------------------------------------------------------------- /config/routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Route Mappings 5 | * (sails.config.routes) 6 | * 7 | * Your routes map URLs to views and controllers. 8 | * 9 | * If Sails receives a URL that doesn't match any of the routes below, 10 | * it will check for matching files (images, scripts, stylesheets, etc.) 11 | * in your assets directory. e.g. `http://localhost:1337/images/foo.jpg` 12 | * might match an image file: `/assets/images/foo.jpg` 13 | * 14 | * Finally, if those don't match either, the default 404 handler is triggered. 15 | * See `config/404.js` to adjust your app's 404 logic. 16 | * 17 | * Note: Sails doesn't ACTUALLY serve stuff from `assets`-- the default Gruntfile in Sails copies 18 | * flat files from `assets` to `.tmp/public`. This allows you to do things like compile LESS or 19 | * CoffeeScript for the front-end. 20 | * 21 | * For more information on configuring custom routes, check out: 22 | * http://sailsjs.org/#/documentation/concepts/Routes/RouteTargetSyntax.html 23 | */ 24 | module.exports.routes = { 25 | // See https://github.com/balderdashy/sails/issues/2062 26 | 'OPTIONS /*': function(req, res) { 27 | res.send(200); 28 | }, 29 | 30 | // Authentication routes 31 | '/logout': 'AuthController.logout', 32 | 'POST /login': 'AuthController.callback', 33 | 'POST /login/:action': 'AuthController.callback', 34 | 'POST /auth/local': 'AuthController.callback', 35 | 'POST /auth/local/:action': 'AuthController.callback' 36 | }; 37 | -------------------------------------------------------------------------------- /config/session.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Session Configuration 5 | * (sails.config.session) 6 | * 7 | * Sails session integration leans heavily on the great work already done by 8 | * Express, but also unifies Socket.io with the Connect session store. It uses 9 | * Connect's cookie parser to normalize configuration differences between Express 10 | * and Socket.io and hooks into Sails' middleware interpreter to allow you to access 11 | * and auto-save to `req.session` with Socket.io the same way you would with Express. 12 | * 13 | * For more information on configuring the session, check out: 14 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.session.html 15 | */ 16 | module.exports.session = { 17 | /*************************************************************************** 18 | * * 19 | * Session secret is automatically generated when your new app is created * 20 | * Replace at your own risk in production-- you will invalidate the cookies * 21 | * of your users, forcing them to log in again. * 22 | * * 23 | ***************************************************************************/ 24 | secret: '' 25 | 26 | /*************************************************************************** 27 | * * 28 | * Set the session cookie expire time The maxAge is set by milliseconds, * 29 | * the example below is for 24 hours * 30 | * * 31 | ***************************************************************************/ 32 | // cookie: { 33 | // maxAge: 24 * 60 * 60 * 1000 34 | // } 35 | 36 | /*************************************************************************** 37 | * * 38 | * In production, uncomment the following lines to set up a shared redis * 39 | * session store that can be shared across multiple Sails.js servers * 40 | ***************************************************************************/ 41 | // adapter: 'redis', 42 | 43 | /*************************************************************************** 44 | * * 45 | * The following values are optional, if no options are set a redis * 46 | * instance running on localhost is expected. Read more about options at: * 47 | * https://github.com/visionmedia/connect-redis * 48 | * * 49 | * * 50 | ***************************************************************************/ 51 | // host: 'localhost', 52 | // port: 6379, 53 | // ttl: , 54 | // db: 0, 55 | // pass: 56 | // prefix: 'sess:' 57 | 58 | /*************************************************************************** 59 | * * 60 | * Uncomment the following lines to use your Mongo adapter as a session * 61 | * store * 62 | * * 63 | ***************************************************************************/ 64 | // adapter: 'mongo', 65 | // host: 'localhost', 66 | // port: 27017, 67 | // db: 'sails', 68 | // collection: 'sessions', 69 | 70 | /*************************************************************************** 71 | * * 72 | * Optional Values: * 73 | * * 74 | * # Note: url will override other connection settings url: * 75 | * 'mongodb://user:pass@host:port/database/collection', * 76 | * * 77 | ***************************************************************************/ 78 | // username: '', 79 | // password: '', 80 | // auto_reconnect: false, 81 | // ssl: false, 82 | // stringify: true 83 | }; 84 | -------------------------------------------------------------------------------- /config/sockets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WebSocket Server Settings 3 | * (sails.config.sockets) 4 | * 5 | * These settings provide transparent access to the options for Sails' 6 | * encapsulated WebSocket server, as well as some additional Sails-specific 7 | * configuration layered on top. 8 | * 9 | * For more information on sockets configuration, including advanced config options, see: 10 | * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.sockets.html 11 | */ 12 | module.exports.sockets = { 13 | /*************************************************************************** 14 | * * 15 | * Node.js (and consequently Sails.js) apps scale horizontally. It's a * 16 | * powerful, efficient approach, but it involves a tiny bit of planning. At * 17 | * scale, you'll want to be able to copy your app onto multiple Sails.js * 18 | * servers and throw them behind a load balancer. * 19 | * * 20 | * One of the big challenges of scaling an application is that these sorts * 21 | * of clustered deployments cannot share memory, since they are on * 22 | * physically different machines. On top of that, there is no guarantee * 23 | * that a user will "stick" with the same server between requests (whether * 24 | * HTTP or sockets), since the load balancer will route each request to the * 25 | * Sails server with the most available resources. However that means that * 26 | * all room/pubsub/socket processing and shared memory has to be offloaded * 27 | * to a shared, remote messaging queue (usually Redis) * 28 | * * 29 | * Luckily, Socket.io (and consequently Sails.js) apps support Redis for * 30 | * sockets by default. To enable a remote redis pubsub server, uncomment * 31 | * the config below. * 32 | * * 33 | * Worth mentioning is that, if `adapter` config is `redis`, but host/port * 34 | * is left unset, Sails will try to connect to redis running on localhost * 35 | * via port 6379 * 36 | * * 37 | ***************************************************************************/ 38 | adapter: 'memory', 39 | 40 | // 41 | // -OR- 42 | // 43 | 44 | // adapter: 'redis', 45 | // host: '127.0.0.1', 46 | // port: 6379, 47 | // db: 'sails', 48 | // pass: '' 49 | 50 | /*************************************************************************** 51 | * * 52 | * Whether to expose a 'get /__getcookie' route with CORS support that sets * 53 | * a cookie (this is used by the sails.io.js socket client to get access to * 54 | * a 3rd party cookie and to enable sessions). * 55 | * * 56 | * Warning: Currently in this scenario, CORS settings apply to interpreted * 57 | * requests sent via a socket.io connection that used this cookie to * 58 | * connect, even for non-browser clients! (e.g. iOS apps, toasters, node.js * 59 | * unit tests) * 60 | * * 61 | ***************************************************************************/ 62 | grant3rdPartyCookie: true, 63 | 64 | /*************************************************************************** 65 | * * 66 | * `beforeConnect` * 67 | * * 68 | * This custom beforeConnect function will be run each time BEFORE a new * 69 | * socket is allowed to connect, when the initial socket.io handshake is * 70 | * performed with the server. * 71 | * * 72 | * By default, when a socket tries to connect, Sails allows it, every time. * 73 | * (much in the same way any HTTP request is allowed to reach your routes. * 74 | * If no valid cookie was sent, a temporary session will be created for the * 75 | * connecting socket. * 76 | * * 77 | * If the cookie sent as part of the connetion request doesn't match any * 78 | * known user session, a new user session is created for it. * 79 | * * 80 | * In most cases, the user would already have a cookie since they loaded * 81 | * the socket.io client and the initial HTML pageyou're building. * 82 | * * 83 | * However, in the case of cross-domain requests, it is possible to receive * 84 | * a connection upgrade request WITHOUT A COOKIE (for certain transports) * 85 | * In this case, there is no way to keep track of the requesting user * 86 | * between requests, since there is no identifying information to link * 87 | * him/her with a session. The sails.io.js client solves this by connecting * 88 | * to a CORS/jsonp endpoint first to get a 3rd party cookie(fortunately this* 89 | * works, even in Safari), then opening the connection. * 90 | * * 91 | * You can also pass along a ?cookie query parameter to the upgrade url, * 92 | * which Sails will use in the absense of a proper cookie e.g. (when * 93 | * connecting from the client): * 94 | * io.sails.connect('http://localhost:1337?cookie=smokeybear') * 95 | * * 96 | * Finally note that the user's cookie is NOT (and will never be) accessible* 97 | * from client-side javascript. Using HTTP-only cookies is crucial for your * 98 | * app's security. * 99 | * * 100 | ***************************************************************************/ 101 | // beforeConnect: function(handshake, cb) { 102 | // // `true` allows the connection 103 | // return cb(null, true); 104 | // 105 | // // (`false` would reject the connection) 106 | // }, 107 | 108 | /*************************************************************************** 109 | * * 110 | * This custom afterDisconnect function will be run each time a socket * 111 | * disconnects * 112 | * * 113 | ***************************************************************************/ 114 | // afterDisconnect: function(session, socket, cb) { 115 | // // By default: do nothing. 116 | // return cb(); 117 | // }, 118 | 119 | transports: [ 120 | 'websocket', 121 | 'htmlfile', 122 | 'polling' 123 | ], 124 | 125 | origins: '*:*' 126 | 127 | // More configuration options for Sails+Socket.io: 128 | // http://sailsjs.org/#/documentation/reference/sails.config/sails.config.sockets.html 129 | }; 130 | -------------------------------------------------------------------------------- /config/views.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * View Engine Configuration 5 | * (sails.config.views) 6 | * 7 | * Server-sent views are a classic and effective way to get your app up 8 | * and running. Views are normally served from controllers. Below, you can 9 | * configure your templating language/framework of choice and configure 10 | * Sails' layout support. 11 | * 12 | * For more information on views and layouts, check out: 13 | * http://sailsjs.org/#/documentation/concepts/Views 14 | */ 15 | module.exports.views = { 16 | /**************************************************************************** 17 | * * 18 | * View engine (aka template language) to use for your app's *server-side* * 19 | * views * 20 | * * 21 | * Sails+Express supports all view engines which implement TJ Holowaychuk's * 22 | * `consolidate.js`, including, but not limited to: * 23 | * * 24 | * ejs, jade, handlebars, mustache underscore, hogan, haml, haml-coffee, * 25 | * dust atpl, eco, ect, jazz, jqtpl, JUST, liquor, QEJS, swig, templayed, * 26 | * toffee, walrus, & whiskers * 27 | * * 28 | * For more options, check out the docs: * 29 | * https://github.com/balderdashy/sails-wiki/blob/0.9/config.views.md#engine * 30 | * * 31 | ****************************************************************************/ 32 | engine: 'ejs', 33 | 34 | /**************************************************************************** 35 | * * 36 | * Layouts are simply top-level HTML templates you can use as wrappers for * 37 | * your server-side views. If you're using ejs or jade, you can take * 38 | * advantage of Sails' built-in `layout` support. * 39 | * * 40 | * When using a layout, when one of your views is served, it is injected * 41 | * into the `body` partial defined in the layout. This lets you reuse header * 42 | * and footer logic between views. * 43 | * * 44 | * NOTE: Layout support is only implemented for the `ejs` view engine! * 45 | * For most other engines, it is not necessary, since they implement * 46 | * partials/layouts themselves. In those cases, this config will be * 47 | * silently ignored. * 48 | * * 49 | * The `layout` setting may be set to one of the following: * 50 | * * 51 | * If `false`, layouts will be disabled. Otherwise, if a string is * 52 | * specified, it will be interpreted as the relative path to your layout * 53 | * file from `views/` folder. (the file extension, ".ejs", should be * 54 | * omitted) * 55 | * * 56 | ****************************************************************************/ 57 | 58 | /**************************************************************************** 59 | * * 60 | * Using Multiple Layouts with EJS * 61 | * * 62 | * If you're using the default engine, `ejs`, Sails supports the use of * 63 | * multiple `layout` files. To take advantage of this, before rendering a * 64 | * view, override the `layout` local in your controller by setting * 65 | * `res.locals.layout`. (this is handy if you parts of your app's UI look * 66 | * completely different from each other) * 67 | * * 68 | * e.g. your default might be * 69 | * layout: 'layouts/public' * 70 | * * 71 | * But you might override that in some of your controllers with: * 72 | * layout: 'layouts/internal' * 73 | * * 74 | ****************************************************************************/ 75 | layout: 'layout', 76 | 77 | /**************************************************************************** 78 | * * 79 | * Partials are simply top-level snippets you can leverage to reuse template * 80 | * for your server-side views. If you're using handlebars, you can take * 81 | * advantage of Sails' built-in `partials` support. * 82 | * * 83 | * If `false` or empty partials will be located in the same folder as views. * 84 | * Otherwise, if a string is specified, it will be interpreted as the * 85 | * relative path to your partial files from `views/` folder. * 86 | * * 87 | ****************************************************************************/ 88 | partials: false 89 | }; 90 | -------------------------------------------------------------------------------- /dummy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Dummy JS file for smart IDEs like php/webStorm. 5 | * 6 | * Purpose of this file is to help IDE to use autocomplete features. 7 | */ 8 | 9 | // Debug / log helpers, see /config/application.js 10 | var __filename; 11 | var __line; 12 | var __function; 13 | var __stack; 14 | 15 | // Sails is defined as global see /config/globals.js 16 | var sails; 17 | var exports; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend-server", 3 | "private": true, 4 | "version": "0.1.0", 5 | "description": "Backend server for angular-sailsjs-boilerplate a Sails application that serves just a JSON API for frontend application", 6 | "keywords": [ 7 | "sails.js", 8 | "backend", 9 | "REST", 10 | "API", 11 | "JWT", 12 | "JSON Web Token", 13 | "Passport.js" 14 | ], 15 | "dependencies": { 16 | "async": "1.5.0", 17 | "barrels": "1.6.0", 18 | "bcryptjs": "2.3.0", 19 | "bluebird": "3.0.5", 20 | "include-all": "0.1.6", 21 | "jsonwebtoken": "5.4.1", 22 | "lodash": "3.10.1", 23 | "moment-timezone": "0.4.1", 24 | "passport": "0.3.0", 25 | "passport-facebook": "2.0.0", 26 | "passport-github": "1.0.0", 27 | "passport-google": "0.3.0", 28 | "passport-local": "1.0.0", 29 | "passport-twitter": "1.0.3", 30 | "rc": "1.1.5", 31 | "sails": "0.11.2", 32 | "sails-disk": "0.10.8", 33 | "sails-hook-apianalytics": "git://github.com/mikermcneil/sails-hook-apianalytics", 34 | "sails-mysql": "0.11.0", 35 | "ua-parser": "0.3.5", 36 | "validator": "4.2.1" 37 | }, 38 | "devDependencies": { 39 | "chai": "3.4.0", 40 | "mocha": "2.3.3", 41 | "should": "7.1.1", 42 | "supertest": "1.1.0" 43 | }, 44 | "engines": { 45 | "node": "0.10.x" 46 | }, 47 | "scripts": { 48 | "start": "node app.js", 49 | "debug": "node debug app.js", 50 | "test": "./node_modules/mocha/bin/mocha" 51 | }, 52 | "main": "app.js", 53 | "repository": { 54 | "type": "git", 55 | "url": "git://github.com/tarlepp/angular-sailsjs-boilerplate.git" 56 | }, 57 | "bugs": { 58 | "url": "https://github.com/tarlepp/angular-sailsjs-boilerplate/issues" 59 | }, 60 | "author": "Tarmo Leppänen", 61 | "license": "MIT" 62 | } 63 | -------------------------------------------------------------------------------- /test/bootstrap.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Mocha bootstrap file for backend application tests. 3 | */ 4 | 'use strict'; 5 | 6 | var Sails = require('sails'); 7 | var fs = require('fs'); 8 | 9 | /** 10 | * Mocha bootstrap before function, that is run before any tests are being processed. This will lift sails.js with 11 | * test configuration. 12 | * 13 | * Note! Tests will use localDiskDb connection and this _removes_ possible existing disk store file from .tmp folder! 14 | * 15 | * @param {Function} next Callback function 16 | */ 17 | before(function before(next) { 18 | fs.unlink('.tmp/localDiskDb.db', function unlinkDone(error) { 19 | Sails.lift({ 20 | // configuration for testing purposes 21 | models: { 22 | connection: 'localDiskDb', 23 | migrate: 'drop' 24 | }, 25 | port: 1336, 26 | environment: 'development', 27 | log: { 28 | level: 'error' 29 | }, 30 | hooks: { 31 | grunt: false 32 | } 33 | }, function callback(error, sails) { 34 | // Yeah sails is lifted now! 35 | next(error, sails); 36 | }); 37 | }); 38 | }); 39 | 40 | /** 41 | * Mocha bootstrap after function, that is run after all tests are processed. Main purpose of this is just to 42 | * lower sails test instance. 43 | * 44 | * @param {Function} next Callback function 45 | */ 46 | after(function after(next) { 47 | sails.lower(next); 48 | }); 49 | -------------------------------------------------------------------------------- /test/fixtures/Author.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "J. R. R. Tolkien", 4 | "description": "John Ronald Reuel Tolkien, CBE (/ˈtɒlkiːn/ tol-keen; 3 January 1892 – 2 September 1973) was an English writer, poet, philologist, and university professor, best known as the author of the classic high fantasy works The Hobbit, The Lord of the Rings, and The Silmarillion.", 5 | "createdUser": 1, 6 | "updatedUser": 1 7 | }, 8 | { 9 | "name": "Leo Tolstoy", 10 | "description": "Count Lev Nikolayevich Tolstoy (Russian: Лев Никола́евич Толсто́й, pronounced [lʲef nʲɪkɐˈlaɪvʲɪt͡ɕ tɐlˈstoj] ( listen); 9 September [O.S. 28 August] 1828 – 20 November [O.S. 7 November] 1910), also known as Leo Tolstoy, was a Russian writer and philosopher who primarily wrote novels and short stories. Tolstoy was a master of realistic fiction and is widely considered one of the world's greatest novelists. He is best known for two long novels, War and Peace (1869) and Anna Karenina (1877). Tolstoy first achieved literary acclaim in his 20s with his semi-autobiographical trilogy of novels, Childhood, Boyhood, and Youth (1852-1856) and Sevastopol Sketches (1855), based on his experiences in the Crimean War. His fiction output also includes two additional novels, dozens of short stories, and several famous novellas, including The Death of Ivan Ilych, Family Happiness, and Hadji Murad. Later in life, he also wrote plays and essays. Tolstoy is equally known for his complicated and paradoxical persona and for his extreme moralistic and ascetic views, which he adopted after a moral crisis and spiritual awakening in the 1870s, after which he also became noted as a moral thinker and social reformer.", 11 | "createdUser": 1, 12 | "updatedUser": 1 13 | }, 14 | { 15 | "name": "William Gibson", 16 | "description": "William Ford Gibson (born March 17, 1948) is an American-Canadian speculative fiction novelist who has been called the \"noir prophet\" of the cyberpunk subgenre. Gibson coined the term \"cyberspace\" in his short story \"Burning Chrome\" (1982) and later popularized the concept in his debut novel, Neuromancer (1984). In envisaging cyberspace, Gibson created an iconography for the information age before the ubiquity of the Internet in the 1990s. He is also credited with predicting the rise of reality television and with establishing the conceptual foundations for the rapid growth of virtual environments such as video games and the World Wide Web.", 17 | "createdUser": 1, 18 | "updatedUser": 1 19 | }, 20 | { 21 | "name": "Herbert George “H.G.” Wells", 22 | "description": "Herbert George “H.G.” Wells (21 September 1866 – 13 August 1946) was an English writer, now best known for his work in the science fiction genre. He was also a prolific writer in many other genres, including contemporary novels, history, politics, and social commentary, even writing textbooks and rules for war games. Wells is sometimes called The Father of Science Fiction, as are Jules Verne and Hugo Gernsback. His most notable science fiction works include The War of the Worlds, The Time Machine, The Invisible Man, and The Island of Doctor Moreau.", 23 | "createdUser": 1, 24 | "updatedUser": 1 25 | }, 26 | { 27 | "name": "Jules Verne", 28 | "description": "Jules Gabriel Verne (French: [ʒyl vɛʁn]; 8 February 1828 – 24 March 1905) was a French novelist, poet, and playwright best known for his adventure novels and his profound influence on the literary genre of science fiction.\r\nBorn to bourgeois parents in the seaport of Nantes, Verne was trained to follow in his father's footsteps as a lawyer, but quit the profession early in life to write for magazines and the stage. His collaboration with the publisher Pierre-Jules Hetzel led to the creation of the Voyages Extraordinaires, a widely popular series of scrupulously researched adventure novels including Journey to the Center of the Earth, Twenty Thousand Leagues Under the Sea, and Around the World in Eighty Days.\r\nVerne is generally considered a major literary author in France and most of Europe, where he has had a wide influence on the literary avant-garde and on surrealism. His reputation is markedly different in Anglophone regions, where he has often been labeled a writer of genre fiction or children's books, not least because of the highly abridged and altered translations in which his novels are often reprinted.\r\nVerne is the second most-translated author in the world since 1979, between the English-language writers Agatha Christie and William Shakespeare, and probably was the most-translated during the 1960s and 1970s. He is one of the authors sometimes called \"The Father of Science Fiction\", as are H. G. Wells and Hugo Gernsback.", 29 | "createdUser": 1, 30 | "updatedUser": 1 31 | } 32 | ] -------------------------------------------------------------------------------- /test/fixtures/Book.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "releaseDate": "1936-01-01", 4 | "title": "Songs for the Philologists", 5 | "description": "Songs for the Philologists is a collection of poems by E. V. Gordon and J. R. R. Tolkien as well as traditional songs. It is the rarest and most difficult to find Tolkien-related book. Originally a collection of typescripts compiled by Gordon in 1921–26 for the students of the University of Leeds, it was given by A. H. Smith of University College London, a former student at Leeds, to a group of students to be printed privately in 1935 or 1936, and printed in 1936 with the impressuum 'Printed by G. Tillotson, A. H. Smith, B. Pattison and other members of the English Department, University College, London.'", 6 | "author": 1, 7 | "createdUser": 1, 8 | "updatedUser": 1 9 | }, 10 | { 11 | "releaseDate": "1937-01-01", 12 | "title": "The Hobbit", 13 | "description": "The Hobbit, or There and Back Again, is a fantasy novel and children's book by English author J. R. R. Tolkien. It was published on 21 September 1937 to wide critical acclaim, being nominated for the Carnegie Medal and awarded a prize from the New York Herald Tribune for best juvenile fiction. The book remains popular and is recognized as a classic in children's literature.\r\nSet in a time \"Between the Dawn of Færie and the Dominion of Men\", The Hobbit follows the quest of home-loving hobbit Bilbo Baggins to win a share of the treasure guarded by the dragon, Smaug. Bilbo's journey takes him from light-hearted, rural surroundings into more sinister territory. The story is told in the form of an episodic quest, and most chapters introduce a specific creature, or type of creature, of Tolkien's Wilderland. By accepting the disreputable, romantic, fey and adventurous side of his nature and applying his wits and common sense, Bilbo gains a new level of maturity, competence and wisdom. The story reaches its climax in the Battle of Five Armies, where many of the characters and creatures from earlier chapters re-emerge to engage in conflict.\r\nPersonal growth and forms of heroism are central themes of the story. Along with motifs of warfare, these themes have led critics to view Tolkien's own experiences during World War I as instrumental in shaping the story. The author's scholarly knowledge of Germanic philology and interest in fairy tales are often noted as influences.\r\nEncouraged by the book's critical and financial success, the publisher requested a sequel. As Tolkien's work on the successor The Lord of the Rings progressed, he made retrospective accommodations for it in The Hobbit. These few but significant changes were integrated into the second edition. Further editions followed with minor emendations, including those reflecting Tolkien's changing concept of the world into which Bilbo stumbled. The work has never been out of print. Its ongoing legacy encompasses many adaptations for stage, screen, radio, board games and video games. Several of these adaptations have received critical recognition on their own merits.", 14 | "author": 1, 15 | "createdUser": 1, 16 | "updatedUser": 1 17 | }, 18 | { 19 | "releaseDate": "1945-01-01", 20 | "title": " Leaf by Niggle", 21 | "description": "\"Leaf by Niggle\" is a short story written by J. R. R. Tolkien in 1938–39 and first published in the Dublin Review in January 1945. It can be found, most notably, in Tolkien's book titled Tree and Leaf, and in other places (including the collections The Tolkien Reader, Poems & Stories, A Tolkien Miscellany, and Tales from the Perilous Realm). This is notable because the book, consisting of a seminal essay called \"On Fairy-Stories\" and \"Leaf by Niggle\" offers the underlying philosophy (Creation and Sub-Creation, see below) of much of Tolkien's fantastical writings.\r\n\"Leaf by Niggle\" is often seen as an allegory of Tolkien's own creative process, and, to an extent, of his own life.", 22 | "author": 1, 23 | "createdUser": 1, 24 | "updatedUser": 1 25 | }, 26 | { 27 | "releaseDate": "1945-01-01", 28 | "title": "The Lay of Aotrou and Itroun", 29 | "description": "The Lay of Aotrou and Itroun is a poem of 508 lines, written by J. R. R. Tolkien in 1930 and published in Welsh Review in December, 1945.\r\nAotrou and Itroun are Breton words for \"lord\" and \"lady\". The poem is modelled on the genre of the \"Breton lay\" popular in Middle English literature of the 12th century, and it explores the conflict of heroic or chivalric values and Christianity, and their relation to the institution of marriage.", 30 | "author": 1, 31 | "createdUser": 1, 32 | "updatedUser": 1 33 | }, 34 | { 35 | "releaseDate": "1949-01-01", 36 | "title": " Farmer Giles of Ham", 37 | "description": "\"Farmer Giles of Ham\" is a comic Medieval fable written by J. R. R. Tolkien in 1937 and published in 1949. The story describes the encounters between Farmer Giles and a wily dragon named Chrysophylax, and how Giles manages to use these to rise from humble beginnings to rival the king of the land. It is cheerfully anachronistic and light-hearted, set in Britain in an imaginary period of the Dark Ages, and featuring mythical creatures, medieval knights, and primitive firearms. It is only tangentially connected with the author's Middle-earth legendarium: both were originally intended as essays in \"English mythology\".\r\nThe book was originally illustrated by Pauline Baynes. The story has appeared with other works by Tolkien in omnibus editions, including The Tolkien Reader and Tales from the Perilous Realm.", 38 | "author": 1, 39 | "createdUser": 1, 40 | "updatedUser": 1 41 | }, 42 | { 43 | "releaseDate": "1953-01-01", 44 | "title": "The Homecoming of Beorhtnoth Beorhthelm's Son", 45 | "description": "The Homecoming of Beorhtnoth Beorhthelm's Son is the title of a work by J. R. R. Tolkien that was originally published in 1953 in volume 6 of the scholarly journal Essays and Studies by Members of the English Association, and later republished in 1966 in The Tolkien Reader. It is a work of historical fiction, inspired by the Old English poem The Battle of Maldon. It is written in the form of an alliterative poem, but is also a play, being mainly a dialogue between two characters in the aftermath of the Battle of Maldon. The work was accompanied by two essays, also by Tolkien, one before and one after the main work.", 46 | "author": 1, 47 | "createdUser": 1, 48 | "updatedUser": 1 49 | }, 50 | { 51 | "releaseDate": "1954-01-01", 52 | "title": "The Lord of the Rings: The Fellowship of the Ring", 53 | "description": "The Fellowship of the Ring is the first of three volumes of the epic novel The Lord of the Rings by the English author J. R. R. Tolkien. It takes place in the fictional universe of Middle-earth. It was originally published on July 29, 1954 in the United Kingdom. The volume consists of a Prologue titled \"Concerning Hobbits, and other matters\" followed by Book I and Book II.", 54 | "author": 1, 55 | "createdUser": 1, 56 | "updatedUser": 1 57 | }, 58 | { 59 | "releaseDate": "1954-01-01", 60 | "title": "The Lord of the Rings: The Two Towers", 61 | "description": "The Two Towers is the second volume of J. R. R. Tolkien's high fantasy novel The Lord of the Rings. It is preceded by The Fellowship of the Ring and followed by The Return of the King.", 62 | "author": 1, 63 | "createdUser": 1, 64 | "updatedUser": 1 65 | }, 66 | { 67 | "releaseDate": "1955-01-01", 68 | "title": "The Lord of the Rings: The Return of the King", 69 | "description": "The Return of the King is the third and final volume of J. R. R. Tolkien's The Lord of the Rings, following The Fellowship of the Ring and The Two Towers. The story begins in the kingdom of Gondor, which is soon to be attacked by the Dark Lord Sauron.", 70 | "author": 1, 71 | "createdUser": 1, 72 | "updatedUser": 1 73 | }, 74 | { 75 | "releaseDate": "1962-01-01", 76 | "title": "The Adventures of Tom Bombadil", 77 | "description": "The Adventures of Tom Bombadil is a collection of poetry written by J. R. R. Tolkien and published in 1962. The book contains 16 poems, three of which deal with Tom Bombadil (two specifically about Tom, and he also appears in the poem; The Stone Troll), a character who is most famous for his encounter with Frodo Baggins in The Fellowship of the Ring (the first volume in Tolkien's best-selling The Lord of the Rings). The rest of the poems are an assortment of bestiary verse and fairy tale rhyme. Three of the poems appear in The Lord of the Rings as well. The book is part of Tolkien's Middle-earth legendarium and the Middle-earth canon.", 78 | "author": 1, 79 | "createdUser": 1, 80 | "updatedUser": 1 81 | }, 82 | { 83 | "releaseDate": "1964-01-01", 84 | "title": "Tree and Leaf", 85 | "description": "Tree and Leaf is a small book published in 1964, containing two works by J. R. R. Tolkien:
  • a revised version of an essay called \"On Fairy-Stories\" (originally published in 1947 in Essays Presented to Charles Williams)
  • an allegorical short story called \"Leaf by Niggle\" (originally published in the Dublin Review in 1945).
The book was originally illustrated by Pauline Baynes.", 86 | "author": 1, 87 | "createdUser": 1, 88 | "updatedUser": 1 89 | }, 90 | { 91 | "releaseDate": "1966-01-01", 92 | "title": "Bilbo's Last Song", 93 | "description": "\"Bilbo's Last Song\" is a poem by J. R. R. Tolkien. It was given by Tolkien as a gift to his secretary Joy Hill in 1966. Although it was never published in the author's lifetime, it has been published in text form and with music several times since Tolkien's death in 1973.", 94 | "author": 1, 95 | "createdUser": 1, 96 | "updatedUser": 1 97 | }, 98 | { 99 | "releaseDate": "1966-01-01", 100 | "title": "The Tolkien Reader", 101 | "description": "The Tolkien Reader is an anthology of works by J. R. R. Tolkien. It includes a variety of short stories, poems, a play and some non-fiction by Tolkien. It compiles material previously published as three separate shorter books (Tree and Leaf, Farmer Giles of Ham, and The Adventures of Tom Bombadil) together with one additional piece and introductory material. It was published in 1966 by Ballantine Books in the USA.", 102 | "author": 1, 103 | "createdUser": 1, 104 | "updatedUser": 1 105 | }, 106 | { 107 | "releaseDate": "1967-01-01", 108 | "title": "The Road Goes Ever On", 109 | "description": "The Road Goes Ever On is a song cycle that has been published as sheet music and as an audio recording. The music was written by Donald Swann, and the words are taken from poems in J. R. R. Tolkien's Middle-earth writings, especially The Lord of the Rings.\r\nThe title of this opus is taken from \"The Road Goes Ever On\", the first song in the collection. The songs form a song cycle, designed to fit together when played in sequence.", 110 | "author": 1, 111 | "createdUser": 1, 112 | "updatedUser": 1 113 | }, 114 | { 115 | "releaseDate": "1967-01-01", 116 | "title": "Smith of Wootton Major", 117 | "description": "Smith of Wootton Major, first published in 1967, is a novella by J. R. R. Tolkien.", 118 | "author": 1, 119 | "createdUser": 1, 120 | "updatedUser": 1 121 | }, 122 | { 123 | "releaseDate": "1852-01-01", 124 | "title": "Childhood – Volume 1 of 'Autobiographical Trilogy'", 125 | "description": "Childhood (Russian: Детство, Detstvo) is the first published novel by Leo Tolstoy, released under the initials L. N. in the November 1852 issue of the popular Russian literary journal The Contemporary.\r\nIt is the first in a series of three novels and is followed by Boyhood and Youth. Published when Tolstoy was just twenty-three years old, the book was an immediate success, earning notice from other Russian novelists including Ivan Turgenev, who heralded the young Tolstoy as a major up-and-coming figure in Russian literature.\r\nChildhood is an exploration of the inner life of a young boy, Nikolenka, and one of the books in Russian writing to explore an expressionistic style, mixing fact, fiction and emotions to render the moods and reactions of the narrator.", 126 | "author": 2, 127 | "createdUser": 1, 128 | "updatedUser": 1 129 | }, 130 | { 131 | "releaseDate": "1854-01-01", 132 | "title": "Boyhood – Volume 2 of 'Autobiographical Trilogy'", 133 | "description": "Boyhood (Russian: Отрочество, Otrochestvo) is the second novel in Leo Tolstoy's autobiographical trilogy, following Childhood and followed by Youth. The novel was first published in the Russian literary journal Sovremennik in 1854.", 134 | "author": 2, 135 | "createdUser": 1, 136 | "updatedUser": 1 137 | }, 138 | { 139 | "releaseDate": "1856-01-01", 140 | "title": "Youth – Volume 3 of 'Autobiographical Trilogy'", 141 | "description": "Youth (Russian: Юность [Yunost']; 1856) is the third novel in Leo Tolstoy's autobiographical trilogy, following Childhood and Boyhood. It was first published in the popular Russian literary magazine Sovremennik.", 142 | "author": 2, 143 | "createdUser": 1, 144 | "updatedUser": 1 145 | }, 146 | { 147 | "releaseDate": "1863-01-01", 148 | "title": "The Cossacks", 149 | "description": "The Cossacks (Russian: Казаки [Kazaki]) is a short novel by Leo Tolstoy, published in 1863 in the popular literary magazine The Russian Messenger. It was originally called Young Manhood. Both Ivan Turgenev and the Nobel prize-winning Russian writer Ivan Bunin gave the work great praise, Turgenev calling it his favorite work by Tolstoy. Tolstoy began work on the story in August 1853. In August 1857, after having reread Iliad, he vowed to completely rewrite The Cossacks. In February 1862, after having lost badly at cards he finished the novel to help pay his debts. The novel was published in 1863, the same year his first child was born.", 150 | "author": 2, 151 | "createdUser": 1, 152 | "updatedUser": 1 153 | }, 154 | { 155 | "releaseDate": "1869-01-01", 156 | "title": "War and Peace", 157 | "description": "War and Peace (Pre-reform Russian: «Война и миръ», Voyna i mir) is a novel by the Russian author Leo Tolstoy, first published in 1869. The work is epic in scale and is regarded as one of the most important works of world literature. It is considered as Tolstoy's finest literary achievement, along with his other major prose work, Anna Karenina (1873–1877).\r\nWar and Peace delineates in graphic detail events surrounding the French invasion of Russia, and the impact of the Napoleonic era on Tsarist society, as seen through the eyes of five Russian aristocratic families. Portions of an earlier version of the novel, then known as The Year 1805, were serialized in the magazine The Russian Messenger between 1865 and 1867. The novel was first published in its entirety in 1869. Newsweek in 2009 ranked it first in its list of the Top 100 Books. In 2003, the novel was listed at number 20 on the BBC's survey The Big Read.\r\nTolstoy himself, somewhat enigmatically, said of War and Peace that it was \"not a novel, even less is it a poem, and still less a historical chronicle\". Large sections of the work, especially in the later chapters, are philosophical discussion rather than narrative. He went on to elaborate that the best Russian literature does not conform to standard norms and hence hesitated to call War and Peace a novel. (Instead, Tolstoy regarded Anna Karenina as his first true novel.)", 158 | "author": 2, 159 | "createdUser": 1, 160 | "updatedUser": 1 161 | }, 162 | { 163 | "releaseDate": "1877-01-01", 164 | "title": "Anna Karenina", 165 | "description": "Anna Karenina (Russian: «Анна Каренина»; Russian pronunciation: [ˈanːə kɐˈrʲenʲɪnə]) is a novel by the Russian writer Leo Tolstoy, published in serial installments from 1873 to 1877 in the periodical The Russian Messenger. Tolstoy clashed with editor Mikhail Katkov over political issues that arose in the final installment (Tolstoy's unpopular views of volunteers going to Serbia); therefore, the novel's first complete appearance was in book form in 1878.\r\nWidely regarded as a pinnacle in realist fiction, Tolstoy considered Anna Karenina his first true novel, when he came to consider War and Peace to be more than a novel.\r\nFyodor Dostoyevsky declared it to be \"flawless as a work of art\". His opinion was shared by Vladimir Nabokov, who especially admired \"the flawless magic of Tolstoy's style\", and by William Faulkner, who described the novel as \"the best ever written\". The novel is currently enjoying popularity, as demonstrated by a recent poll of 125 contemporary authors by J. Peder Zane, published in 2007 in \"The Top Ten\" in Time, which declared that Anna Karenina is the \"greatest novel ever written\".", 166 | "author": 2, 167 | "createdUser": 1, 168 | "updatedUser": 1 169 | }, 170 | { 171 | "releaseDate": "1899-01-01", 172 | "title": "Resurrection", 173 | "description": "Resurrection (Russian: Воскресение, Voskreseniye), first published in 1899, was the last novel written by Leo Tolstoy. The book is the last of his major long fiction works published in his lifetime. Tolstoy intended the novel as an exposition of injustice of man-made laws and the hypocrisy of institutionalized church. It was first published serially in the popular weekly magazine Niva in an effort to raise funds for the resettlement of the Dukhobors.", 174 | "author": 2, 175 | "createdUser": 1, 176 | "updatedUser": 1 177 | }, 178 | { 179 | "releaseDate": "1984-01-01", 180 | "title": "Neuromancer", 181 | "description": "Neuromancer is a 1984 novel by William Gibson, a seminal work in the cyberpunk genre and the first winner of the science-fiction \"triple crown\" — the Nebula Award, the Philip K. Dick Award, and the Hugo Award. It was Gibson's debut novel and the beginning of the Sprawl trilogy. The novel tells the story of a washed-up computer hacker hired by a mysterious employer to pull off the ultimate hack.", 182 | "author": 3, 183 | "createdUser": 1, 184 | "updatedUser": 1 185 | }, 186 | { 187 | "releaseDate": "1986-01-01", 188 | "title": "Count Zero", 189 | "description": "Count Zero is a science fiction novel written by William Gibson, originally published 1986. It is the second volume of the Sprawl trilogy, which begins with Neuromancer and concludes with Mona Lisa Overdrive, and is a canonical example of the cyberpunk sub-genre.", 190 | "author": 3, 191 | "createdUser": 1, 192 | "updatedUser": 1 193 | }, 194 | { 195 | "releaseDate": "1988-01-01", 196 | "title": "Mona Lisa Overdrive", 197 | "description": "Mona Lisa Overdrive is a cyberpunk novel by William Gibson published in 1988 and the final novel of the Sprawl trilogy, following Neuromancer and Count Zero. It takes place eight years after the events of Count Zero and is set, as were its predecessors, in The Sprawl. The novel was nominated for the Nebula Award for Best Novel, the Hugo Award for Best Novel, and the Locus Award for Best Science Fiction Novel in 1989.", 198 | "author": 3, 199 | "createdUser": 1, 200 | "updatedUser": 1 201 | }, 202 | { 203 | "releaseDate": "1993-01-01", 204 | "title": "Virtual Light", 205 | "description": "Virtual Light is the first book in William Gibson's Bridge trilogy. Virtual Light is a science-fiction novel set in a postmodern, dystopian, cyberpunk future. The term 'Virtual Light' was coined by scientist Stephen Beck to describe a form of instrumentation that produces optical sensations directly in the eye without the use of photons. The novel was a finalist nominee for a Hugo Award, and shortlisted for the Locus Award in 1994.", 206 | "author": 3, 207 | "createdUser": 1, 208 | "updatedUser": 1 209 | }, 210 | { 211 | "releaseDate": "1996-01-01", 212 | "title": "Idoru", 213 | "description": "Idoru is the second book in William Gibson's Bridge trilogy. Idoru is a science-fiction novel set in a postmodern, dystopian, cyberpunk future. The main character, Colin Laney, has a talent for identifying nodal points, analogous to Gibson's own.", 214 | "author": 3, 215 | "createdUser": 1, 216 | "updatedUser": 1 217 | }, 218 | { 219 | "releaseDate": "1999-01-01", 220 | "title": "All Tomorrow's Parties ", 221 | "description": "All Tomorrow's Parties is the final novel in William Gibson's Bridge trilogy. Like its predecessors, All Tomorrow's Parties is a speculative fiction novel set in a postmodern, dystopian, postcyberpunk future. The novel borrows its title from a song by Velvet Underground. It is written in the third person and deals with Gibsonian themes of emergent technology.", 222 | "author": 3, 223 | "createdUser": 1, 224 | "updatedUser": 1 225 | }, 226 | { 227 | "releaseDate": "2003-01-01", 228 | "title": "Pattern Recognition", 229 | "description": "Pattern Recognition is a novel by science fiction writer William Gibson published in 2003. Set in August and September 2002, the story follows Cayce Pollard, a 32-year-old marketing consultant who has a psychological sensitivity to corporate symbols. The action takes place in London, Tokyo, and Moscow as Cayce judges the effectiveness of a proposed corporate symbol and is hired to seek the creators of film clips anonymously posted to the internet.", 230 | "author": 3, 231 | "createdUser": 1, 232 | "updatedUser": 1 233 | }, 234 | { 235 | "releaseDate": "2007-01-01", 236 | "title": "Spook Country", 237 | "description": "Spook Country is a 2007 novel by speculative fiction author William Gibson. A political thriller set in contemporary North America, it followed on from the author's previous novel, Pattern Recognition (2003), and was succeeded in 2010 by Zero History, which featured much of its core cast of characters. The plot comprises the intersecting tales of three protagonists: Hollis Henry, a musician-turned-journalist researching a story on locative art; Tito, a young Cuban-Chinese operative whose family is on occasion in the employ of a renegade ex-CIA agent; and Milgrim, a drug-addled translator held captive by Brown, a strangely authoritarian and secretive man. Themes explored include the ubiquity of locative technology, the eversion of cyberspace and the political climate of the United States in the aftermath of the September 11, 2001 attacks.", 238 | "author": 3, 239 | "createdUser": 1, 240 | "updatedUser": 1 241 | }, 242 | { 243 | "releaseDate": "2010-01-01", 244 | "title": "Zero History", 245 | "description": "Zero History is a novel by William Gibson published in 2010. It concludes the informal trilogy begun by Pattern Recognition (2003) and continued by Spook Country (2007), and features the characters Hollis Henry and Milgrim from the latter novel as its protagonists.", 246 | "author": 3, 247 | "createdUser": 1, 248 | "updatedUser": 1 249 | }, 250 | { 251 | "releaseDate": "1895-01-01", 252 | "title": "The Time Machine", 253 | "description": "The Time Machine is a science fiction novella by H. G. Wells, published in 1895. Wells is generally credited with the popularisation of the concept of time travel by using a vehicle that allows an operator to travel purposefully and selectively. The term \"time machine\", coined by Wells, is now universally used to refer to such a vehicle. This work is an early example of the Dying Earth subgenre.\r\nThe Time Machine has since been adapted into two feature films of the same name, as well as two television versions, and a large number of comic book adaptations. It has also indirectly inspired many more works of fiction in many media.", 254 | "author": 4, 255 | "createdUser": 1, 256 | "updatedUser": 1 257 | }, 258 | { 259 | "releaseDate": "1895-01-01", 260 | "title": "The Wonderful Visit", 261 | "description": "The Wonderful Visit is an 1895 novel by H. G. Wells. With an angel—a creature of fantasy—as protagonist, and taking place in contemporary England, the book could be classified as contemporary fantasy, although the genre was not recognised in Wells's time. The Wonderful Visit also has strong satirical themes, gently mocking customs and institutions of Victorian England as well as idealistic rebellion itself.", 262 | "author": 4, 263 | "createdUser": 1, 264 | "updatedUser": 1 265 | }, 266 | { 267 | "releaseDate": "1896-01-01", 268 | "title": "The Island of Doctor Moreau", 269 | "description": "The Island of Doctor Moreau is an 1896 science fiction novel by H. G. Wells, who called it \"an exercise in youthful blasphemy\". The text of the novel is the narration of Edward Prendick, a shipwrecked man rescued by a passing boat who is left on the island home of Doctor Moreau, who creates human-like beings from animals via vivisection. The novel deals with a number of philosophical themes, including pain and cruelty, moral responsibility, human identity, and human interference with nature.\r\nAt the time of the novel's publication in 1896, there was growing discussion in Europe regarding degeneration and animal vivisection. Two years later, several interest groups were formed to address the issue such as the British Union for the Abolition of Vivisection.", 270 | "author": 4, 271 | "createdUser": 1, 272 | "updatedUser": 1 273 | }, 274 | { 275 | "releaseDate": "1896-01-01", 276 | "title": "The Wheels of Chance", 277 | "description": "The Wheels of Chance is an early comic novel by H. G. Wells about an August 1895 cycling holiday, somewhat in the style of Three Men in a Boat. In 1922 it was adapted into a silent film The Wheels of Chance directed by Harold M. Shaw.", 278 | "author": 4, 279 | "createdUser": 1, 280 | "updatedUser": 1 281 | }, 282 | { 283 | "releaseDate": "1897-01-01", 284 | "title": "The Invisible Man", 285 | "description": "The Invisible Man is a science fiction novella by H. G. Wells published in 1897. Originally serialised in Pearson's Weekly in 1897, it was published as a novel the same year. The Invisible Man of the title is Griffin, a scientist who has devoted himself to research into optics and invents a way to change a body's refractive index to that of air so that it absorbs and reflects no light and thus becomes invisible. He successfully carries out this procedure on himself, but fails in his attempt to reverse the procedure.\r\nWhile its predecessors, The Time Machine and The Island of Doctor Moreau, were written using first-person narrators, Wells adopts a third-person objective point of view in The Invisible Man.", 286 | "author": 4, 287 | "createdUser": 1, 288 | "updatedUser": 1 289 | }, 290 | { 291 | "releaseDate": "1898-01-01", 292 | "title": "The War of the Worlds", 293 | "description": "The War of the Worlds is a science fiction novel by English author H. G. Wells. It first appeared in serialized form in 1897, published simultaneously in Pearson's Magazine in the UK and Cosmopolitan magazine in the US. The first appearance in book form was published by William Heinemann of London in 1898. It is the first-person narrative of the adventures of an unnamed protagonist and his brother in Surrey and London as Earth is invaded by Martians. Written between 1895 and 1897, it is one of the earliest stories that detail a conflict between mankind and an extraterrestrial race. The novel is one of the most commented-on works in the science fiction canon.\r\nThe War of the Worlds has two parts, Book One: The Coming of the Martians and Book Two: The Earth under the Martians. The narrator, a philosophically-inclined author, struggles to return to his wife while seeing the Martians lay waste to southern England. Book One also imparts the experience of his brother, also unnamed, who describes events in the capital and escapes the Martians by boarding a ship near Tillingham, on the Essex coast.\r\nThe plot has been related to invasion literature of the time. The novel has been variously interpreted as a commentary on evolutionary theory, British Imperialism, and generally Victorian superstitions, fears and prejudices. At the time of publication it was classified as a scientific romance, like his earlier novel The Time Machine. The War of the Worlds has been both popular (having never gone out of print) and influential, spawning half a dozen feature films, radio dramas, a record album, various comic book adaptations, a television series, and sequels or parallel stories by other authors. It has even influenced the work of scientists, notably Robert Hutchings Goddard.", 294 | "author": 4, 295 | "createdUser": 1, 296 | "updatedUser": 1 297 | }, 298 | { 299 | "releaseDate": "1899-01-01", 300 | "title": "When the Sleeper Wakes", 301 | "description": "The Sleeper Awakes (1910) is a dystopian science fiction novel by H. G. Wells about a man who sleeps for two hundred and three years, waking up in a completely transformed London, where, because of compound interest on his bank accounts, he has become the richest man in the world. The main character awakes to see his dreams realised, and the future revealed to him in all its horrors and malformities.\r\nThe novel is a rewritten version of When the Sleeper Wakes a story by Wells that was serialised between 1898 and 1899.", 302 | "author": 4, 303 | "createdUser": 1, 304 | "updatedUser": 1 305 | }, 306 | { 307 | "releaseDate": "1900-01-01", 308 | "title": "Love and Mr Lewisham", 309 | "description": "Love and Mr Lewisham is an 1899 novel set in the 1880s by H. G. Wells. It was among his first outside the science fiction genre. Wells took considerable pains over the manuscript and said of it that \"the writing was an altogether more serious undertaking than I have ever done before.\" He later included it in a 1933 anthology entitled Stories of Men and Women in Love.", 310 | "author": 4, 311 | "createdUser": 1, 312 | "updatedUser": 1 313 | }, 314 | { 315 | "releaseDate": "1901-01-01", 316 | "title": "The First Men in the Moon", 317 | "description": "The First Men in the Moon is a scientific romance published in 1901 by the English author H. G. Wells, who called it one of his \"fantastic stories\". The novel tells the story of a journey to the moon undertaken by the two protagonists, a businessman narrator, Mr. Bedford, and an eccentric scientist, Mr. Cavor. Bedford and Cavor discover that the moon is inhabited by a sophisticated extraterrestrial civilization of insect-like creatures they call \"Selenites\".", 318 | "author": 4, 319 | "createdUser": 1, 320 | "updatedUser": 1 321 | }, 322 | { 323 | "releaseDate": "1863-01-01", 324 | "title": "Five Weeks", 325 | "description": "Five Weeks in a Balloon, or, Journeys and Discoveries in Africa by Three Englishmen (French: Cinq semaines en ballon) is an adventure novel by Jules Verne.\r\nIt is the first Verne novel in which he perfected the \"ingredients\" of his later work, skillfully mixing a plot full of adventure and twists that hold the reader's interest with passages of technical, geographic, and historic description. The book gives readers a glimpse of the exploration of Africa, which was still not completely known to Europeans of the time, with explorers traveling all over the continent in search of its secrets.\r\nPublic interest in fanciful tales of African exploration was at its height, and the book was an instant hit; it made Verne financially independent and got him a contract with Jules Hetzel's publishing house, which put out several dozen more works of his for over forty years afterward.", 326 | "author": 5, 327 | "createdUser": 1, 328 | "updatedUser": 1 329 | }, 330 | { 331 | "releaseDate": "1866-01-01", 332 | "title": "The Adventures of Captain Hatteras", 333 | "description": "The Adventures of Captain Hatteras (French: Voyages et aventures du capitaine Hatteras) is an adventure novel by Jules Verne in two parts: The English at the North Pole (French: Les Anglais au pôle nord) and The desert of ice (French: Le Désert de glace).\r\nThe novel was published for the first time in 1864. The definitive version from 1866 was included into Voyages Extraordinaires series (The Extraordinary Voyages). Although it was the first book of the series it was labeled as number two. Three of Verne's books from 1863-65 (Five Weeks in a Balloon, Journey to the Center of the Earth, and From the Earth to the Moon) were added into the series retroactively. Captain Hatteras shows many similarities with British explorer John Franklin.", 334 | "author": 5, 335 | "createdUser": 1, 336 | "updatedUser": 1 337 | }, 338 | { 339 | "releaseDate": "1864-01-01", 340 | "title": "Journey to the Center of the Earth", 341 | "description": "Journey to the Center of the Earth (French: Voyage au centre de la Terre, also translated under the titles A Journey to the Centre of the Earth and A Journey to the Interior of the Earth) is a classic 1864 science fiction novel by Jules Verne. The story involves German professor Otto Lidenbrock who believes there are volcanic tubes going toward the centre of the Earth. He, his nephew Axel, and their guide Hans descend into the Icelandic volcano Snæfellsjökull, encountering many adventures, including prehistoric animals and natural hazards, before eventually coming to the surface again in southern Italy, at the Stromboli volcano.\r\nFrom a scientific point of view, this story has not aged quite as well as other Verne stories, since most of his ideas about what the interior of the Earth contains have since been disproved, but it still manages to captivate audiences when regarded as a classic fantasy novel.", 342 | "author": 5, 343 | "createdUser": 1, 344 | "updatedUser": 1 345 | }, 346 | { 347 | "releaseDate": "1865-01-01", 348 | "title": "From the Earth to the Moon", 349 | "description": "From the Earth to the Moon (French: De la terre à la lune) is an 1865 novel by Jules Verne. It tells the story of the Baltimore Gun Club, a post-American Civil War society of weapons aficionados, and their attempts to build an enormous sky-facing Columbiad space gun and launch three people — the Gun Club's president, his Philadelphian armor-making rival, and a French poet — in a projectile with the goal of a moon landing.\r\nThe story is also notable in that Verne attempted to do some rough calculations as to the requirements for the cannon and, considering the comparative lack of any data on the subject at the time, some of his figures are surprisingly close to reality. However, his scenario turned out to be impractical for safe manned space travel since a much longer muzzle would have been required to reach escape velocity while limiting acceleration to survivable limits for the passengers.\r\nThe character of Michel Ardan, the French poet in the novel, was inspired by the real-life photographer Félix Nadar.", 350 | "author": 5, 351 | "createdUser": 1, 352 | "updatedUser": 1 353 | }, 354 | { 355 | "releaseDate": "1873-01-01", 356 | "title": "In Search of the Castaways", 357 | "description": "In Search of the Castaways (French: Les Enfants du capitaine Grant, lit. The Children of Captain Grant) is a novel by the French writer Jules Verne, published in 1867–1868. The original edition, published by Hetzel, contains a number of illustrations by Édouard Riou. In 1876 it was republished by George Routledge & Sons as a three volume set titled \"A Voyage Round The World\". The three volumes were subtitled \"South America\", \"Australia\", and \"New Zealand\". (As often with Verne, English translations have appeared under different names; another edition has the overall title \"Captain Grant's Children\" and has two volumes subtitled \"The Mysterious Document\" and \"Among the Cannibals\".)", 358 | "author": 5, 359 | "createdUser": 1, 360 | "updatedUser": 1 361 | }, 362 | { 363 | "releaseDate": "1870-01-01", 364 | "title": "Twenty Thousand Leagues Under the Sea", 365 | "description": "Twenty Thousand Leagues Under the Sea (French: Vingt mille lieues sous les mers) is a classic science fiction novel by French writer Jules Verne published in 1870. It tells the story of Captain Nemo and his submarine Nautilus, as seen from the perspective of Professor Pierre Aronnax after he, his servant Conseil, and Canadian harpoonist Ned Land wash up on their ship. On the Nautilus, the three embark on a journey which has them going all around the world.\r\nThe original edition had no illustrations; the first illustrated edition was published by Hetzel with illustrations by Alphonse de Neuville and Édouard Riou. The book was highly acclaimed when released and still is now; it is regarded as one of the premiere adventure novels and one of Verne's greatest works, along with Around the World in Eighty Days and Journey to the Center of the Earth. The description of Nemo's ship, the Nautilus, was considered ahead of its time, as it accurately describes features on submarines, which at the time were very primitive vessels. Thus, the book has been able to age well because of its scientific theories, unlike some other of Verne's works, like Journey to the Center of the Earth, which are not scientifically accurate and serve more simply as adventure novels.", 366 | "author": 5, 367 | "createdUser": 1, 368 | "updatedUser": 1 369 | }, 370 | { 371 | "releaseDate": "1870-01-01", 372 | "title": "Around the Moon", 373 | "description": "Around the Moon (French: Autour de la Lune, 1870), Jules Verne's sequel to From the Earth to the Moon, is a science fiction novel continuing the trip to the moon which left the reader in suspense after the previous novel. It was later combined with From the Earth to the Moon to create A Trip to the Moon and Around It.", 374 | "author": 5, 375 | "createdUser": 1, 376 | "updatedUser": 1 377 | }, 378 | { 379 | "releaseDate": "1871-01-01", 380 | "title": "A Floating City", 381 | "description": "A Floating City (French: Une vil le flottante) is an adventure novel by French writer Jules Verne first published in 1871. It tells of a woman who, on board the ship Great Eastern with her abusive husband, finds that the man she loves is also on board", 382 | "author": 5, 383 | "createdUser": 1, 384 | "updatedUser": 1 385 | } 386 | ] -------------------------------------------------------------------------------- /test/fixtures/Message.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "nick": "da_wunder", 4 | "message": "Hello World!" 5 | } 6 | ] -------------------------------------------------------------------------------- /test/fixtures/Passport.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "protocol": "local", 4 | "password": "adminadminadmin", 5 | "user": 1 6 | }, 7 | { 8 | "protocol": "local", 9 | "password": "demodemodemo", 10 | "user": 2 11 | } 12 | ] -------------------------------------------------------------------------------- /test/fixtures/User.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "username": "admin", 5 | "email": "admin@some.domain", 6 | "firstName": "Arnold", 7 | "lastName": "Administrator", 8 | "admin": true 9 | }, 10 | { 11 | "id": 2, 12 | "username": "demo", 13 | "email": "demo@some.domain", 14 | "firstName": "John", 15 | "lastName": "Doe", 16 | "admin": false, 17 | "createdUser": 1, 18 | "updatedUser": 1 19 | } 20 | ] -------------------------------------------------------------------------------- /test/fixtures/UserLogin.json: -------------------------------------------------------------------------------- 1 | [ 2 | ] 3 | -------------------------------------------------------------------------------- /test/functional/controllers/AuthController.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var request = require('supertest'); 4 | var expect = require('chai').expect; 5 | var login = require("./../../helpers/login"); 6 | var _ = require('lodash'); 7 | var Barrels = require('barrels'); 8 | var barrels = new Barrels(); 9 | 10 | describe('AuthController', function AuthController() { 11 | describe('action login', function loginTest() { 12 | [ 13 | { 14 | payload: null, 15 | status: 401, 16 | condition: 'null as login payload' 17 | }, 18 | { 19 | payload: '', 20 | status: 401, 21 | condition: 'empty string "" as login payload' 22 | }, 23 | { 24 | payload: 'foobar', 25 | status: 401, 26 | condition:'dummy string "foobar" as login payload' 27 | }, 28 | { 29 | payload: {}, 30 | status: 401, 31 | condition:'empty object {} as login payload' 32 | }, 33 | { 34 | payload: { 35 | identifier: '', 36 | password: '' 37 | }, 38 | status: 401, 39 | condition: 'empty string "" identifier and empty string "" password as login payload' 40 | }, 41 | { 42 | payload: { 43 | identifier: 'foo', 44 | password: '' 45 | }, 46 | status: 401, 47 | condition: 'dummy string "foo" identifier and empty string "" password as login payload' 48 | }, 49 | { 50 | payload: { 51 | identifier: '', 52 | password: 'bar' 53 | }, 54 | status: 401, 55 | condition: 'empty string "" identifier and dummy string "bar" password as login payload' 56 | }, 57 | { 58 | payload: { 59 | identifier: 'foo', 60 | password: 'bar' 61 | }, 62 | status: 401, 63 | condition: 'dummy string "foo" identifier and dummy string "bar" password as login payload' 64 | }, 65 | { 66 | payload: { 67 | identifier: 'demo', 68 | password: 'demodemodemo' 69 | }, 70 | status: 200, 71 | condition: 'valid "demo" user username and password as login payload' 72 | }, 73 | { 74 | payload: { 75 | identifier: 'admin', 76 | password: 'adminadminadmin' 77 | }, 78 | status: 200, 79 | condition: 'valid "admin" user username and password as login payload' 80 | } 81 | ].forEach(function testCase(testCase, index) { 82 | describe('TestCase #' + (parseInt(index, 10) + 1) + ' - ' + testCase.condition, function loginTest() { 83 | it('should return expected HTTP status and object as response body', function it(done) { 84 | request(sails.hooks.http.app) 85 | .post('/login') 86 | .send(testCase.payload) 87 | .expect(testCase.status) 88 | .end( 89 | function(error, result) { 90 | if (error) { 91 | return done(error); 92 | } 93 | 94 | expect(result.res.body).to.be.a('object'); 95 | 96 | done(); 97 | } 98 | ); 99 | }); 100 | }); 101 | }); 102 | 103 | describe('after successfully login', function successfullyLogin() { 104 | beforeEach(function beforeEach(done) { 105 | barrels.populate(['userlogin'], done); 106 | }); 107 | 108 | [ 109 | { 110 | credential: 'demo', 111 | condition: 'using demo user credentials' 112 | }, 113 | { 114 | credential: 'admin', 115 | condition: 'using admin user credentials' 116 | } 117 | ].forEach(function testCase(testCase, index) { 118 | describe('TestCase #' + (parseInt(index, 10) + 1) + ' - ' + testCase.condition, function loginTest() { 119 | it('should write user login information to database', function it(done) { 120 | login.authenticate(testCase.credential, function callback(error) { 121 | if (error) { 122 | return done(error); 123 | } 124 | 125 | sails.models['userlogin'] 126 | .find() 127 | .exec(function(error, results) { 128 | if (error) { 129 | return done(error); 130 | } 131 | 132 | expect(results).to.be.a('array'); 133 | expect(results).to.have.length(1); 134 | 135 | done(); 136 | }); 137 | }); 138 | }); 139 | }); 140 | }); 141 | }); 142 | }); 143 | 144 | describe('action logout', function logoutTest() { 145 | it('should return HTTP status 200', function it(done) { 146 | request(sails.hooks.http.app) 147 | .get('/auth/logout') 148 | .expect(200) 149 | .end( 150 | function end(error, result) { 151 | if (error) { 152 | return done(error); 153 | } 154 | 155 | expect(result.res.body).to.be.true; 156 | 157 | done(); 158 | } 159 | ); 160 | }); 161 | }); 162 | 163 | describe('action checkPassword', function checkPasswordTest() { 164 | [ 165 | { 166 | credential: 'demo', 167 | password: 'demodemodemo', 168 | condition: 'using demo user credentials' 169 | }, 170 | { 171 | credential: 'admin', 172 | password: 'adminadminadmin', 173 | condition: 'using admin user credentials' 174 | } 175 | ].forEach(function testCase(testCase, index) { 176 | var token = ''; 177 | 178 | before(function beforeTest(done) { 179 | login.authenticate(testCase.credential, function callback(error, result) { 180 | if (!error) { 181 | token = result.token; 182 | } 183 | 184 | done(error); 185 | }); 186 | }); 187 | 188 | describe('TestCase #' + (parseInt(index, 10) + 1) + ' - ' + testCase.condition, function loginTest() { 189 | describe('with invalid authorization header', function() { 190 | it('should return HTTP status 401 with expected body', function it(done) { 191 | request(sails.hooks.http.app) 192 | .post('/auth/checkPassword') 193 | .expect(401) 194 | .end( 195 | function end(error, result) { 196 | if (error) { 197 | return done(error); 198 | } 199 | 200 | expect(result.res.body).to.deep.equal({message: 'No authorization header was found'}); 201 | 202 | done(); 203 | } 204 | ); 205 | }); 206 | }); 207 | 208 | describe('with valid authorization header', function() { 209 | describe('and invalid password', function() { 210 | it('should return HTTP status 401 with expected body', function it(done) { 211 | request(sails.hooks.http.app) 212 | .post('/auth/checkPassword') 213 | .set('Authorization', 'bearer ' + token) 214 | .set('Content-Type', 'application/json') 215 | .send({password: "invalid password"}) 216 | .expect(400) 217 | .end( 218 | function end(error, result) { 219 | if (error) { 220 | return done(error); 221 | } 222 | 223 | expect(result.res.body).to.deep.equal({message: 'Given password does not match.'}); 224 | 225 | done(); 226 | } 227 | ); 228 | }); 229 | }); 230 | 231 | describe('and valid password', function() { 232 | it('should return HTTP status 200 with expected body', function it(done) { 233 | request(sails.hooks.http.app) 234 | .post('/auth/checkPassword') 235 | .set('Authorization', 'bearer ' + token) 236 | .set('Content-Type', 'application/json') 237 | .send({password: testCase.password}) 238 | .expect(200) 239 | .end( 240 | function end(error, result) { 241 | if (error) { 242 | return done(error); 243 | } 244 | 245 | expect(result.res.body).to.be.true; 246 | 247 | done(); 248 | } 249 | ); 250 | }); 251 | }); 252 | }); 253 | }); 254 | }) 255 | }); 256 | }); 257 | -------------------------------------------------------------------------------- /test/helpers/README.MD: -------------------------------------------------------------------------------- 1 | # test/helpers 2 | This folder contains helper functions for backend side tests. Generally these helpers are just node modules that 3 | can be required in each test where those are needed. 4 | 5 | ## login.js 6 | This file contains helper functions for authenticate process for sails.js backend. 7 | 8 | ### authenticate(user, callback) 9 | This will make actual authenticate process (login) to backend with specified user and returns user data and 10 | authorization JSON Web Token as in results. 11 | 12 | #### Usage example 13 | ```javascript 14 | var request = require('supertest'); 15 | var expect = require('chai').expect; 16 | var login = require("./helpers/login"); 17 | 18 | describe('your test description', function testSet() { 19 | var token = ''; 20 | 21 | before(function beforeTest(done) { 22 | login.authenticate('demo', function callback(error, result) { 23 | if (!error) { 24 | token = result.token; 25 | } 26 | 27 | done(error); 28 | }); 29 | }); 30 | 31 | describe('another test description', function findRecords() { 32 | it('endpoint should return HTTP status 200 with array of objects as in body', function it(done) { 33 | request(sails.hooks.http.app) 34 | .get('/endpointUrlHere') 35 | .set('Authorization', 'bearer ' + token) 36 | .set('Content-Type', 'application/json') 37 | .expect(200) 38 | .end( 39 | function end(error, result) { 40 | if (error) { 41 | return done(error); 42 | } 43 | 44 | expect(result.res.body).to.be.a('array'); 45 | expect(result.res.body).to.have.length(123); 46 | 47 | result.res.body.forEach(function(value) { 48 | expect(value).to.be.a('object'); 49 | }); 50 | 51 | done(); 52 | } 53 | ); 54 | }); 55 | }); 56 | }); 57 | ``` -------------------------------------------------------------------------------- /test/helpers/login.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var request = require("supertest"); 4 | 5 | /** 6 | * Generic helper function to authenticate specified user with current sails testing instance. Function 7 | * will call specified callback function with response (res) body, which contains all necessary user data 8 | * and Json Web Token, which is required to make actual API calls to backend. 9 | * 10 | * @param {String} user User which to use within login 11 | * @param {Function} next Callback function which is called after login attempt 12 | */ 13 | module.exports.authenticate = function authenticate(user, next) { 14 | // Static credential information, which are used within tests. 15 | var credentials = { 16 | demo: { 17 | identifier: 'demo', 18 | password: 'demodemodemo' 19 | }, 20 | admin: { 21 | identifier: 'admin', 22 | password: 'adminadminadmin' 23 | } 24 | }; 25 | 26 | request(sails.hooks.http.app) 27 | .post('/login') 28 | .set('Content-Type', 'application/json') 29 | .send(credentials[user]) 30 | .expect(200) 31 | .end( 32 | function(error, result) { 33 | if (error) { 34 | next(error); 35 | } else { 36 | next(null, result.res.body); 37 | } 38 | } 39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --timeout 60000 2 | --reporter spec 3 | --ui bdd 4 | test/**/*.test.js --------------------------------------------------------------------------------