From 8e05164678d302700c794feecd50d83357f7719d Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Thu, 16 Mar 2017 15:50:10 +0100 Subject: feathers stuff --- .editorconfig | 13 +++++++ .gitignore | 31 +++++++++++++++ .jshintrc | 29 ++++++++++++++ .npmignore | 30 ++++++++++++++ LICENSE | 22 +++++++++++ README | 2 + README.md | 57 +++++++++++++++++++++++++++ config/default.json | 13 +++++++ config/production.json | 13 +++++++ package.json | 50 ++++++++++++++++++++---- public/favicon.ico | Bin 0 -> 5533 bytes public/index.html | 73 +++++++++++++++++++++++++++++++++++ src/app.js | 33 ++++++++++++++++ src/hooks/index.js | 19 +++++++++ src/index.js | 9 +++++ src/middleware/index.js | 16 ++++++++ src/middleware/logger.js | 24 ++++++++++++ src/middleware/not-found-handler.js | 9 +++++ src/services/authentication/index.js | 14 +++++++ src/services/index.js | 18 +++++++++ src/services/meal/hooks/index.js | 31 +++++++++++++++ src/services/meal/index.js | 29 ++++++++++++++ src/services/meal/meal-model.js | 38 ++++++++++++++++++ src/services/user/hooks/index.js | 51 ++++++++++++++++++++++++ src/services/user/index.js | 29 ++++++++++++++ src/services/user/user-model.js | 36 +++++++++++++++++ test/app.test.js | 51 ++++++++++++++++++++++++ test/services/meal/index.test.js | 10 +++++ test/services/user/index.test.js | 10 +++++ 29 files changed, 752 insertions(+), 8 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .jshintrc create mode 100644 .npmignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 config/default.json create mode 100644 config/production.json create mode 100644 public/favicon.ico create mode 100644 public/index.html create mode 100644 src/app.js create mode 100644 src/hooks/index.js create mode 100644 src/index.js create mode 100644 src/middleware/index.js create mode 100644 src/middleware/logger.js create mode 100644 src/middleware/not-found-handler.js create mode 100644 src/services/authentication/index.js create mode 100644 src/services/index.js create mode 100644 src/services/meal/hooks/index.js create mode 100644 src/services/meal/index.js create mode 100644 src/services/meal/meal-model.js create mode 100644 src/services/user/hooks/index.js create mode 100644 src/services/user/index.js create mode 100644 src/services/user/user-model.js create mode 100644 test/app.test.js create mode 100644 test/services/meal/index.test.js create mode 100644 test/services/user/index.test.js diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e717f5e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3cd787d --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# Commenting this out is preferred by some people, see +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- +node_modules + +# Users Environment Variables +.lock-wscript + +lib/ +data/ diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..1c271e2 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,29 @@ +{ + "node": true, + "esnext": true, + "bitwise": true, + "camelcase": true, + "curly": true, + "eqeqeq": true, + "immed": true, + "indent": 2, + "latedef": "nofunc", + "newcap": false, + "noarg": true, + "quotmark": "single", + "regexp": true, + "undef": true, + "unused": false, + "strict": false, + "trailing": true, + "smarttabs": true, + "white": false, + "globals": { + "it": true, + "describe": true, + "before": true, + "beforeEach": true, + "after": true, + "afterEach": true + } +} diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..40e14ef --- /dev/null +++ b/.npmignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# Commenting this out is preferred by some people, see +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- +node_modules + +# Users Environment Variables +.lock-wscript + +data/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..40b7881 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Feathers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README b/README index e69de29..957cd2d 100644 --- a/README +++ b/README @@ -0,0 +1,2 @@ +calorie counter +=============== diff --git a/README.md b/README.md new file mode 100644 index 0000000..141e92e --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# feathers-test + +> testing this thing + +## About + +This project uses [Feathers](http://feathersjs.com). An open source web framework for building modern real-time applications. + +## Getting Started + +Getting up and running is as easy as 1, 2, 3. + +1. Make sure you have [NodeJS](https://nodejs.org/) and [npm](https://www.npmjs.com/) installed. +2. Install your dependencies + + ``` + cd path/to/feathers-test; npm install + ``` + +3. Start your app + + ``` + npm start + ``` + +## Testing + +Simply run `npm test` and all your tests in the `test/` directory will be run. + +## Scaffolding + +Feathers has a powerful command line interface. Here are a few things it can do: + +``` +$ npm install -g feathers-cli # Install Feathers CLI + +$ feathers generate service # Generate a new Service +$ feathers generate hook # Generate a new Hook +$ feathers generate model # Generate a new Model +$ feathers help # Show all commands +``` + +## Help + +For more information on all the things you can do with Feathers visit [docs.feathersjs.com](http://docs.feathersjs.com). + +## Changelog + +__0.1.0__ + +- Initial release + +## License + +Copyright (c) 2016 + +Licensed under the [MIT license](LICENSE). diff --git a/config/default.json b/config/default.json new file mode 100644 index 0000000..73eac90 --- /dev/null +++ b/config/default.json @@ -0,0 +1,13 @@ +{ + "host": "localhost", + "port": 3030, + "mysql": "mysql://featherstest:password@localhost:3306/feathersTest", + "public": "../public/", + "auth": { + "idField": "id", + "token": { + "secret": "bS83fhImJ5QH0swwuS2o4bT8SPAzwxNZMzUYvPT6szyI+y5xRT6MPcv+iU0dIdMS+oLDfX3dm47HfXwgHsCguQ==" + }, + "local": {} + } +} diff --git a/config/production.json b/config/production.json new file mode 100644 index 0000000..b3bfa5a --- /dev/null +++ b/config/production.json @@ -0,0 +1,13 @@ +{ + "host": "feathers-test-app.feathersjs.com", + "port": 80, + "mysql": "DATABASE_URL", + "public": "../public/", + "auth": { + "idField": "id", + "token": { + "secret": "FEATHERS_AUTH_SECRET" + }, + "local": {} + } +} diff --git a/package.json b/package.json index 6d3cced..643d501 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,49 @@ { "name": "toptal-calorie-counter", + "description": "calorie counter assignment", "version": "1.0.0", - "description": "toptal calorie counter", - "main": "index.js", + "homepage": "", + "main": "src/", + "keywords": [ + "feathers" + ], + "license": "MIT", + "repository": {}, + "author": {}, + "contributors": [], + "bugs": {}, + "engines": { + "node": ">= 0.12.0" + }, "scripts": { - "test": "mocha -R spec" + "test": "npm run jshint && npm run mocha", + "jshint": "jshint src/. test/. --config", + "start": "node src/", + "mocha": "mocha test/ --recursive" }, - "repository": { - "type": "git", - "url": "git@git.toptal.com:eluttner/julian-laplace.git" + "dependencies": { + "body-parser": "^1.17.1", + "compression": "^1.6.2", + "cors": "^2.8.1", + "feathers": "^2.1.1", + "feathers-authentication": "^0.7.12", + "feathers-configuration": "^0.3.3", + "feathers-errors": "^2.6.1", + "feathers-hooks": "^1.8.1", + "feathers-rest": "^1.7.1", + "feathers-sequelize": "^1.4.2", + "mysql": "^2.13.0", + "passport": "^0.3.2", + "promise-or": "^1.0.0", + "sequelize": "^3.30.2", + "serve-favicon": "^2.4.1", + "winston": "^2.3.1" }, - "author": "jules laplace ", - "license": "UNLICENSED" + "devDependencies": { + "chai": "^3.5.0", + "chai-http": "^3.0.0", + "jshint": "^2.9.4", + "mocha": "^3.2.0", + "request": "^2.81.0" + } } diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..7ed25a6 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..02b4b12 --- /dev/null +++ b/public/index.html @@ -0,0 +1,73 @@ + + + Welcome to Feathers + + + +
+ +

A minimalist real-time framework for tomorrow's apps.

+ + +
+ + diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..9830783 --- /dev/null +++ b/src/app.js @@ -0,0 +1,33 @@ +'use strict'; + +const path = require('path'); +const serveStatic = require('feathers').static; +const favicon = require('serve-favicon'); +const compress = require('compression'); +const cors = require('cors'); +const feathers = require('feathers'); +const configuration = require('feathers-configuration'); +const hooks = require('feathers-hooks'); +const rest = require('feathers-rest'); +const bodyParser = require('body-parser'); + +const middleware = require('./middleware'); +const services = require('./services'); + +const app = feathers(); + +app.configure(configuration(path.join(__dirname, '..'))); + +app.use(compress()) + .options('*', cors()) + .use(cors()) + .use(favicon( path.join(app.get('public'), 'favicon.ico') )) + .use('/', serveStatic( app.get('public') )) + .use(bodyParser.json()) + .use(bodyParser.urlencoded({ extended: true })) + .configure(hooks()) + .configure(rest()) + .configure(services) + .configure(middleware); + +module.exports = app; diff --git a/src/hooks/index.js b/src/hooks/index.js new file mode 100644 index 0000000..2b122bb --- /dev/null +++ b/src/hooks/index.js @@ -0,0 +1,19 @@ +'use strict'; + +// Add any common hooks you want to share across services in here. +// +// Below is an example of how a hook is written and exported. Please +// see http://docs.feathersjs.com/hooks/readme.html for more details +// on hooks. + +const hooks = require('feathers-hooks'); +const auth = require('feathers-authentication').hooks; +const or = require('promise-or'); + +exports.restrictToOwnersOrAdmins = function() { + var ownerHook = auth.restrictToOwner() + var adminHook = auth.restrictToRoles({ roles: ["admin"] }) + return function(hook) { + return or(ownerHook(hook), adminHook(hook)) + }; +}; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..30e2908 --- /dev/null +++ b/src/index.js @@ -0,0 +1,9 @@ +'use strict'; + +const app = require('./app'); +const port = app.get('port'); +const server = app.listen(port); + +server.on('listening', () => + console.log(`Feathers application started on ${app.get('host')}:${port}`) +); \ No newline at end of file diff --git a/src/middleware/index.js b/src/middleware/index.js new file mode 100644 index 0000000..6d655fa --- /dev/null +++ b/src/middleware/index.js @@ -0,0 +1,16 @@ +'use strict'; + +const handler = require('feathers-errors/handler'); +const notFound = require('./not-found-handler'); +const logger = require('./logger'); + +module.exports = function() { + // Add your custom middleware here. Remember, that + // just like Express the order matters, so error + // handling middleware should go last. + const app = this; + + app.use(notFound()); + app.use(logger(app)); + app.use(handler()); +}; diff --git a/src/middleware/logger.js b/src/middleware/logger.js new file mode 100644 index 0000000..22ea137 --- /dev/null +++ b/src/middleware/logger.js @@ -0,0 +1,24 @@ +'use strict'; + +const winston = require('winston'); + +module.exports = function(app) { + // Add a logger to our app object for convenience + app.logger = winston; + + return function(error, req, res, next) { + if (error) { + const message = `${error.code ? `(${error.code}) ` : '' }Route: ${req.url} - ${error.message}`; + + if (error.code === 404) { + winston.info(message); + } + else { + winston.error(message); + winston.info(error.stack); + } + } + + next(error); + }; +}; diff --git a/src/middleware/not-found-handler.js b/src/middleware/not-found-handler.js new file mode 100644 index 0000000..cb0ee51 --- /dev/null +++ b/src/middleware/not-found-handler.js @@ -0,0 +1,9 @@ +'use strict'; + +const errors = require('feathers-errors'); + +module.exports = function() { + return function(req, res, next) { + next(new errors.NotFound('Page not found')); + }; +}; diff --git a/src/services/authentication/index.js b/src/services/authentication/index.js new file mode 100644 index 0000000..d5a66b5 --- /dev/null +++ b/src/services/authentication/index.js @@ -0,0 +1,14 @@ +'use strict'; + +const authentication = require('feathers-authentication'); + + +module.exports = function() { + const app = this; + + let config = app.get('auth'); + + + + app.configure(authentication(config)); +}; diff --git a/src/services/index.js b/src/services/index.js new file mode 100644 index 0000000..a05c097 --- /dev/null +++ b/src/services/index.js @@ -0,0 +1,18 @@ +'use strict'; +const authentication = require('./authentication'); +const user = require('./user'); +const meal = require('./meal'); +const Sequelize = require('sequelize'); +module.exports = function() { + const app = this; + + const sequelize = new Sequelize(app.get('mysql'), { + dialect: 'mysql', + logging: false + }); + app.set('sequelize', sequelize); + + app.configure(authentication); + app.configure(user); + app.configure(meal); +}; diff --git a/src/services/meal/hooks/index.js b/src/services/meal/hooks/index.js new file mode 100644 index 0000000..2e2795a --- /dev/null +++ b/src/services/meal/hooks/index.js @@ -0,0 +1,31 @@ +'use strict'; + +const globalHooks = require('../../../hooks'); +const hooks = require('feathers-hooks'); +const auth = require('feathers-authentication').hooks; + +exports.before = { + all: [ + auth.verifyToken(), + auth.populateUser(), + auth.restrictToAuthenticated(), + globalHooks.restrictToOwnersOrAdmins(), + ], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] +}; + +exports.after = { + all: [], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] +}; + diff --git a/src/services/meal/index.js b/src/services/meal/index.js new file mode 100644 index 0000000..88ca1bd --- /dev/null +++ b/src/services/meal/index.js @@ -0,0 +1,29 @@ +'use strict'; + +const service = require('feathers-sequelize'); +const meal = require('./meal-model'); +const hooks = require('./hooks'); + +module.exports = function(){ + const app = this; + + const options = { + Model: meal(app.get('sequelize')), + paginate: { + default: 5, + max: 25 + } + }; + + // Initialize our service with any options it requires + app.use('/meals', service(options)); + + // Get our initialize service to that we can bind hooks + const mealService = app.service('/meals'); + + // Set up our before hooks + mealService.before(hooks.before); + + // Set up our after hooks + mealService.after(hooks.after); +}; diff --git a/src/services/meal/meal-model.js b/src/services/meal/meal-model.js new file mode 100644 index 0000000..d15b244 --- /dev/null +++ b/src/services/meal/meal-model.js @@ -0,0 +1,38 @@ +'use strict'; + +// meal-model.js - A sequelize model +// +// See http://docs.sequelizejs.com/en/latest/docs/models-definition/ +// for more of what you can do here. + +const Sequelize = require('sequelize'); + +module.exports = function(sequelize) { + const meal = sequelize.define('meals', { + name: { + type: Sequelize.STRING, + allowNull: false + }, + date: { + type: Sequelize.DATE, + allowNull: false + }, + calories: { + type: Sequelize.INTEGER, + allowNull: false + }, + user_id: { + type: Sequelize.INTEGER, + references: { + model: sequelize.model('users'), + key: 'id', + } + }, + }, { + freezeTableName: true + }); + + meal.sync(); + + return meal; +}; diff --git a/src/services/user/hooks/index.js b/src/services/user/hooks/index.js new file mode 100644 index 0000000..9dfe425 --- /dev/null +++ b/src/services/user/hooks/index.js @@ -0,0 +1,51 @@ +'use strict'; + +const globalHooks = require('../../../hooks'); +const hooks = require('feathers-hooks'); +const auth = require('feathers-authentication').hooks; + +exports.before = { + all: [], + find: [ + auth.verifyToken(), + auth.populateUser(), + auth.restrictToAuthenticated() + ], + get: [ + auth.verifyToken(), + auth.populateUser(), + auth.restrictToAuthenticated(), + auth.restrictToOwner({ ownerField: 'id' }) + ], + create: [ + auth.hashPassword() + ], + update: [ + auth.verifyToken(), + auth.populateUser(), + auth.restrictToAuthenticated(), + auth.restrictToOwner({ ownerField: 'id' }) + ], + patch: [ + auth.verifyToken(), + auth.populateUser(), + auth.restrictToAuthenticated(), + auth.restrictToOwner({ ownerField: 'id' }) + ], + remove: [ + auth.verifyToken(), + auth.populateUser(), + auth.restrictToAuthenticated(), + auth.restrictToOwner({ ownerField: 'id' }) + ] +}; + +exports.after = { + all: [hooks.remove('password')], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] +}; diff --git a/src/services/user/index.js b/src/services/user/index.js new file mode 100644 index 0000000..3f3438f --- /dev/null +++ b/src/services/user/index.js @@ -0,0 +1,29 @@ +'use strict'; + +const service = require('feathers-sequelize'); +const user = require('./user-model'); +const hooks = require('./hooks'); + +module.exports = function(){ + const app = this; + + const options = { + Model: user(app.get('sequelize')), + paginate: { + default: 5, + max: 25 + } + }; + + // Initialize our service with any options it requires + app.use('/users', service(options)); + + // Get our initialize service to that we can bind hooks + const userService = app.service('/users'); + + // Set up our before hooks + userService.before(hooks.before); + + // Set up our after hooks + userService.after(hooks.after); +}; diff --git a/src/services/user/user-model.js b/src/services/user/user-model.js new file mode 100644 index 0000000..03db95b --- /dev/null +++ b/src/services/user/user-model.js @@ -0,0 +1,36 @@ +'use strict'; + +// user-model.js - A sequelize model +// +// See http://docs.sequelizejs.com/en/latest/docs/models-definition/ +// for more of what you can do here. + +const Sequelize = require('sequelize'); + +module.exports = function(sequelize) { + const user = sequelize.define('users', { + email: { + type: Sequelize.STRING, + allowNull: false, + unique: true + }, + password: { + type: Sequelize.STRING, + allowNull: false + }, + role: { + type: Sequelize.ENUM('user', 'manager', 'admin'), + allowNull: false + }, + goal: { + type: Sequelize.INTEGER, + allowNull: false + }, + }, { + freezeTableName: true + }); + + user.sync(); + + return user; +}; diff --git a/test/app.test.js b/test/app.test.js new file mode 100644 index 0000000..a16c396 --- /dev/null +++ b/test/app.test.js @@ -0,0 +1,51 @@ +'use strict'; + +const assert = require('assert'); +const request = require('request'); +const app = require('../src/app'); + +describe('Feathers application tests', function() { + before(function(done) { + this.server = app.listen(3030); + this.server.once('listening', () => done()); + }); + + after(function(done) { + this.server.close(done); + }); + + it('starts and shows the index page', function(done) { + request('http://localhost:3030', function(err, res, body) { + assert.ok(body.indexOf('') !== -1); + done(err); + }); + }); + + describe('404', function() { + it('shows a 404 HTML page', function(done) { + request({ + url: 'http://localhost:3030/path/to/nowhere', + headers: { + 'Accept': 'text/html' + } + }, function(err, res, body) { + assert.equal(res.statusCode, 404); + assert.ok(body.indexOf('') !== -1); + done(err); + }); + }); + + it('shows a 404 JSON error without stack trace', function(done) { + request({ + url: 'http://localhost:3030/path/to/nowhere', + json: true + }, function(err, res, body) { + assert.equal(res.statusCode, 404); + assert.equal(body.code, 404); + assert.equal(body.message, 'Page not found'); + assert.equal(body.name, 'NotFound'); + done(err); + }); + }); + }); +}); diff --git a/test/services/meal/index.test.js b/test/services/meal/index.test.js new file mode 100644 index 0000000..92dd70f --- /dev/null +++ b/test/services/meal/index.test.js @@ -0,0 +1,10 @@ +'use strict'; + +const assert = require('assert'); +const app = require('../../../src/app'); + +describe('meal service', function() { + it('registered the meals service', () => { + assert.ok(app.service('meals')); + }); +}); diff --git a/test/services/user/index.test.js b/test/services/user/index.test.js new file mode 100644 index 0000000..a43dcae --- /dev/null +++ b/test/services/user/index.test.js @@ -0,0 +1,10 @@ +'use strict'; + +const assert = require('assert'); +const app = require('../../../src/app'); + +describe('user service', function() { + it('registered the users service', () => { + assert.ok(app.service('users')); + }); +}); -- cgit v1.2.3-70-g09d2