├── .editorconfig ├── .gitignore ├── .jshintrc ├── .npmignore ├── LICENSE ├── README.md ├── client └── index.js ├── config ├── default.json └── production.json ├── live-query.gif ├── package.json ├── public ├── dist │ └── bundle.js ├── favicon.ico ├── index-devserver.html └── index.html ├── server ├── app.js ├── hooks │ └── index.js ├── index.js ├── middleware │ ├── index.js │ ├── logger.js │ └── not-found-handler.js └── services │ ├── authentication │ └── index.js │ ├── index.js │ ├── messages │ ├── exerciseMessages.js │ ├── hooks │ │ └── index.js │ └── index.js │ └── user │ ├── hooks │ └── index.js │ └── index.js └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | lib/ 31 | data/ 32 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": "nofunc", 11 | "newcap": false, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "regexp": true, 15 | "undef": true, 16 | "unused": false, 17 | "strict": false, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "white": false, 21 | "globals": { 22 | "it": true, 23 | "describe": true, 24 | "before": true, 25 | "beforeEach": true, 26 | "after": true, 27 | "afterEach": true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | data/ 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Feathers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # feathers-live-query 2 | 3 | > Live queries with 1 line of JS. 4 | 5 | ![Live query demo](./live-query.gif) 6 | 7 | ## Getting Started 8 | 9 | Getting up and running is as easy as 1, 2, 3. 10 | 11 | 1. Make sure you have [NodeJS](https://nodejs.org/) and [npm](https://www.npmjs.com/) installed. 12 | 2. Install your dependencies 13 | 14 | ``` 15 | cd path/to/feathers-live-query 16 | npm install 17 | ``` 18 | 19 | 3. Start the server 20 | 21 | ``` 22 | npm start 23 | ``` 24 | 25 | 4. Start one or more clients by pointing a browser to `localhost:3030`. 26 | 27 | The server runs one cycle of DB commands and then waits on client requests. 28 | The clients communicate with the server using `socket.io` websockets. 29 | They make no requests of the server other than the live request. 30 | 31 | You have to restart the server and the clients to rerun the demo. 32 | 33 | ## Testing 34 | 35 | Simply run `npm test` and all your tests in the `test/` directory will be run. 36 | 37 | ## Help 38 | 39 | [The book](http://docs.feathersjs.com). 40 | 41 | [The community](https://feathersjs.slack.com/messages/general/). 42 | 43 | ## Changelog 44 | 45 | __0.1.0__ 46 | 47 | - Initial release 48 | 49 | ## License 50 | 51 | Copyright (c) 2016 52 | 53 | Licensed under the [MIT license](LICENSE). 54 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | 2 | const feathers = require('feathers/client'); 3 | const socketio = require('feathers-socketio/client'); 4 | const hooks = require('feathers-hooks'); 5 | const authentication = require('feathers-authentication/client'); 6 | const rx = require('feathers-reactive'); 7 | const RxJS = require('rxjs'); 8 | const io = require('socket.io-client'); 9 | 10 | console.log('..Client started'); 11 | 12 | const socket = io('http://localhost:3030'); 13 | const app = feathers() 14 | .configure(hooks()) 15 | .configure(socketio(socket)) 16 | .configure(authentication({ storage: window.localStorage })) 17 | .configure(rx(RxJS)); 18 | 19 | app.service('messages').rx({ 20 | idField: '_id', 21 | listStrategy: 'smart', 22 | }); 23 | 24 | // Config messages 25 | const messages = app.service('messages'); 26 | var messagesMirror; 27 | 28 | /* 29 | messages.on('created', messages => console.log('CREATED event:', messages.text)); 30 | messages.on('updated', messages => console.log('UPDATED event:', messages.text)); 31 | messages.on('patched', messages => console.log('PATCHED event:', messages.text)); 32 | messages.on('removed', messages => console.log('REMOVED event:', messages.text)); 33 | */ 34 | 35 | const messages$ = messages.find({ query: {}}); 36 | 37 | // Handler live query 38 | messages$.subscribe(messages => { 39 | messagesMirror = messages.sort((a, b) => a.order < b.order ? -1 : 1); 40 | 41 | console.log('.. Live query now has', messages.length, 'items'); 42 | messages.forEach((message, i) => { 43 | console.log(`${i}: ${message._id} ${message.text} ${message.order}`); 44 | }); 45 | 46 | updateUi(); 47 | }); 48 | 49 | function updateUi() { 50 | const listEl = document.getElementById('list'); 51 | 52 | while (listEl.hasChildNodes()) 53 | listEl.removeChild(listEl.lastChild); 54 | 55 | messagesMirror.forEach(message => { 56 | const liEl = document.createElement('LI'); 57 | const textEl = document.createTextNode(message.text); 58 | liEl.appendChild(textEl); 59 | listEl.appendChild(liEl); 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "localhost", 3 | "port": 3030, 4 | "nedb": "../data/", 5 | "public": "../public/", 6 | "auth": { 7 | "token": { 8 | "secret": "ho5mfU7vBFurbx6UN7scirpgWLqudZq5ipNUQq7AtH7RZ/YH2b7f3t8ePC7opYjvZQG3fAf3y9m0QdJMe/Sv/A==" 9 | }, 10 | "local": {} 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /config/production.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "feathers-generated-app.feathersjs.com", 3 | "port": 80, 4 | "nedb": "NEDB_BASE_PATH", 5 | "public": "../public/", 6 | "auth": { 7 | "token": { 8 | "secret": "FEATHERS_AUTH_SECRET" 9 | }, 10 | "local": {} 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /live-query.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eddyystop/feathers-live-query/2dd2704d57538ac7e93c4b02b4a2d9bcd0186d19/live-query.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "feathers-live-query", 3 | "description": "Live query. Mirror part of a DB on the client.", 4 | "version": "0.0.1", 5 | "homepage": "https://github.com/feathers/feathers-live-query#readme", 6 | "main": "server/", 7 | "keywords": [ 8 | "feathers", 9 | "feathersjs", 10 | "hook", 11 | "hooks", 12 | "live query", 13 | "mirror" 14 | ], 15 | "license": "MIT", 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/eddyystop/feathers-live-query.git" 19 | }, 20 | "author": "John Szwaronek ", 21 | "contributors": [], 22 | "bugs": {}, 23 | "engines": { 24 | "node": ">=5.0.0", 25 | "npm": ">=3.8.0" 26 | }, 27 | "scripts": { 28 | "build:devserver": "NODE_ENV=devserver webpack-dev-server --config webpack.config.js --content-base public/dist/ | grep -v \"\\[built\\]\"", 29 | "build": "NODE_ENV=development webpack --config webpack.config.js --content-base public/dist/ | grep -v \"\\[built\\]\"", 30 | "start": "node ./server/index.js", 31 | "npm:patch": "npm version patch && npm publish", 32 | "npm:minor": "npm version minor && npm publish", 33 | "npm:major": "npm version major && npm publish", 34 | "npm:updates": "npm-check-updates" 35 | }, 36 | "dependencies": { 37 | "babel-cli": "6.14.0", 38 | "babel-core": "6.14.0", 39 | "babel-eslint": "6.1.2", 40 | "babel-loader": "6.2.5", 41 | "babel-plugin-add-module-exports": "0.2.1", 42 | "babel-plugin-transform-es2015-destructuring": "6.9.0", 43 | "babel-plugin-transform-object-rest-spread": "6.8.0", 44 | "babel-plugin-transform-react-constant-elements": "6.9.1", 45 | "babel-plugin-transform-react-inline-elements": "6.8.0", 46 | "babel-plugin-transform-runtime": "6.15.0", 47 | "babel-preset-es2015": "6.14.0", 48 | "babel-preset-react": "6.11.1", 49 | "babel-preset-stage-0": "6.5.0", 50 | "body-parser": "1.15.2", 51 | "compression": "1.6.2", 52 | "cors": "2.8.1", 53 | "feathers": "2.0.2", 54 | "feathers-authentication": "0.7.11", 55 | "feathers-configuration": "0.2.3", 56 | "feathers-errors": "2.4.0", 57 | "feathers-hooks": "1.5.8", 58 | "feathers-nedb": "2.5.1", 59 | "feathers-reactive": "0.4.0", 60 | "feathers-rest": "1.5.0", 61 | "feathers-socketio": "1.4.1", 62 | "nedb": "1.8.0", 63 | "passport": "0.3.2", 64 | "postcss-loader": "0.13.0", 65 | "rucksack-css": "0.8.6", 66 | "rxjs": "5.0.0-rc.1", 67 | "serve-favicon": "2.3.0", 68 | "style-loader": "0.13.1", 69 | "winston": "2.2.0" 70 | }, 71 | "devDependencies": { 72 | "socket.io-client": "1.5.0", 73 | "webpack": "1.13.2", 74 | "webpack-dev-server": "1.15.2" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eddyystop/feathers-live-query/2dd2704d57538ac7e93c4b02b4a2d9bcd0186d19/public/favicon.ico -------------------------------------------------------------------------------- /public/index-devserver.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Feathers live query 4 | 5 | 6 |
7 |
8 |
// Client code for a live-query
 9 | const messages.find({ query: { ... }})
10 |   .subscribe(messages => { makeDomList(messages); });
11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Feathers live query 4 | 5 | 6 |
7 |
8 |
// Client code for a live-query
 9 | const messages.find({ query: { ... }})
10 |   .subscribe(messages => { makeDomList(messages); });
11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const serveStatic = require('feathers').static; 5 | const favicon = require('serve-favicon'); 6 | const compress = require('compression'); 7 | const cors = require('cors'); 8 | const feathers = require('feathers'); 9 | const configuration = require('feathers-configuration'); 10 | const authentication = require('feathers-authentication'); 11 | const hooks = require('feathers-hooks'); 12 | const rest = require('feathers-rest'); 13 | const bodyParser = require('body-parser'); 14 | const socketio = require('feathers-socketio'); 15 | 16 | const middleware = require('./middleware'); 17 | const services = require('./services'); 18 | 19 | const app = feathers(); 20 | 21 | app.configure(configuration(path.join(__dirname, '..'))); 22 | 23 | app.use(compress()) 24 | .options('*', cors()) 25 | .use(cors()) 26 | .use(favicon( path.join(app.get('public'), 'favicon.ico') )) 27 | .use('/', serveStatic( app.get('public') )) 28 | .use(bodyParser.json()) 29 | .use(bodyParser.urlencoded({ extended: true })) 30 | .configure(hooks()) 31 | .configure(rest()) 32 | .configure(socketio()) 33 | .configure(services) 34 | .configure(middleware) 35 | .configure(authentication()); 36 | 37 | // erase the messages DB 38 | const messages = app.service('messages'); 39 | console.log('.. Clear DB'); 40 | 41 | messages.remove(null) 42 | .then(() => { 43 | console.log('.. Waiting for a client to connect'); 44 | }); 45 | 46 | module.exports = app; 47 | -------------------------------------------------------------------------------- /server/hooks/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Add any common hooks you want to share across services in here. 4 | // 5 | // Below is an example of how a hook is written and exported. Please 6 | // see http://docs.feathersjs.com/hooks/readme.html for more details 7 | // on hooks. 8 | 9 | exports.myHook = function(options) { 10 | return function(hook) { 11 | console.log('My custom global hook ran. Feathers is awesome!'); 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const app = require('./app'); 4 | const port = app.get('port'); 5 | const server = app.listen(port); 6 | 7 | server.on('listening', () => 8 | console.log(`Feathers application started on ${app.get('host')}:${port}`) 9 | ); -------------------------------------------------------------------------------- /server/middleware/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const handler = require('feathers-errors/handler'); 4 | const notFound = require('./not-found-handler'); 5 | const logger = require('./logger'); 6 | 7 | module.exports = function() { 8 | // Add your custom middleware here. Remember, that 9 | // just like Express the order matters, so error 10 | // handling middleware should go last. 11 | const app = this; 12 | 13 | app.use(notFound()); 14 | app.use(logger(app)); 15 | app.use(handler()); 16 | }; 17 | -------------------------------------------------------------------------------- /server/middleware/logger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const winston = require('winston'); 4 | 5 | module.exports = function(app) { 6 | // Add a logger to our app object for convenience 7 | app.logger = winston; 8 | 9 | return function(error, req, res, next) { 10 | if (error) { 11 | const message = `${error.code ? `(${error.code}) ` : '' }Route: ${req.url} - ${error.message}`; 12 | 13 | if (error.code === 404) { 14 | winston.info(message); 15 | } 16 | else { 17 | winston.error(message); 18 | winston.info(error.stack); 19 | } 20 | } 21 | 22 | next(error); 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /server/middleware/not-found-handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const errors = require('feathers-errors'); 4 | 5 | module.exports = function() { 6 | return function(req, res, next) { 7 | next(new errors.NotFound('Page not found')); 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /server/services/authentication/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const authentication = require('feathers-authentication'); 4 | 5 | 6 | module.exports = function() { 7 | const app = this; 8 | 9 | let config = app.get('auth'); 10 | 11 | 12 | 13 | app.configure(authentication(config)); 14 | }; 15 | -------------------------------------------------------------------------------- /server/services/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const authentication = require('./authentication'); 3 | const user = require('./user'); 4 | const messages = require('./messages'); 5 | 6 | module.exports = function() { 7 | const app = this; 8 | 9 | app.configure(authentication); 10 | app.configure(user); 11 | app.configure(messages); 12 | }; 13 | -------------------------------------------------------------------------------- /server/services/messages/exerciseMessages.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (app) { 3 | const ms = 500; 4 | const ids = []; 5 | var order = -1; 6 | 7 | const messages = app.service('messages'); 8 | 9 | const actions = [ 10 | ['create', 0, `Hi. Watch a DB action every ${ms/1000} sec.`], 11 | ['create'], 12 | ['create'], 13 | ['patch', 1, 'Message 1 has been changed.'], 14 | ['create'], 15 | ['create'], 16 | ['create'], 17 | ['patch', 1, 'Message 1 has been changed again.'], 18 | ['create'], 19 | ['patch', 4, 'Oh no, message 4 has been changed too.'], 20 | ['create'], 21 | ['remove', 3], 22 | ['remove', 4], 23 | ['remove', 5], 24 | ['create'], 25 | ['create'], 26 | ['remove', 6], 27 | ['remove', 7], 28 | ['remove', 8], 29 | ['remove', 9], 30 | ['remove', 2], 31 | ['remove', 1], 32 | ['remove', 0], 33 | ['create', 0, 'I may not say anything, but I see everything!'] 34 | ]; 35 | 36 | messages.on('created', messages => console.log('CREATED event:', messages.text)); 37 | messages.on('updated', messages => console.log('UPDATED event:', messages.text)); 38 | messages.on('patched', messages => console.log('PATCHED event:', messages.text)); 39 | messages.on('removed', messages => console.log('REMOVED event:', messages.text)); 40 | 41 | var promise = Promise.resolve(); 42 | 43 | actions.forEach(action => { 44 | const method = action[0]; 45 | const idNum = action[1]; 46 | const text = action[2]; 47 | 48 | switch (method) { 49 | case 'create': 50 | promise = promise.then(() => { 51 | order += 1; 52 | return messages.create({ text: `${text || `This is message ${order}.`}`, order }) 53 | .then(message => { 54 | ids.push(message._id); 55 | }) 56 | }); 57 | break; 58 | case 'patch': 59 | promise = promise.then(() => messages.patch(ids[idNum], { text: `${text || 'Patched.'}` })); 60 | break; 61 | case 'remove': 62 | promise = promise.then(() => messages.remove(ids[idNum])); 63 | break; 64 | default: 65 | } 66 | 67 | promise = promise.then(delayPromise(ms)); 68 | }); 69 | }; 70 | 71 | function delayPromise(duration) { 72 | return () => new Promise((resolve) => { 73 | setTimeout(() => { resolve(); }, duration); 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /server/services/messages/hooks/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const globalHooks = require('../../../hooks'); 4 | const hooks = require('feathers-hooks'); 5 | const exerciseMessages = require('../exerciseMessages'); 6 | 7 | var isFirstClient = true; 8 | 9 | exports.before = { 10 | all: [], 11 | find: [ 12 | (hook) => { 13 | if (isFirstClient) { 14 | isFirstClient = false; 15 | exerciseMessages(hook.app); 16 | } 17 | }, 18 | ], 19 | get: [], 20 | create: [], 21 | update: [], 22 | patch: [], 23 | remove: [] 24 | }; 25 | 26 | exports.after = { 27 | all: [], 28 | find: [], 29 | get: [], 30 | create: [], 31 | update: [], 32 | patch: [], 33 | remove: [] 34 | }; 35 | -------------------------------------------------------------------------------- /server/services/messages/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const NeDB = require('nedb'); 5 | const service = require('feathers-nedb'); 6 | const hooks = require('./hooks'); 7 | 8 | module.exports = function(){ 9 | const app = this; 10 | 11 | const db = new NeDB({ 12 | filename: path.join(app.get('nedb'), 'messages.db'), 13 | autoload: true 14 | }); 15 | 16 | let options = { 17 | Model: db, 18 | }; 19 | 20 | // Initialize our service with any options it requires 21 | app.use('/messages', service(options)); 22 | 23 | // Get our initialize service to that we can bind hooks 24 | const messagesService = app.service('/messages'); 25 | 26 | // Set up our before hooks 27 | messagesService.before(hooks.before); 28 | 29 | // Set up our after hooks 30 | messagesService.after(hooks.after); 31 | }; 32 | -------------------------------------------------------------------------------- /server/services/user/hooks/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const globalHooks = require('../../../hooks'); 4 | const hooks = require('feathers-hooks'); 5 | const auth = require('feathers-authentication').hooks; 6 | 7 | exports.before = { 8 | all: [], 9 | find: [ 10 | auth.verifyToken(), 11 | auth.populateUser(), 12 | auth.restrictToAuthenticated() 13 | ], 14 | get: [ 15 | auth.verifyToken(), 16 | auth.populateUser(), 17 | auth.restrictToAuthenticated(), 18 | auth.restrictToOwner({ ownerField: '_id' }) 19 | ], 20 | create: [ 21 | auth.hashPassword() 22 | ], 23 | update: [ 24 | auth.verifyToken(), 25 | auth.populateUser(), 26 | auth.restrictToAuthenticated(), 27 | auth.restrictToOwner({ ownerField: '_id' }) 28 | ], 29 | patch: [ 30 | auth.verifyToken(), 31 | auth.populateUser(), 32 | auth.restrictToAuthenticated(), 33 | auth.restrictToOwner({ ownerField: '_id' }) 34 | ], 35 | remove: [ 36 | auth.verifyToken(), 37 | auth.populateUser(), 38 | auth.restrictToAuthenticated(), 39 | auth.restrictToOwner({ ownerField: '_id' }) 40 | ] 41 | }; 42 | 43 | exports.after = { 44 | all: [hooks.remove('password')], 45 | find: [], 46 | get: [], 47 | create: [], 48 | update: [], 49 | patch: [], 50 | remove: [] 51 | }; 52 | -------------------------------------------------------------------------------- /server/services/user/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const NeDB = require('nedb'); 5 | const service = require('feathers-nedb'); 6 | const hooks = require('./hooks'); 7 | 8 | module.exports = function(){ 9 | const app = this; 10 | 11 | const db = new NeDB({ 12 | filename: path.join(app.get('nedb'), 'users.db'), 13 | autoload: true 14 | }); 15 | 16 | let options = { 17 | Model: db, 18 | paginate: { 19 | default: 5, 20 | max: 25 21 | } 22 | }; 23 | 24 | // Initialize our service with any options it requires 25 | app.use('/users', service(options)); 26 | 27 | // Get our initialize service to that we can bind hooks 28 | const userService = app.service('/users'); 29 | 30 | // Set up our before hooks 31 | userService.before(hooks.before); 32 | 33 | // Set up our after hooks 34 | userService.after(hooks.after); 35 | }; 36 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 2 | const webpack = require('webpack'); // eslint-disable-line import/no-unresolved 3 | const rucksack = require('rucksack-css'); 4 | const path = require('path'); 5 | 6 | module.exports = { 7 | context: path.join(__dirname, './client'), 8 | devtool: 'inline-source-map', 9 | entry: './index.js', 10 | output: { 11 | path: path.join(__dirname, './public/dist'), 12 | filename: 'bundle.js', 13 | }, 14 | module: { 15 | loaders: [ 16 | { 17 | test: /\.(js|jsx)$/, 18 | exclude: /node_modules/, 19 | loaders: [ 20 | 'babel-loader', 21 | ], 22 | }, 23 | { 24 | // When require'd, these /client/../*.inject.css files are injected into the DOM as is. 25 | test: /\.inject\.css$/, 26 | include: /client/, 27 | loader: 'style!css', 28 | }, 29 | { 30 | // When required, the class names in these /client/../*.css are returned as an object. 31 | // after being made unique. The css with the modified class names is injected into the DOM. 32 | test: /^(?!.*\.inject\.css).*\.css$/, 33 | include: /client/, 34 | loaders: [ 35 | 'style-loader', 36 | 'css-loader?modules&sourceMap&importLoaders=1&localIdentName=' + 37 | '[name]__[local]___[hash:base64:5]', 38 | 'postcss-loader', 39 | ], 40 | }, 41 | { 42 | // Standard processing for .css outside /client 43 | test: /\.css$/, 44 | exclude: /client/, 45 | loader: 'style!css', 46 | }, 47 | ], 48 | }, 49 | resolve: { 50 | extensions: ['', '.js', '.jsx'], 51 | }, 52 | postcss: [ 53 | rucksack({ 54 | autoprefixer: true, 55 | }), 56 | ], 57 | plugins: [ 58 | // Webpack's default file watcher does not work in Virtual Machines with NFS file systems. 59 | new webpack.OldWatchingPlugin(), // can use "webpack-dev-server --watch-poll" instead 60 | // Define replacements for global constants in the client code. 61 | new webpack.DefinePlugin({ 62 | 'process.env': { NODE_ENV: JSON.stringify(process.env.NODE_ENV) }, // used by React, etc? 63 | __processEnvNODE_ENV__: JSON.stringify(process.env.NODE_ENV), // used by us 64 | }), 65 | ], 66 | devServer: { 67 | contentBase: './client', 68 | }, 69 | }; 70 | --------------------------------------------------------------------------------