summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.editorconfig13
-rw-r--r--.gitignore31
-rw-r--r--.jshintrc29
-rw-r--r--.npmignore30
-rw-r--r--LICENSE22
-rw-r--r--README2
-rw-r--r--README.md57
-rw-r--r--config/default.json13
-rw-r--r--config/production.json13
-rw-r--r--package.json50
-rw-r--r--public/favicon.icobin0 -> 5533 bytes
-rw-r--r--public/index.html73
-rw-r--r--src/app.js33
-rw-r--r--src/hooks/index.js19
-rw-r--r--src/index.js9
-rw-r--r--src/middleware/index.js16
-rw-r--r--src/middleware/logger.js24
-rw-r--r--src/middleware/not-found-handler.js9
-rw-r--r--src/services/authentication/index.js14
-rw-r--r--src/services/index.js18
-rw-r--r--src/services/meal/hooks/index.js31
-rw-r--r--src/services/meal/index.js29
-rw-r--r--src/services/meal/meal-model.js38
-rw-r--r--src/services/user/hooks/index.js51
-rw-r--r--src/services/user/index.js29
-rw-r--r--src/services/user/user-model.js36
-rw-r--r--test/app.test.js51
-rw-r--r--test/services/meal/index.test.js10
-rw-r--r--test/services/user/index.test.js10
29 files changed, 752 insertions, 8 deletions
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 <julescarbon@gmail.com>",
- "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
--- /dev/null
+++ b/public/favicon.ico
Binary files 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 @@
+<html>
+ <head>
+ <title>Welcome to Feathers</title>
+ <style>
+ * {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ }
+
+ html, body {
+ font-family: 'Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif';
+ font-weight: 400;
+ font-size: 16px;
+ color: #333;
+ }
+
+ .center-text {
+ text-align: center;
+ }
+
+ main {
+ margin-top: 100px;
+ padding: 20px;
+ }
+
+ img.logo {
+ display: block;
+ margin: 0 auto;
+ max-width: 100%;
+ margin-bottom: 30px;
+ }
+
+ h2 {
+ font-size: 2em;
+ font-weight: 100;
+ }
+
+ footer {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ padding: 20px;
+ }
+
+ footer p {
+ font-weight: 300;
+ font-size: 1.0em;
+ }
+
+ a {
+ color: #31D8A0;
+ text-decoration: none;
+ }
+
+ a:hover,
+ a:focus {
+ color: #31D8A0;
+ }
+ </style>
+ </head>
+ <body>
+ <main class="container">
+ <img class="logo" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAk4AAABkCAMAAABHL0++AAADAFBMVEUAAAD///+AgIBVVVVAQEAzMzNVVVVJSUlAQEA5OTkzMzNGRkZAQEA7Ozs3NzczMzNAQEA8PDw5OTk2NjYzMzM9PT06Ojo3Nzc1NTUzMzM7Ozs5OTk3Nzc1NTUzMzM6Ojo4ODg2NjY1NTUzMzM5OTk3Nzc2NjY0NDQzMzM4ODg3Nzc1NTU0NDQzMzM3Nzc2NjY1NTU0NDQzMzM3Nzc2NjY1NTU0NDQzMzM3Nzc2NjY1NTU0NDQzMzM2NjY1NTU1NTU0NDQzMzM2NjY1NTU1NTU0NDQzMzM2NjY1NTU0NDQ0NDQzMzM2NjY1NTU0NDQ0NDQzMzM2NjY1NTU0NDQ0NDQzMzM1NTU1NTU0NDQ0NDQzMzM1NTU1NTU0NDQ0NDQzMzM1NTU1NTU0NDQ0NDQzMzM1NTU1NTU0NDQzMzMzMzM1NTU0NDQ0NDQzMzMzMzM1NTU0NDQ0NDQzMzMzMzM1NTU0NDQ0NDQzMzMzMzM1NTU0NDQ0NDQzMzMzMzM1NTU0NDQ0NDQzMzMzMzM1NTU0NDQ0NDQzMzMzMzM1NTU0NDQ0NDQzMzMzMzM0NDQ0NDQ0NDQzMzMzMzM0NDQ0NDQ0NDQzMzMzMzM0NDQ0NDQ0NDQzMzMzMzM0NDQ0NDQ0NDQzMzMzMzM0NDQ0NDQ0NDQzMzMzMzM0NDQ0NDQ0NDQzMzMzMzM0NDQ0NDQ0NDQzMzMzMzM0NDQ0NDQ0NDQzMzMzMzM0NDQ0NDQ0NDQzMzMzMzM0NDQ0NDQ0NDQzMzMzMzM0NDQ0NDQ0NDQzMzMzMzM0NDQ0NDQ0NDQzMzMzMzM0NDQ0NDQ0NDQzMzMzMzM0NDQ0NDQzMzMzMzMzMzM0NDQ0NDQzMzMzMzMzMzM0NDQ0NDQzMzMzMzMzMzM0NDQ0NDQzMzMzMzMzMzM0NDQ0NDQzMzMzMzMzMzM0NDQ0NDQzMzMzMzMzMzM0NDQ0NDQzMzMzMzMzMzM0NDQ0NDQzMzMzMzMzMzM0NDQ0NDQzMzMzMzMzMzM0NDQ0NDQzMzMzMzMzMzN4a/VhAAAA/3RSTlMAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdISUpLTE1O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" alt="Feathers Logo">
+ <h2 class="center-text">A minimalist real-time framework for tomorrow's apps.</h2>
+
+ <footer>
+ <p class="center-text">For more information on Feathers see <a href="http://docs.feathersjs.com" alt="Feathers Documentation" target="blank">docs.feathersjs.com</a>.</p>
+ </footer>
+ </main>
+ </body>
+</html>
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('<html>') !== -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('<html>') !== -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'));
+ });
+});