├── README.MD ├── client ├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── README.md ├── babel.config.js ├── cypress.json ├── jest.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public │ ├── favicon.ico │ ├── img │ │ └── icons │ │ │ ├── android-chrome-192x192.png │ │ │ ├── android-chrome-512x512.png │ │ │ ├── apple-touch-icon-120x120.png │ │ │ ├── apple-touch-icon-152x152.png │ │ │ ├── apple-touch-icon-180x180.png │ │ │ ├── apple-touch-icon-60x60.png │ │ │ ├── apple-touch-icon-76x76.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── msapplication-icon-144x144.png │ │ │ ├── mstile-150x150.png │ │ │ └── safari-pinned-tab.svg │ ├── index.html │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ └── HelloWorld.vue │ ├── config │ │ └── index.js │ ├── feathers │ │ └── index.js │ ├── main.js │ ├── mixins │ │ └── TodosMixin.js │ ├── registerServiceWorker.js │ ├── router.js │ ├── store │ │ ├── index.js │ │ └── services │ │ │ ├── todos.js │ │ │ └── users.js │ └── views │ │ └── Todos.vue ├── tests │ ├── e2e │ │ ├── .eslintrc.js │ │ ├── plugins │ │ │ └── index.js │ │ ├── specs │ │ │ └── test.js │ │ └── support │ │ │ ├── commands.js │ │ │ └── index.js │ └── unit │ │ ├── .eslintrc.js │ │ └── example.spec.js └── yarn.lock ├── logo.png ├── preview.png └── server ├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── config ├── default.json └── production.json ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── app.hooks.js ├── app.js ├── channels.js ├── hooks │ └── log.js ├── index.js ├── logger.js ├── middleware │ └── index.js ├── models │ └── todos.model.js └── services │ ├── index.js │ └── todos │ ├── todos.hooks.js │ └── todos.service.js └── test ├── app.test.js └── services └── todos.test.js /README.MD: -------------------------------------------------------------------------------- 1 | ![logo.png](logo.png) 2 | 3 | # Feathers-vuex TodoMVC demo 4 | 5 | This project takes the existing [Vue](https://vuejs.org) [TodoMVC demo](https://vuejs.org/v2/examples/todomvc.html) and adds [Feathers](https://featherjs.com), and [Feathers-vuex](https://feathers-plus.github.io/v1/feathers-vuex) awesomeness! 6 | 7 | In short, this stack makes it really simple to get reactive updates in Vue from a Feathers.js server, through Vuex. 8 | 9 | It’s a beautiful combination of full-stack tech. 10 | 11 | # How to get started 12 | 13 | First, clone this repo 14 | 15 | `git clone git@github.com:wdmtech/feathers-vuex-todomvc.git` 16 | 17 | ## Server (Feathers) 18 | 19 | `cd server` 20 | 21 | `npm install` 22 | 23 | `npm run start` 24 | 25 | ## Client (Vue) 26 | 27 | `cd client` 28 | 29 | `npm install` 30 | 31 | `npm run serve` or `vue ui` if you have [vue-cli 3](https://cli.vuejs.org/) or later. 32 | 33 | Navigate to the url displayed (default `localhost:8080`) to view in your browser: 34 | 35 | ![preview.png](preview.png) 36 | 37 | # Still to do - contributions welcome! 38 | 39 | - [ ] Filter todos using [reactive live queries](https://feathers-plus.github.io/v1/feathers-vuex/common-patterns.html#Reactive-Lists-with-Live-Queries) 40 | 41 | # Why? 42 | 43 | I built this because there seemed to be a need for examples containing actual implementation of Feathers with Vue and Feathers-vuex. 44 | 45 | # Further information 46 | 47 | ## CRUD operations in the UI 48 | 49 | The [Models and Instances API](https://feathers-plus.github.io/v1/feathers-vuex/model-classes.html) is used to create/remove/edit todos, and there is also an example [implementation](https://github.com/wdmtech/feathers-vuex-todomvc/blob/master/client/src/mixins/TodosMixin.js) that illustrates receiving events from the server on the client. 50 | 51 | ## `todos` service 52 | 53 | ### …on the client 54 | 55 | The service is defined in feathers-vuex: [./client/src/store/services/todos.js](https://github.com/wdmtech/feathers-vuex-todomvc/blob/master/client/src/store/services/todos.js) 56 | 57 | This is then loaded by the store: [./client/src/store/index.js](https://github.com/wdmtech/feathers-vuex-todomvc/blob/master/client/src/store/index.js) 58 | 59 | ### …on the server 60 | 61 | #### Initial todo data 62 | 63 | The `todos` service is seeded with some initial data, in [./server/src/services/todos/todos.service.js](https://github.com/wdmtech/feathers-vuex-todomvc/blob/master/server/src/services/todos/todos.service.js) 64 | -------------------------------------------------------------------------------- /client/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /client/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: ["plugin:vue/essential", "@vue/prettier"], 7 | rules: { 8 | "no-console": process.env.NODE_ENV === "production" ? "error" : "off", 9 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off" 10 | }, 11 | parserOptions: { 12 | parser: "babel-eslint" 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | /tests/e2e/videos/ 6 | /tests/e2e/screenshots/ 7 | 8 | # local env files 9 | .env.local 10 | .env.*.local 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw* 25 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # vffv 2 | 3 | ## Project setup 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | yarn run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | yarn run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | yarn run lint 21 | ``` 22 | 23 | ### Run your unit tests 24 | ``` 25 | yarn run test:unit 26 | ``` 27 | 28 | ### Run your end-to-end tests 29 | ``` 30 | yarn run test:e2e 31 | ``` 32 | -------------------------------------------------------------------------------- /client/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/app"] 3 | }; 4 | -------------------------------------------------------------------------------- /client/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /client/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ["js", "jsx", "json", "vue"], 3 | transform: { 4 | "^.+\\.vue$": "vue-jest", 5 | ".+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$": 6 | "jest-transform-stub", 7 | "^.+\\.jsx?$": "babel-jest" 8 | }, 9 | moduleNameMapper: { 10 | "^@/(.*)$": "/src/$1" 11 | }, 12 | snapshotSerializers: ["jest-serializer-vue"], 13 | testMatch: [ 14 | "**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)" 15 | ], 16 | testURL: "http://localhost/" 17 | }; 18 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "feathers-vuex-todomvc-client", 3 | "description": "Vue, feathers and feathers-vuex todoMVC repo client", 4 | "version": "0.1.0", 5 | "private": true, 6 | "scripts": { 7 | "serve": "vue-cli-service serve", 8 | "build": "vue-cli-service build", 9 | "lint": "vue-cli-service lint", 10 | "test:unit": "vue-cli-service test:unit", 11 | "test:e2e": "vue-cli-service test:e2e" 12 | }, 13 | "dependencies": { 14 | "@feathersjs/authentication-client": "^1.0.4", 15 | "@feathersjs/feathers": "^3.2.1", 16 | "@feathersjs/socketio-client": "^1.1.2", 17 | "feathers-vuex": "^1.4.8", 18 | "register-service-worker": "^1.0.0", 19 | "socket.io-client": "^2.1.1", 20 | "vue": "^2.5.17", 21 | "vue-router": "^3.0.1", 22 | "vuex": "^3.0.1" 23 | }, 24 | "devDependencies": { 25 | "@vue/cli-plugin-babel": "^3.0.3", 26 | "@vue/cli-plugin-e2e-cypress": "^3.0.3", 27 | "@vue/cli-plugin-eslint": "^3.0.3", 28 | "@vue/cli-plugin-pwa": "^3.0.3", 29 | "@vue/cli-plugin-unit-jest": "^3.0.3", 30 | "@vue/cli-service": "^3.0.3", 31 | "@vue/eslint-config-prettier": "^3.0.3", 32 | "@vue/test-utils": "^1.0.0-beta.20", 33 | "babel-core": "7.0.0-bridge.0", 34 | "babel-jest": "^23.0.1", 35 | "node-sass": "^4.9.0", 36 | "sass-loader": "^7.0.1", 37 | "vue-template-compiler": "^2.5.17" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /client/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdmtech/feathers-vuex-todomvc/671be7d737445e44c9d99d72ff708badc6b53610/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdmtech/feathers-vuex-todomvc/671be7d737445e44c9d99d72ff708badc6b53610/client/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /client/public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdmtech/feathers-vuex-todomvc/671be7d737445e44c9d99d72ff708badc6b53610/client/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /client/public/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdmtech/feathers-vuex-todomvc/671be7d737445e44c9d99d72ff708badc6b53610/client/public/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /client/public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdmtech/feathers-vuex-todomvc/671be7d737445e44c9d99d72ff708badc6b53610/client/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /client/public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdmtech/feathers-vuex-todomvc/671be7d737445e44c9d99d72ff708badc6b53610/client/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /client/public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdmtech/feathers-vuex-todomvc/671be7d737445e44c9d99d72ff708badc6b53610/client/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /client/public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdmtech/feathers-vuex-todomvc/671be7d737445e44c9d99d72ff708badc6b53610/client/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /client/public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdmtech/feathers-vuex-todomvc/671be7d737445e44c9d99d72ff708badc6b53610/client/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /client/public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdmtech/feathers-vuex-todomvc/671be7d737445e44c9d99d72ff708badc6b53610/client/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /client/public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdmtech/feathers-vuex-todomvc/671be7d737445e44c9d99d72ff708badc6b53610/client/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /client/public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdmtech/feathers-vuex-todomvc/671be7d737445e44c9d99d72ff708badc6b53610/client/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /client/public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdmtech/feathers-vuex-todomvc/671be7d737445e44c9d99d72ff708badc6b53610/client/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /client/public/img/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Feathers-vuex-todomvc demo 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vffv", 3 | "short_name": "vffv", 4 | "icons": [ 5 | { 6 | "src": "/img/icons/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/img/icons/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": "/index.html", 17 | "display": "standalone", 18 | "background_color": "#000000", 19 | "theme_color": "#4DBA87" 20 | } 21 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /client/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /client/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdmtech/feathers-vuex-todomvc/671be7d737445e44c9d99d72ff708badc6b53610/client/src/assets/logo.png -------------------------------------------------------------------------------- /client/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 44 | 45 | 46 | 62 | -------------------------------------------------------------------------------- /client/src/config/index.js: -------------------------------------------------------------------------------- 1 | export const isDev = Boolean(process.env.NODE_ENV !== "production"); 2 | 3 | export default { 4 | connection: process.env.VUE_APP_API_URL 5 | ? process.env.VUE_APP_API_URL 6 | : "http://localhost:3030", 7 | domain: !isDev ? "*.my-domain.com" : "localhost" 8 | }; 9 | -------------------------------------------------------------------------------- /client/src/feathers/index.js: -------------------------------------------------------------------------------- 1 | import io from "socket.io-client"; 2 | import feathers from "@feathersjs/feathers"; 3 | import socketio from "@feathersjs/socketio-client"; 4 | import auth from "@feathersjs/authentication-client"; 5 | import config from "@/config"; 6 | 7 | const socket = io(config.connection, { transports: ["websocket"] }); 8 | 9 | const feathersClient = feathers() 10 | .configure(socketio(socket)) 11 | .configure(auth({ storage: window.localStorage })); 12 | 13 | export default feathersClient; 14 | -------------------------------------------------------------------------------- /client/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "./App.vue"; 3 | import router from "./router"; 4 | import store from "@/store/index"; 5 | import "./registerServiceWorker"; 6 | 7 | Vue.config.productionTip = false; 8 | 9 | console.log(store); 10 | 11 | new Vue({ 12 | router, 13 | store, 14 | render: h => h(App) 15 | }).$mount("#app"); 16 | -------------------------------------------------------------------------------- /client/src/mixins/TodosMixin.js: -------------------------------------------------------------------------------- 1 | import { mapState, mapGetters, mapActions } from "vuex"; 2 | import feathersClient from "@/feathers"; 3 | 4 | export default { 5 | computed: { 6 | ...mapState("todos", { 7 | areTodosLoading: "isFindPending", 8 | todosPagination: "pagination" 9 | }), 10 | ...mapGetters("todos", { 11 | findTodosInStore: "find", 12 | listTodos: "list" 13 | }) 14 | }, 15 | methods: { 16 | ...mapActions("todos", { 17 | findTodos: "find" 18 | }) 19 | }, 20 | async created() { 21 | console.log("TodosMixin was loaded"); 22 | 23 | feathersClient.service("todos").on("created", data => { 24 | console.log(`Todo created - ${data.title}`); 25 | }); 26 | 27 | try { 28 | await this.findTodos({}); 29 | console.log("found todos"); 30 | } catch (e) { 31 | console.error(e); 32 | } 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /client/src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { register } from "register-service-worker"; 4 | 5 | if (process.env.NODE_ENV === "production") { 6 | register(`${process.env.BASE_URL}service-worker.js`, { 7 | ready() { 8 | console.log( 9 | "App is being served from cache by a service worker.\n" + 10 | "For more details, visit https://goo.gl/AFskqB" 11 | ); 12 | }, 13 | cached() { 14 | console.log("Content has been cached for offline use."); 15 | }, 16 | updated() { 17 | console.log("New content is available; please refresh."); 18 | }, 19 | offline() { 20 | console.log( 21 | "No internet connection found. App is running in offline mode." 22 | ); 23 | }, 24 | error(error) { 25 | console.error("Error during service worker registration:", error); 26 | } 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /client/src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Router from "vue-router"; 3 | 4 | Vue.use(Router); 5 | 6 | export default new Router({ 7 | mode: "history", 8 | base: process.env.BASE_URL, 9 | routes: [ 10 | { 11 | path: "/", 12 | name: "todos", 13 | // route level code-splitting 14 | // this generates a separate chunk (about.[hash].js) for this route 15 | // which is lazy-loaded when the route is visited. 16 | component: () => 17 | import(/* webpackChunkName: "todos" */ "./views/Todos.vue") 18 | } 19 | ] 20 | }); 21 | -------------------------------------------------------------------------------- /client/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | import feathersVuex from "feathers-vuex"; 4 | import feathersClient from "@/feathers"; 5 | 6 | const { 7 | // service, 8 | // auth, 9 | FeathersVuex 10 | } = feathersVuex(feathersClient, { 11 | idField: "_id" 12 | }); 13 | 14 | Vue.use(FeathersVuex); 15 | Vue.use(Vuex); 16 | 17 | const requireModule = require.context( 18 | // The relative path holding the service modules 19 | "./services", 20 | // Whether to look in subfolders 21 | false, 22 | // Only include .js files (prevents duplicate imports) 23 | /.js$/ 24 | ); 25 | const servicePlugins = requireModule 26 | .keys() 27 | .map(modulePath => requireModule(modulePath).default); 28 | 29 | export default new Vuex.Store({ 30 | plugins: [ 31 | // Use the spread operator to register all of the imported plugins 32 | ...servicePlugins 33 | // auth({ userService: 'users' }), // Populates user object on successful auth 34 | // service('another-service'), 35 | ] 36 | }); 37 | -------------------------------------------------------------------------------- /client/src/store/services/todos.js: -------------------------------------------------------------------------------- 1 | import feathersVuex from "feathers-vuex"; 2 | import feathersClient from "@/feathers"; 3 | 4 | const { service } = feathersVuex(feathersClient, { idField: "_id" }); 5 | const servicePath = "todos"; 6 | 7 | const servicePlugin = service(servicePath, { 8 | instanceDefaults: { 9 | _id: null, 10 | title: "", 11 | completed: null 12 | } 13 | }); 14 | 15 | feathersClient.service(servicePath).hooks({ 16 | before: { 17 | all: [], 18 | find: [], 19 | get: [], 20 | create: [], 21 | patch: [], 22 | remove: [] 23 | }, 24 | after: { 25 | all: [], 26 | find: [], 27 | get: [], 28 | create: [], 29 | update: [], 30 | patch: [], 31 | remove: [] 32 | }, 33 | error: { 34 | all: [], 35 | find: [], 36 | get: [], 37 | create: [], 38 | update: [], 39 | patch: [], 40 | remove: [] 41 | } 42 | }); 43 | 44 | export default servicePlugin; 45 | 46 | console.log("todos service loaded"); 47 | -------------------------------------------------------------------------------- /client/src/store/services/users.js: -------------------------------------------------------------------------------- 1 | import feathersVuex from "feathers-vuex"; 2 | import feathersClient from "@/feathers"; 3 | 4 | const { service } = feathersVuex(feathersClient, { idField: "_id" }); 5 | const servicePath = "users"; 6 | 7 | const servicePlugin = service(servicePath, { 8 | instanceDefaults: { 9 | email: "", 10 | firstName: "", 11 | lastName: "", 12 | get displayName() { 13 | return `${this.firstName} ${this.lastName}`; 14 | } 15 | } 16 | }); 17 | 18 | feathersClient.service(servicePath).hooks({ 19 | before: { 20 | all: [], 21 | find: [], 22 | get: [], 23 | create: [], 24 | patch: [], 25 | remove: [] 26 | }, 27 | after: { 28 | all: [], 29 | find: [], 30 | get: [], 31 | create: [], 32 | update: [], 33 | patch: [], 34 | remove: [] 35 | }, 36 | error: { 37 | all: [], 38 | find: [], 39 | get: [], 40 | create: [], 41 | update: [], 42 | patch: [], 43 | remove: [] 44 | } 45 | }); 46 | 47 | export default servicePlugin; 48 | 49 | console.log("users service loaded"); 50 | -------------------------------------------------------------------------------- /client/src/views/Todos.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 184 | 185 | 188 | -------------------------------------------------------------------------------- /client/tests/e2e/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ["cypress"], 3 | env: { 4 | mocha: true, 5 | "cypress/globals": true 6 | }, 7 | rules: { 8 | strict: "off" 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /client/tests/e2e/plugins/index.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/guides/guides/plugins-guide.html 2 | 3 | module.exports = (on, config) => { 4 | return Object.assign({}, config, { 5 | fixturesFolder: "tests/e2e/fixtures", 6 | integrationFolder: "tests/e2e/specs", 7 | screenshotsFolder: "tests/e2e/screenshots", 8 | videosFolder: "tests/e2e/videos", 9 | supportFile: "tests/e2e/support/index.js" 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /client/tests/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/api/introduction/api.html 2 | 3 | describe("My First Test", () => { 4 | it("Visits the app root url", () => { 5 | cy.visit("/"); 6 | cy.contains("h1", "Welcome to Your Vue.js App"); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /client/tests/e2e/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /client/tests/e2e/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import "./commands"; 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /client/tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /client/tests/unit/example.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from "@vue/test-utils"; 2 | import HelloWorld from "@/components/HelloWorld.vue"; 3 | 4 | describe("HelloWorld.vue", () => { 5 | it("renders props.msg when passed", () => { 6 | const msg = "new message"; 7 | const wrapper = shallowMount(HelloWorld, { 8 | propsData: { msg } 9 | }); 10 | expect(wrapper.text()).toMatch(msg); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdmtech/feathers-vuex-todomvc/671be7d737445e44c9d99d72ff708badc6b53610/logo.png -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdmtech/feathers-vuex-todomvc/671be7d737445e44c9d99d72ff708badc6b53610/preview.png -------------------------------------------------------------------------------- /server/.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 | -------------------------------------------------------------------------------- /server/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true, 5 | "mocha": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 2017 9 | }, 10 | "extends": "eslint:recommended", 11 | "rules": { 12 | "indent": [ 13 | "error", 14 | 2 15 | ], 16 | "linebreak-style": [ 17 | "error", 18 | "unix" 19 | ], 20 | "quotes": [ 21 | "error", 22 | "single" 23 | ], 24 | "semi": [ 25 | "error", 26 | "always" 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /server/.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 | # IDEs and editors (shamelessly copied from @angular/cli's .gitignore) 31 | /.idea 32 | .project 33 | .classpath 34 | .c9/ 35 | *.launch 36 | .settings/ 37 | *.sublime-workspace 38 | 39 | # IDE - VSCode 40 | .vscode/* 41 | !.vscode/settings.json 42 | !.vscode/tasks.json 43 | !.vscode/launch.json 44 | !.vscode/extensions.json 45 | 46 | ### Linux ### 47 | *~ 48 | 49 | # temporary files which can be created if a process still has a handle open of a deleted file 50 | .fuse_hidden* 51 | 52 | # KDE directory preferences 53 | .directory 54 | 55 | # Linux trash folder which might appear on any partition or disk 56 | .Trash-* 57 | 58 | # .nfs files are created when an open file is removed but is still being accessed 59 | .nfs* 60 | 61 | ### OSX ### 62 | *.DS_Store 63 | .AppleDouble 64 | .LSOverride 65 | 66 | # Icon must end with two \r 67 | Icon 68 | 69 | 70 | # Thumbnails 71 | ._* 72 | 73 | # Files that might appear in the root of a volume 74 | .DocumentRevisions-V100 75 | .fseventsd 76 | .Spotlight-V100 77 | .TemporaryItems 78 | .Trashes 79 | .VolumeIcon.icns 80 | .com.apple.timemachine.donotpresent 81 | 82 | # Directories potentially created on remote AFP share 83 | .AppleDB 84 | .AppleDesktop 85 | Network Trash Folder 86 | Temporary Items 87 | .apdisk 88 | 89 | ### Windows ### 90 | # Windows thumbnail cache files 91 | Thumbs.db 92 | ehthumbs.db 93 | ehthumbs_vista.db 94 | 95 | # Folder config file 96 | Desktop.ini 97 | 98 | # Recycle Bin used on file shares 99 | $RECYCLE.BIN/ 100 | 101 | # Windows Installer files 102 | *.cab 103 | *.msi 104 | *.msm 105 | *.msp 106 | 107 | # Windows shortcuts 108 | *.lnk 109 | 110 | # Others 111 | lib/ 112 | data/ 113 | -------------------------------------------------------------------------------- /server/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 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | # vffv-server 2 | 3 | > Vue, feathers and feathers-vuex demo repo 4 | 5 | ## About 6 | 7 | This project uses [Feathers](http://feathersjs.com). An open source web framework for building modern real-time applications. 8 | 9 | ## Getting Started 10 | 11 | Getting up and running is as easy as 1, 2, 3. 12 | 13 | 1. Make sure you have [NodeJS](https://nodejs.org/) and [npm](https://www.npmjs.com/) installed. 14 | 2. Install your dependencies 15 | 16 | ``` 17 | cd path/to/vffv-server; npm install 18 | ``` 19 | 20 | 3. Start your app 21 | 22 | ``` 23 | npm start 24 | ``` 25 | 26 | ## Testing 27 | 28 | Simply run `npm test` and all your tests in the `test/` directory will be run. 29 | 30 | ## Scaffolding 31 | 32 | Feathers has a powerful command line interface. Here are a few things it can do: 33 | 34 | ``` 35 | $ npm install -g @feathersjs/cli # Install Feathers CLI 36 | 37 | $ feathers generate service # Generate a new Service 38 | $ feathers generate hook # Generate a new Hook 39 | $ feathers help # Show all commands 40 | ``` 41 | 42 | ## Help 43 | 44 | For more information on all the things you can do with Feathers visit [docs.feathersjs.com](http://docs.feathersjs.com). 45 | 46 | ## Changelog 47 | 48 | __0.1.0__ 49 | 50 | - Initial release 51 | 52 | ## License 53 | 54 | Copyright (c) 2018 55 | 56 | Licensed under the [MIT license](LICENSE). 57 | -------------------------------------------------------------------------------- /server/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "localhost", 3 | "port": 3030, 4 | "public": "../public/", 5 | "paginate": { 6 | "default": 10, 7 | "max": 50 8 | }, 9 | "nedb": "../data" 10 | } 11 | -------------------------------------------------------------------------------- /server/config/production.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "vffv-server-app.feathersjs.com", 3 | "port": "PORT" 4 | } 5 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "feathers-vuex-todomvc-server", 3 | "description": "Vue, feathers and feathers-vuex todoMVC repo server", 4 | "version": "0.0.0", 5 | "homepage": "", 6 | "main": "src", 7 | "keywords": [ 8 | "feathers" 9 | ], 10 | "author": "Will Murray ", 11 | "contributors": [], 12 | "directories": { 13 | "test": "test" 14 | }, 15 | "engines": { 16 | "node": "^8.0.0", 17 | "npm": ">= 3.0.0" 18 | }, 19 | "scripts": { 20 | "test": "npm run eslint && npm run mocha", 21 | "eslint": "eslint src/. test/. --config .eslintrc.json", 22 | "dev": "nodemon src/", 23 | "start": "node src/", 24 | "mocha": "mocha test/ --recursive --exit", 25 | "upgrade-interactive": "npm-check --update" 26 | }, 27 | "dependencies": { 28 | "@feathersjs/configuration": "^2.0.2", 29 | "@feathersjs/errors": "^3.3.2", 30 | "@feathersjs/express": "^1.2.5", 31 | "@feathersjs/feathers": "^3.2.1", 32 | "@feathersjs/socketio": "^3.2.4", 33 | "compression": "^1.7.3", 34 | "cors": "^2.8.4", 35 | "feathers-nedb": "^3.1.0", 36 | "helmet": "^3.13.0", 37 | "nedb": "^1.8.0", 38 | "serve-favicon": "^2.5.0", 39 | "winston": "^3.1.0" 40 | }, 41 | "devDependencies": { 42 | "eslint": "^4.19.1", 43 | "mocha": "^5.2.0", 44 | "nodemon": "^1.18.4", 45 | "request": "^2.88.0", 46 | "request-promise": "^4.2.2" 47 | }, 48 | "license": "ISC" 49 | } 50 | -------------------------------------------------------------------------------- /server/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdmtech/feathers-vuex-todomvc/671be7d737445e44c9d99d72ff708badc6b53610/server/public/favicon.ico -------------------------------------------------------------------------------- /server/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Welcome to Feathers 4 | 62 | 63 | 64 |
65 | 66 |

A REST and realtime API layer for modern applications.

67 | 68 | 71 |
72 | 73 | 74 | -------------------------------------------------------------------------------- /server/src/app.hooks.js: -------------------------------------------------------------------------------- 1 | // Application hooks that run for every service 2 | const log = require('./hooks/log'); 3 | 4 | module.exports = { 5 | before: { 6 | all: [ log() ], 7 | find: [], 8 | get: [], 9 | create: [], 10 | update: [], 11 | patch: [], 12 | remove: [] 13 | }, 14 | 15 | after: { 16 | all: [ log() ], 17 | find: [], 18 | get: [], 19 | create: [], 20 | update: [], 21 | patch: [], 22 | remove: [] 23 | }, 24 | 25 | error: { 26 | all: [ log() ], 27 | find: [], 28 | get: [], 29 | create: [], 30 | update: [], 31 | patch: [], 32 | remove: [] 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /server/src/app.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const favicon = require('serve-favicon'); 3 | const compress = require('compression'); 4 | const helmet = require('helmet'); 5 | const cors = require('cors'); 6 | const logger = require('./logger'); 7 | 8 | const feathers = require('@feathersjs/feathers'); 9 | const configuration = require('@feathersjs/configuration'); 10 | const express = require('@feathersjs/express'); 11 | const socketio = require('@feathersjs/socketio'); 12 | 13 | 14 | const middleware = require('./middleware'); 15 | const services = require('./services'); 16 | const appHooks = require('./app.hooks'); 17 | const channels = require('./channels'); 18 | 19 | const app = express(feathers()); 20 | 21 | // Load app configuration 22 | app.configure(configuration()); 23 | // Enable security, CORS, compression, favicon and body parsing 24 | app.use(helmet()); 25 | app.use(cors()); 26 | app.use(compress()); 27 | app.use(express.json()); 28 | app.use(express.urlencoded({ extended: true })); 29 | app.use(favicon(path.join(app.get('public'), 'favicon.ico'))); 30 | // Host the public folder 31 | app.use('/', express.static(app.get('public'))); 32 | 33 | // Set up Plugins and providers 34 | app.configure(express.rest()); 35 | app.configure(socketio()); 36 | 37 | // Configure other middleware (see `middleware/index.js`) 38 | app.configure(middleware); 39 | // Set up our services (see `services/index.js`) 40 | app.configure(services); 41 | // Set up event channels (see channels.js) 42 | app.configure(channels); 43 | 44 | // Configure a middleware for 404s and the error handler 45 | app.use(express.notFound()); 46 | app.use(express.errorHandler({ logger })); 47 | 48 | app.hooks(appHooks); 49 | 50 | module.exports = app; 51 | -------------------------------------------------------------------------------- /server/src/channels.js: -------------------------------------------------------------------------------- 1 | module.exports = function(app) { 2 | if(typeof app.channel !== 'function') { 3 | // If no real-time functionality has been configured just return 4 | return; 5 | } 6 | 7 | app.on('connection', connection => { 8 | // On a new real-time connection, add it to the anonymous channel 9 | app.channel('anonymous').join(connection); 10 | }); 11 | 12 | app.on('login', (authResult, { connection }) => { 13 | // connection can be undefined if there is no 14 | // real-time connection, e.g. when logging in via REST 15 | if(connection) { 16 | // Obtain the logged in user from the connection 17 | // const user = connection.user; 18 | 19 | // The connection is no longer anonymous, remove it 20 | app.channel('anonymous').leave(connection); 21 | 22 | // Add it to the authenticated user channel 23 | app.channel('authenticated').join(connection); 24 | 25 | // Channels can be named anything and joined on any condition 26 | 27 | // E.g. to send real-time events only to admins use 28 | // if(user.isAdmin) { app.channel('admins').join(connection); } 29 | 30 | // If the user has joined e.g. chat rooms 31 | // if(Array.isArray(user.rooms)) user.rooms.forEach(room => app.channel(`rooms/${room.id}`).join(channel)); 32 | 33 | // Easily organize users by email and userid for things like messaging 34 | // app.channel(`emails/${user.email}`).join(channel); 35 | // app.channel(`userIds/$(user.id}`).join(channel); 36 | } 37 | }); 38 | 39 | // eslint-disable-next-line no-unused-vars 40 | app.publish((data, hook) => { 41 | // Here you can add event publishers to channels set up in `channels.js` 42 | // To publish only for a specific event use `app.publish(eventname, () => {})` 43 | 44 | console.log('Publishing all events to all ANONYMOUS users. See `channels.js` and https://docs.feathersjs.com/api/channels.html for more information.'); // eslint-disable-line 45 | 46 | // e.g. to publish all service events to all authenticated users use 47 | // return app.channel('authenticated'); 48 | 49 | // e.g. to publish all service events to all ANONYMOUS users use 50 | return app.channel('anonymous'); 51 | }); 52 | 53 | // Here you can also add service specific event publishers 54 | // e.g. the publish the `users` service `created` event to the `admins` channel 55 | // app.service('users').publish('created', () => app.channel('admins')); 56 | 57 | // With the userid and email organization from above you can easily select involved users 58 | // app.service('messages').publish(() => { 59 | // return [ 60 | // app.channel(`userIds/${data.createdBy}`), 61 | // app.channel(`emails/${data.recipientEmail}`) 62 | // ]; 63 | // }); 64 | }; 65 | -------------------------------------------------------------------------------- /server/src/hooks/log.js: -------------------------------------------------------------------------------- 1 | // A hook that logs service method before, after and error 2 | // See https://github.com/winstonjs/winston for documentation 3 | // about the logger. 4 | const logger = require('../logger'); 5 | const util = require('util'); 6 | 7 | // To see more detailed messages, uncomment the following line: 8 | // logger.level = 'debug'; 9 | 10 | module.exports = function () { 11 | return context => { 12 | // This debugs the service call and a stringified version of the hook context 13 | // You can customize the message (and logger) to your needs 14 | logger.debug(`${context.type} app.service('${context.path}').${context.method}()`); 15 | 16 | if(typeof context.toJSON === 'function' && logger.level === 'debug') { 17 | logger.debug('Hook Context', util.inspect(context, {colors: false})); 18 | } 19 | 20 | if(context.error) { 21 | logger.error(context.error.stack); 22 | } 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /server/src/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const logger = require('./logger'); 3 | const app = require('./app'); 4 | const port = app.get('port'); 5 | const server = app.listen(port); 6 | 7 | process.on('unhandledRejection', (reason, p) => 8 | logger.error('Unhandled Rejection at: Promise ', p, reason) 9 | ); 10 | 11 | server.on('listening', () => 12 | logger.info('Feathers application started on http://%s:%d', app.get('host'), port) 13 | ); 14 | -------------------------------------------------------------------------------- /server/src/logger.js: -------------------------------------------------------------------------------- 1 | const { createLogger, format, transports } = require('winston'); 2 | 3 | // Configure the Winston logger. For the complete documentation see https://github.com/winstonjs/winston 4 | const logger = createLogger({ 5 | // To see more detailed errors, change this to 'debug' 6 | level: 'info', 7 | format: format.combine( 8 | format.splat(), 9 | format.simple() 10 | ), 11 | transports: [ 12 | new transports.Console() 13 | ], 14 | }); 15 | 16 | module.exports = logger; 17 | -------------------------------------------------------------------------------- /server/src/middleware/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | module.exports = function (app) { 3 | // Add your custom middleware here. Remember that 4 | // in Express, the order matters. 5 | }; 6 | -------------------------------------------------------------------------------- /server/src/models/todos.model.js: -------------------------------------------------------------------------------- 1 | const NeDB = require('nedb'); 2 | const path = require('path'); 3 | 4 | module.exports = function (app) { 5 | const dbPath = app.get('nedb'); 6 | const Model = new NeDB({ 7 | filename: path.join(dbPath, 'todos.db'), 8 | autoload: true 9 | }); 10 | 11 | return Model; 12 | }; 13 | -------------------------------------------------------------------------------- /server/src/services/index.js: -------------------------------------------------------------------------------- 1 | const todos = require('./todos/todos.service.js'); 2 | // eslint-disable-next-line no-unused-vars 3 | module.exports = function (app) { 4 | app.configure(todos); 5 | }; 6 | -------------------------------------------------------------------------------- /server/src/services/todos/todos.hooks.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = { 4 | before: { 5 | all: [], 6 | find: [], 7 | get: [], 8 | create: [], 9 | update: [], 10 | patch: [], 11 | remove: [] 12 | }, 13 | 14 | after: { 15 | all: [], 16 | find: [], 17 | get: [], 18 | create: [], 19 | update: [], 20 | patch: [], 21 | remove: [] 22 | }, 23 | 24 | error: { 25 | all: [], 26 | find: [], 27 | get: [], 28 | create: [], 29 | update: [], 30 | patch: [], 31 | remove: [] 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /server/src/services/todos/todos.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `todos` service on path `/todos` 2 | const createService = require('feathers-nedb'); 3 | const createModel = require('../../models/todos.model'); 4 | const hooks = require('./todos.hooks'); 5 | 6 | module.exports = async function (app) { 7 | const Model = createModel(app); 8 | const paginate = app.get('paginate'); 9 | 10 | const options = { 11 | Model, 12 | paginate 13 | }; 14 | 15 | // Initialize our service with any options it requires 16 | app.use('/todos', createService(options)); 17 | 18 | // Get our initialized service so that we can register hooks 19 | const service = app.service('todos'); 20 | 21 | const todos = [ 22 | { title: 'Learn vue', completed: true }, 23 | { title: 'Learn vuex', completed: true }, 24 | { title: 'Learn feathers', completed: true }, 25 | { title: 'Learn feathers-vuex', completed: false }, 26 | ]; 27 | 28 | for (let todo of todos) { 29 | const found = await service.find({ query: { title: todo.title } }); 30 | if (!found.total) { 31 | service.create(todo); 32 | } 33 | } 34 | 35 | service.hooks(hooks); 36 | }; 37 | -------------------------------------------------------------------------------- /server/test/app.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const rp = require('request-promise'); 3 | const url = require('url'); 4 | const app = require('../src/app'); 5 | 6 | const port = app.get('port') || 3030; 7 | const getUrl = pathname => url.format({ 8 | hostname: app.get('host') || 'localhost', 9 | protocol: 'http', 10 | port, 11 | pathname 12 | }); 13 | 14 | describe('Feathers application tests', () => { 15 | before(function(done) { 16 | this.server = app.listen(port); 17 | this.server.once('listening', () => done()); 18 | }); 19 | 20 | after(function(done) { 21 | this.server.close(done); 22 | }); 23 | 24 | it('starts and shows the index page', () => { 25 | return rp(getUrl()).then(body => 26 | assert.ok(body.indexOf('') !== -1) 27 | ); 28 | }); 29 | 30 | describe('404', function() { 31 | it('shows a 404 HTML page', () => { 32 | return rp({ 33 | url: getUrl('path/to/nowhere'), 34 | headers: { 35 | 'Accept': 'text/html' 36 | } 37 | }).catch(res => { 38 | assert.equal(res.statusCode, 404); 39 | assert.ok(res.error.indexOf('') !== -1); 40 | }); 41 | }); 42 | 43 | it('shows a 404 JSON error without stack trace', () => { 44 | return rp({ 45 | url: getUrl('path/to/nowhere'), 46 | json: true 47 | }).catch(res => { 48 | assert.equal(res.statusCode, 404); 49 | assert.equal(res.error.code, 404); 50 | assert.equal(res.error.message, 'Page not found'); 51 | assert.equal(res.error.name, 'NotFound'); 52 | }); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /server/test/services/todos.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const app = require('../../src/app'); 3 | 4 | describe('\'todos\' service', () => { 5 | it('registered the service', () => { 6 | const service = app.service('todos'); 7 | 8 | assert.ok(service, 'Registered the service'); 9 | }); 10 | }); 11 | --------------------------------------------------------------------------------