├── .gitignore ├── README.md └── quasar-feathers ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── .stylintrc ├── README.md ├── api ├── .babelrc ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── build │ └── script.clean.js ├── config │ └── default.js ├── data │ ├── messages.db │ └── users.db ├── package-lock.json ├── package.json ├── src │ ├── app.js │ ├── authentication.js │ ├── channels.js │ ├── hooks │ │ ├── gravatar.js │ │ ├── logger.js │ │ └── process-message.js │ ├── main.hooks.js │ ├── main.js │ ├── middleware │ │ └── index.js │ ├── models │ │ ├── messages.model.js │ │ └── users.model.js │ ├── server.js │ └── services │ │ ├── index.js │ │ ├── messages │ │ ├── messages.hooks.js │ │ └── messages.service.js │ │ └── users │ │ ├── users.hooks.js │ │ └── users.service.js └── yarn.lock ├── config ├── dev.env.js ├── index.js └── prod.env.js ├── docker-compose.yml ├── dockerfile ├── package-lock.json ├── package.json ├── quasar.conf.js ├── src ├── App.vue ├── api.js ├── assets │ ├── feathers-logo.png │ ├── quasar-logo-full.svg │ ├── quasar-logo.png │ └── sad.svg ├── auth.js ├── components │ └── .gitkeep ├── css │ ├── app.styl │ └── themes │ │ ├── common.variables.styl │ │ ├── variables.ios.styl │ │ └── variables.mat.styl ├── index.template.html ├── layouts │ └── default.vue ├── pages │ ├── 404.vue │ ├── Chat.vue │ ├── Home.vue │ └── SignIn.vue ├── plugins │ └── .gitkeep ├── router │ ├── index.js │ └── routes.js └── statics │ ├── icons │ ├── apple-icon-152x152.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── icon-128x128.png │ ├── icon-192x192.png │ ├── icon-256x256.png │ ├── icon-384x384.png │ ├── icon-512x512.png │ └── ms-icon-144x144.png │ └── quasar-logo.png └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | .history/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Combining the power of [Quasar](http://quasar-framework.org/) and [Feathers](http://feathersjs.com/) to build real-time web apps 2 | 3 | A couple of days ago I 've started looking at what could be the foundation to build good-looking real-time web apps for new projects. Starting from a [Angular 1.x](https://angularjs.org/) background I was looking for something based on new Javascript standards (TypeScript, ES2015, ES2016, etc.), lightweight and easy to learn, as well as having less to do with the big players. I found [Vue.js](https://vuejs.org/) that mostly satisfy all these criteria. However, it missed a built-in component library, which brings me naturally to find [Quasar](http://quasar-framework.org/). 4 | 5 | Then I looked for something similar to handle the most basic tasks of creating real-time web apps on the server-side, I dreamed of a framework handling indifferently REST/socket API calls, with built-in support for most authentication schemes, being database/transport agnostic so that I could develop microservices powering different technologies. I naturally found [Feathers](https://blog.feathersjs.com/introducing-feathers-2-0-aae8ae8e7920), which additionnaly provides all of this with a plugin based architecture around a minimalist core. 6 | 7 | I decided to start building a basic real-time chat app intensively inspired from https://github.com/feathersjs/feathers-chat (many thanks to the Feathers team for their great material): 8 | 9 | [![Authentication video](https://img.youtube.com/vi/_iqnjpQ9gRo/0.jpg)](https://www.youtube.com/watch?v=_iqnjpQ9gRo) 10 | [![Chat video](https://img.youtube.com/vi/te1w33vaDXI/0.jpg)](https://www.youtube.com/watch?v=te1w33vaDXI) 11 | 12 | ## Contributors 13 | 14 | This tutorial has been enhanced and maintained thanks to the following contributors: 15 | * [Christophe Nouguier](https://github.com/cnouguier) 16 | * [Leo van den Bulck](https://github.com/leob) 17 | 18 | ## Disclaimer 19 | 20 | Although this tutorial details the path to create an application skeleton featuring Quasar and Feathers from scratch, as well as code details, most of this work is currently under integration in the Quasar ecosystem. Indeed, Quasar provides the **wrapper** concept which allows to plug the frontend app into a larger piece of work such as Electron or Express powered backend. The simplest way to retrieve an up-to-date version and start with this application skeleton is to use the Quasar Feathers wrapper guide https://github.com/quasarframework/quasar-wrapper-feathersjs-api. 21 | 22 | You can also download the source code of the app by cloning this repo and jump in the **quasar-feathers** directory: 23 | ``` 24 | npm/yarn install -g quasar-cli 25 | git clone https://github.com/claustres/quasar-feathers-tutorial 26 | cd quasar-feathers-tutorial 27 | cd quasar-feathers 28 | npm/yarn install 29 | quasar dev 30 | // Then in another terminal 31 | cd api 32 | npm/yarn install 33 | npm run dev 34 | ``` 35 | Open your browser on `localhost:8080`. 36 | 37 | Last but not least, I assume your are familiar with the [Vue.js](https://vuejs.org/) and [Node.js](https://nodejs.org) ecosystem. 38 | 39 | ## Installation and configuration 40 | 41 | **This tutorial has been upgraded to Quasar version 0.15.10 and Feathers version 3.1.0 (Buzzard).** 42 | 43 | This tutorial has been initially made with Quasar version 0.13.4 and Feathers version 2.1.1. You can find previous version in the [releases](https://github.com/claustres/quasar-feathers-tutorial/releases). 44 | 45 | Feel free to submit any problem by opening an [Issue](https://github.com/claustres/quasar-feathers-tutorial/issues) or upgrade the code with a [Pull Request](https://github.com/claustres/quasar-feathers-tutorial/pulls). 46 | 47 | Each framework provides its own CLI so that starting a project is easy, with a couple of instructions you have everything ready to start coding your app. 48 | 49 | Quasar for the frontend: 50 | ```bash 51 | $ npm install -g quasar-cli 52 | $ quasar init quasar-feathers 53 | $ cd quasar-feathers 54 | $ npm install 55 | // Will launch the frontend server in dev mode on 8080 56 | $ quasar dev 57 | ``` 58 | 59 | Feathers for the backend in the app root directory: 60 | ```bash 61 | $ npm install -g @feathersjs/cli 62 | $ mkdir api 63 | $ cd api 64 | // For latest Feathers (Auk release) 65 | $ feathers generate app 66 | $ feathers generate authentication 67 | // For legacy Feathers (Pre-Auk releases) 68 | $ feathers generate 69 | // Will launch the backend server in dev mode on 3030 70 | $ npm start 71 | ``` 72 | 73 | The default [NeDB](https://github.com/louischatriot/nedb) datastore is fine for our tutorial because it does not rely on any third-party DB software to be installed. Because we generated the Feathers boilerplate with authentication we already have a **user** service providing [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) operations as well. But as we want to develop a chat application we miss a **message** service so we generate it in the backend folder: 74 | ```bash 75 | feathers generate service 76 | ``` 77 | 78 | Provide the following answers after issuing the ```feathers generate service``` command: 79 | 80 | ``` 81 | ? What kind of service is it? NeDB 82 | ? What is the name of the service? messages 83 | ? Which path should the service be registered on? /messages 84 | ? Does the service require authentication? Yes 85 | ? What is the database connection string? nedb://../data 86 | force config/default.json 87 | create src/services/messages/messages.service.js 88 | force src/services/index.js 89 | create src/models/messages.model.js 90 | create src/services/messages/messages.hooks.js 91 | create test/services/messages.test.js 92 | ``` 93 | 94 | To make the Quasar app correctly contacting the backend you have to configure an API proxy in your frontend **config/index.js**: 95 | ```javascript 96 | ... 97 | dev: { 98 | proxyTable: { 99 | '/api': { 100 | target: 'http://localhost:3030', 101 | changeOrigin: true 102 | } 103 | } 104 | ... 105 | ``` 106 | 107 | ## API glue 108 | 109 | Feathers provides you with a thin layer on the client-side to make API authentication and calls so simple. We create a new **src/api.js** file in the frontend to handle the glue with the API: 110 | ```javascript 111 | import feathers from '@feathersjs/feathers' 112 | import socketio from '@feathersjs/socketio-client' 113 | import auth from '@feathersjs/authentication-client' 114 | import io from 'socket.io-client' 115 | 116 | const socket = io('http://localhost:3030', {transports: ['websocket']}) 117 | 118 | const api = feathers() 119 | .configure(socketio(socket)) 120 | .configure(auth({ storage: window.localStorage })) 121 | 122 | api.service('/users') 123 | api.service('/messages') 124 | 125 | export default api 126 | ``` 127 | 128 | Now the API is easy to integrate in any component to perform the various tasks we need, e.g.: 129 | ```javascript 130 | import api from 'src/api' 131 | const users = api.service('users') 132 | // Authenticate 133 | api.authenticate({ 134 | strategy: 'local', 135 | email: email, 136 | password: password 137 | }).then(_ => { 138 | Toast.create.positive('Authenticated') 139 | }) 140 | // Get all users 141 | users.find().then((response) => { 142 | this.$data.users = response.data 143 | }) 144 | // Listen to user events 145 | users.on('created', user => { 146 | this.$data.users = this.$data.users.concat(user) 147 | }) 148 | ``` 149 | 150 | ## Main layout 151 | 152 | From a end-user perspective the application will be simple: 153 | - a menu toolbar including (**src/layouts/default.vue** component) 154 | - a sign in/register entry when not connected 155 | - home/chat entries and a signout menu to logout when connected 156 | - a sidebar menu recalling the home/chat entries and a about section 157 | - a landing home page displaying different text depending on the connection state (**src/pages/Home.vue** component) 158 | - a signin/register form with email/password (**src/pages/SignIn.vue** component) 159 | - a chat view listing available users and providing real-time messages read/write (**src/pages/Chat.vue** component) 160 | 161 | The main app layout is already part of the Quasar default template so we will directly modify it but additional components can be generated using the CLI: 162 | ```bash 163 | $ quasar new page Home 164 | $ quasar new page SignIn 165 | $ quasar new page Chat 166 | ``` 167 | 168 | We update the layout of the **src/layouts/default.vue** template to include a [Toolbar with some entries](http://quasar-framework.org/components/toolbar.html), a logout [button](http://quasar-framework.org/components/button.html), a [Sidebar menu](http://quasar-framework.org/components/layout.html#Navigation-from-drawer-panels) and an [entry point for other components](https://router.vuejs.org/en/api/router-view.html): 169 | ```html 170 | 171 | 172 | 177 | 185 | 186 | 187 | 188 | 189 | Quasar + Feathers boilerplate 190 | 191 | 192 | 193 | Sign In 194 | 195 | 196 | Register 197 | 198 | 199 | 200 | Home 201 | 202 | 203 | 204 | Chat 205 | 206 | 207 | 208 | Signout 209 | 210 | 211 | 212 | 213 | 214 | 219 | 220 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 |

238 | This is a template project combining the power of Quasar and Feathers to create real-time web apps. 239 |

240 |
241 | 242 |
243 |
244 | 245 | 246 | 247 | 248 |
249 | ``` 250 | 251 | We update the router configuration in **src/router/routes.js** to reflect this as well: 252 | ```javascript 253 | export default [ 254 | { 255 | path: '/', 256 | component: () => import('layouts/default'), 257 | children: [ 258 | { path: '/home', name: 'home', component: () => import('pages/Home') }, 259 | { path: '/signin', name: 'signin', component: () => import('pages/SignIn') }, 260 | { path: '/register', name: 'register', component: () => import('pages/SignIn') }, 261 | { path: '/chat', name: 'chat', component: () => import('pages/Chat'), meta: { requiresAuth: true } } 262 | ] 263 | }, 264 | 265 | { // Always leave this as last one 266 | path: '*', 267 | component: () => import('pages/404') 268 | } 269 | ] 270 | ``` 271 | 272 | We set a ```requiresAuth: true``` flag in the ```meta``` property of the 'chat' route. This is used to implement a Vue "router guard" which enforces authentication on the Chat page (the Chat page should only be accessible after the user logs in). 273 | 274 | The router guard goes into the **src/router/index.js** file and consists of a ```router.beforeEach``` method which accepts three parameters (```to```, ```from``` and ```next```), as shown below. 275 | 276 | ```javascript 277 | ... 278 | // Import the client 'auth' module which allows us to check if there's an authenticated user 279 | import auth from 'src/auth' 280 | ... 281 | router.beforeEach((to, from, next) => { 282 | 283 | if (!to.meta.requiresAuth || auth.authenticated()) { 284 | // All is okay, let the route change continue 285 | next() 286 | } else { 287 | console.log('Not authenticated') 288 | // Cancel the route change and redirect back to the Home page 289 | next({ path: '/home' }) 290 | } 291 | }) 292 | ... 293 | ``` 294 | 295 | The ```to``` parameter is the route which we are trying to navigate to, ```from``` is the route we're coming from, and ```next``` is a callback function which we'll call to determine the outcome of the route change attempt. Consult [this](https://router.vuejs.org/en/advanced/navigation-guards.html) page for details. 296 | 297 | If we see that the route that we try to navigate to requires authentication (this is the ```meta.requiresAuth``` property which we've set in **src/router/index.js**), then we check if there is an authenticated user. If there isn't, then we cancel the navigation attempt, and redirect back to the Home page (from where the user can log in) by calling ```next({ path: '/home' })```. 298 | 299 | The 'auth' module which we use to check if there's an authenticated user will be created later. 300 | 301 | ## Authentication 302 | 303 | ### Backend 304 | 305 | In the boilerplate a [local authentication strategy](https://docs.feathersjs.com/authentication/local.html) has been setup based on a [JSON Web Token](https://docs.feathersjs.com/authentication/token.html) in **api/src/authentication.js**: 306 | ```javascript 307 | const authentication = require('@feathersjs/authentication') 308 | const jwt = require('@feathersjs/authentication-jwt') 309 | const local = require('@feathersjs/authentication-local') 310 | 311 | module.exports = function() { 312 | const app = this 313 | const config = app.get('authentication') 314 | 315 | // Set up authentication with the secret 316 | app.configure(authentication(config)) 317 | app.configure(jwt()) 318 | app.configure(local()) 319 | // The `authentication` service is used to create a JWT. 320 | // The before `create` hook registers strategies that can be used 321 | // to create a new valid JWT (e.g. local or oauth2) 322 | app.service('authentication').hooks({ 323 | before: { 324 | create: [ 325 | authentication.hooks.authenticate(config.strategies) 326 | ], 327 | remove: [ 328 | authentication.hooks.authenticate('jwt') 329 | ] 330 | } 331 | }) 332 | } 333 | ``` 334 | 335 | ### Frontend 336 | 337 | We create a new **src/auth.js** file in the frontend to manage authentication and keep track of the logged in user. This module acts as a simple wrapper around the Feathers authentication API, which makes the Vue components a bit simpler. We also used it in the Vue router guard, to determine if there is an authenticated user (see before). 338 | ```javascript 339 | // Import the Feathers client module that we've created before 340 | import api from 'src/api' 341 | 342 | const auth = { 343 | 344 | // keep track of the logged in user 345 | user: null, 346 | 347 | getUser() { 348 | return this.user 349 | }, 350 | 351 | fetchUser (accessToken) { 352 | 353 | return api.passport.verifyJWT(accessToken) 354 | .then(payload => { 355 | return api.service('users').get(payload.userId) 356 | }) 357 | .then(user => { 358 | return Promise.resolve(user) 359 | }) 360 | }, 361 | 362 | authenticate () { 363 | 364 | return api.authenticate() 365 | .then((response) => { 366 | return this.fetchUser(response.accessToken) 367 | }) 368 | .then(user => { 369 | this.user = user 370 | return Promise.resolve(user) 371 | }) 372 | .catch((err) => { 373 | this.user = null 374 | return Promise.reject(err) 375 | }) 376 | }, 377 | 378 | authenticated () { 379 | return this.user != null 380 | }, 381 | 382 | signout () { 383 | 384 | return api.logout() 385 | .then(() => { 386 | this.user = null 387 | }) 388 | .catch((err) => { 389 | return Promise.reject(err) 390 | }) 391 | }, 392 | 393 | onLogout (callback) { 394 | 395 | api.on('logout', () => { 396 | this.user = null 397 | callback() 398 | }) 399 | }, 400 | 401 | onAuthenticated (callback) { 402 | 403 | api.on('authenticated', response => { 404 | this.fetchUser(response.accessToken) 405 | .then(user => { 406 | this.user = user 407 | callback(this.user) 408 | }) 409 | .catch((err) => { 410 | callback(this.user) 411 | }) 412 | }) 413 | }, 414 | 415 | register (email, password) { 416 | return api.service('users').create({ 417 | email: email, 418 | password: password 419 | }) 420 | }, 421 | 422 | login (email, password) { 423 | return api.authenticate({ 424 | strategy: 'local', 425 | email: email, 426 | password: password 427 | }) 428 | } 429 | 430 | } 431 | 432 | export default auth 433 | ``` 434 | 435 | On the frontend we setup the **src/components/SignIn.vue** component as a [basic dialog](http://quasar-framework.org/components/dialog.html) with e-mail/password inputs: 436 | ```javascript 437 | 455 | 456 | 522 | 523 | 525 | ``` 526 | 527 | We manage registration as well as login, depending on the route used to reach the component. 528 | 529 | The component's ```login``` and ```register``` method simply delegate to the login/register methods of the ```auth``` module that we've created before. 530 | 531 | Once connected, the user should land on the home page then be able to navigate in the app, so that in the main layout we have to track the login state as the currently connected user in **$data.user** (null if not logged in). We will also manage logout from the profile menu entry and restoring the previous session if any by trying to authenticate on mounting **src/layouts/default.vue**: 532 | ```javascript 533 | import auth from 'src/auth' 534 | 535 | export default { 536 | name: 'index', 537 | components: { 538 | }, 539 | data () { 540 | return { 541 | leftDrawerOpen: this.$q.platform.is.desktop, 542 | user: null 543 | } 544 | }, 545 | computed: { 546 | authenticated () { 547 | return this.$data.user !== null 548 | } 549 | }, 550 | methods: { 551 | goTo (route) { 552 | this.$router.push({ name: route }) 553 | }, 554 | signout () { 555 | auth.signout() 556 | .then(() => { 557 | this.$q.notify({type: 'positive', message: 'You are now logged out, sign in again to continue to work'}) 558 | }) 559 | .catch(() => { 560 | this.$q.notify({type: 'positive', message: 'Cannot logout, please check again in a few minutes'}) 561 | }) 562 | }, 563 | setUser (user) { 564 | this.$data.user = user 565 | } 566 | }, 567 | mounted () { 568 | // Check if there is already a session running 569 | auth.authenticate() 570 | .then((user) => { 571 | this.setUser(user) 572 | this.$q.notify({type: 'positive', message: 'Restoring previous session'}) 573 | }) 574 | .catch(_ => { 575 | this.setUser(null) 576 | this.$router.push({ name: 'home' }) 577 | }) 578 | 579 | // On successful login 580 | auth.onAuthenticated((user) => { 581 | this.setUser(user) 582 | this.$router.push({ name: 'home' }) 583 | }) 584 | 585 | // On logout 586 | auth.onLogout(() => { 587 | this.setUser(null) 588 | this.$router.push({ name: 'home' }) 589 | }) 590 | }, 591 | beforeDestroy () { 592 | } 593 | } 594 | ``` 595 | 596 | We make the current user available to sub components easily using a **user** [prop](https://vuejs.org/v2/guide/components.html#Props) in the default layout template: 597 | ```html 598 | 599 | ``` 600 | 601 | ## Real-time chat 602 | 603 | Now most of the skeleton is in place the main feature of our app remains to be developed. 604 | 605 | ### Backend 606 | 607 | In the boilerplate we generated a basic model for our messages and datastore in **api/src/models/messages.model.js**: 608 | ```javascript 609 | const NeDB = require('nedb') 610 | const path = require('path') 611 | 612 | module.exports = function(app) { 613 | const dbPath = app.get('nedb') 614 | const Model = new NeDB({ 615 | filename: path.join(dbPath, 'messages.db'), 616 | autoload: true 617 | }) 618 | 619 | return Model 620 | } 621 | ``` 622 | 623 | We then add a [hook](https://docs.feathersjs.com/hooks/readme.html) in **api/src/hooks/process-message.js** to automatically process messages on creation in order to: 624 | - do some basic escaping of the content 625 | - add the creation date 626 | - add the ID of the user that created it 627 | 628 | ```javascript 629 | module.exports = function() { 630 | return function(hook) { 631 | // The authenticated user 632 | const user = hook.params.user 633 | // The actual message text 634 | const text = hook.data.text 635 | // Messages can't be longer than 400 characters 636 | .substring(0, 400) 637 | // Do some basic HTML escaping 638 | .replace(/&/g,'&').replace(//g,'>') 639 | // Override the original data 640 | hook.data = { 641 | text, 642 | // Set the user id 643 | userId: user._id, 644 | // Add the current time via `getTime` 645 | createdAt: new Date().getTime() 646 | } 647 | // Hooks can either return nothing or a promise 648 | // that resolves with the `hook` object for asynchronous operations 649 | return Promise.resolve(hook) 650 | } 651 | } 652 | ``` 653 | We include this hook for our messages, as well as the one for authentication and the one to automatically populate the user that created the message, in **api/src/services/messages/messages.hooks.js**: 654 | ```javascript 655 | const { authenticate } = require('@feathersjs/authentication').hooks 656 | const { populate } = require('feathers-hooks-common') 657 | const processMessage = require('../../hooks/process-message') 658 | 659 | module.exports = { 660 | before: { 661 | all: [ authenticate('jwt') ], 662 | find: [], 663 | get: [], 664 | create: [ processMessage() ], 665 | update: [ processMessage() ], 666 | patch: [ processMessage() ], 667 | remove: [] 668 | }, 669 | after: { 670 | all: [ 671 | populate({ 672 | schema: { 673 | include: [{ 674 | service: 'users', 675 | nameAs: 'user', 676 | parentField: 'userId', 677 | childField: '_id' 678 | }] 679 | } 680 | }) 681 | ], 682 | ... 683 | }, 684 | error: { 685 | ... 686 | } 687 | } 688 | 689 | ``` 690 | 691 | One more [hook](https://docs.feathersjs.com/hooks/readme.html) in **api/src/hooks/gravatar.js** will help us provide each user with his [Gravatar](https://www.gravatar.com/) in order to have a beautiful picture in the chat view: 692 | ```javascript 693 | // We need this to create the MD5 hash 694 | const crypto = require('crypto') 695 | // The Gravatar image service 696 | const gravatarUrl = 'https://s.gravatar.com/avatar' 697 | // The size query. Our chat needs 60px images 698 | const query = 's=200' 699 | 700 | module.exports = function() { 701 | return function(hook) { 702 | // The user email 703 | const { email } = hook.data 704 | // Gravatar uses MD5 hashes from an email address to get the image 705 | const hash = crypto.createHash('md5').update(email).digest('hex') 706 | hook.data.avatar = `${gravatarUrl}/${hash}?${query}` 707 | // Hooks can either return nothing or a promise 708 | // that resolves with the `hook` object for asynchronous operations 709 | return Promise.resolve(hook); 710 | } 711 | } 712 | ``` 713 | We include this hook for our users, as well as the one for authentication (except to be able to create a user when registering), in **api/src/services/users/users.hooks.js**: 714 | ```javascript 715 | const { authenticate } = require('@feathersjs/authentication').hooks 716 | const { hashPassword, protect } = require('@feathersjs/authentication-local').hooks; 717 | const commonHooks = require('feathers-hooks-common') 718 | const gravatar = require('../../hooks/gravatar') 719 | 720 | module.exports = { 721 | before: { 722 | all: [], 723 | find: [ authenticate('jwt') ], 724 | get: [ authenticate('jwt') ], 725 | create: [hashPassword(), gravatar()], 726 | update: [ authenticate('jwt') ], 727 | patch: [ authenticate('jwt') ], 728 | remove: [ authenticate('jwt') ] 729 | }, 730 | after: { 731 | all: [ 732 | commonHooks.when( 733 | hook => hook.params.provider, 734 | protect('password') 735 | ) 736 | ], 737 | ... 738 | }, 739 | error: { 740 | ... 741 | } 742 | } 743 | 744 | ``` 745 | 746 | ### Frontend 747 | 748 | Helpfully Quasar comes with a built-in [chat component](http://quasar-framework.org/components/chat.html) that we will use to display our messages. We will also use the built-in [list](http://quasar-framework.org/components/lists-and-list-items.html) to list available people. Last, we will use a simple [text input](http://quasar-framework.org/components/input.html#Labeling) to send messages in the chat room. Inside the component these data are respectively stored in **$data.messages**, **$data.users**, **$data.message**. The final template of the **src/components/Chat.vue** component is thus the following: 749 | ```html 750 | 751 |
752 |
753 | 759 |
760 | 761 | People 762 | 763 | 764 | 765 | {{user.email}} 766 | 767 | 768 | 769 | 770 | 771 | 772 |
773 | 782 |
783 | ``` 784 | 785 | As you can see we rely on the Quasar [positioning classes](http://quasar-framework.org/components/positioning.html) to make the message input be fixed at the bottom of the page. 786 | 787 | Retrieving messages/users on mount and in real-time is a piece of cake in **src/components/Chat.vue**: 788 | ```javascript 789 | ... 790 | mounted () { 791 | const messages = api.service('messages') 792 | const users = api.service('users') 793 | 794 | // Get all users and messages 795 | messages.find({ 796 | query: { 797 | $sort: { createdAt: -1 }, 798 | $limit: 25 799 | } 800 | }) 801 | .then((response) => { 802 | // We want the latest messages but in the reversed order 803 | this.$data.messages = response.data.reverse() 804 | }) 805 | users.find() 806 | .then((response) => { 807 | this.$data.users = response.data 808 | }) 809 | 810 | // Add new messages to the message list 811 | messages.on('created', message => { 812 | console.log('message received') 813 | this.$data.messages.unshift(message) 814 | }) 815 | // Add new users to the user list 816 | users.on('created', user => { 817 | console.log('user received') 818 | this.$data.users = this.$data.users.concat(user) 819 | }) 820 | } 821 | ... 822 | ``` 823 | 824 | ## Conclusion 825 | 826 | I hope that, like me, you measure the power of Quasar and Feathers to create beautiful real-time apps in seconds ! 827 | -------------------------------------------------------------------------------- /quasar-feathers/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ "env", {"modules": false} ], 4 | "stage-2" 5 | ], 6 | "plugins": ["transform-runtime"], 7 | "comments": false 8 | } 9 | -------------------------------------------------------------------------------- /quasar-feathers/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /quasar-feathers/.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | -------------------------------------------------------------------------------- /quasar-feathers/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | parser: 'babel-eslint', 5 | sourceType: 'module' 6 | }, 7 | env: { 8 | browser: true 9 | }, 10 | extends: [ 11 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 12 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 13 | 'plugin:vue/essential', 14 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 15 | 'standard' 16 | ], 17 | // required to lint *.vue files 18 | plugins: [ 19 | 'vue' 20 | ], 21 | globals: { 22 | 'ga': true, // Google Analytics 23 | 'cordova': true, 24 | '__statics': true 25 | }, 26 | // add your custom rules here 27 | 'rules': { 28 | // allow async-await 29 | 'generator-star-spacing': 'off', 30 | 31 | // allow paren-less arrow functions 32 | 'arrow-parens': 0, 33 | 'one-var': 0, 34 | 35 | 'import/first': 0, 36 | 'import/named': 2, 37 | 'import/namespace': 2, 38 | 'import/default': 2, 39 | 'import/export': 2, 40 | 'import/extensions': 0, 41 | 'import/no-unresolved': 0, 42 | 'import/no-extraneous-dependencies': 0, 43 | 44 | // allow debugger during development 45 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /quasar-feathers/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .thumbs.db 3 | node_modules/ 4 | dist/ 5 | npm-debug.log* 6 | cordova/platforms 7 | cordova/plugins 8 | .quasar 9 | -------------------------------------------------------------------------------- /quasar-feathers/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | plugins: [ 5 | // to edit target browsers: use "browserslist" field in package.json 6 | require('autoprefixer') 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /quasar-feathers/.stylintrc: -------------------------------------------------------------------------------- 1 | { 2 | "blocks": "never", 3 | "brackets": "never", 4 | "colons": "never", 5 | "colors": "always", 6 | "commaSpace": "always", 7 | "commentSpace": "always", 8 | "cssLiteral": "never", 9 | "depthLimit": false, 10 | "duplicates": true, 11 | "efficient": "always", 12 | "extendPref": false, 13 | "globalDupe": true, 14 | "indentPref": 2, 15 | "leadingZero": "never", 16 | "maxErrors": false, 17 | "maxWarnings": false, 18 | "mixed": false, 19 | "namingConvention": false, 20 | "namingConventionStrict": false, 21 | "none": "never", 22 | "noImportant": false, 23 | "parenSpace": "never", 24 | "placeholder": false, 25 | "prefixVarsWithDollar": "always", 26 | "quotePref": "single", 27 | "semicolons": "never", 28 | "sortOrder": false, 29 | "stackedProperties": "never", 30 | "trailingWhitespace": "never", 31 | "universal": "never", 32 | "valid": true, 33 | "zeroUnits": "never", 34 | "zIndexNormalize": false 35 | } 36 | -------------------------------------------------------------------------------- /quasar-feathers/README.md: -------------------------------------------------------------------------------- 1 | # Quasar App 2 | 3 | > A Quasar project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | $ npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | $ quasar dev 13 | 14 | # build for production with minification 15 | $ quasar build 16 | 17 | # lint code 18 | $ quasar lint 19 | ``` 20 | -------------------------------------------------------------------------------- /quasar-feathers/api/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2017-node7"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /quasar-feathers/api/.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | dist/*.js 4 | -------------------------------------------------------------------------------- /quasar-feathers/api/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | sourceType: 'module' 5 | }, 6 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 7 | extends: 'standard', 8 | // add your custom rules here 9 | 'rules': { 10 | // allow paren-less arrow functions 11 | 'arrow-parens': 0, 12 | 'one-var': 0, 13 | // allow debugger during development 14 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 15 | 'brace-style': [2, 'stroustrup', { 'allowSingleLine': true }] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /quasar-feathers/api/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | npm-debug.log.* 6 | -------------------------------------------------------------------------------- /quasar-feathers/api/build/script.clean.js: -------------------------------------------------------------------------------- 1 | var 2 | shell = require('shelljs'), 3 | path = require('path') 4 | 5 | shell.rm('-rf', path.resolve(__dirname, '../dist')) 6 | console.log(' Cleaned Express build artifacts.\n') 7 | -------------------------------------------------------------------------------- /quasar-feathers/api/config/default.js: -------------------------------------------------------------------------------- 1 | var clientConfig = require('../../config') 2 | 3 | module.exports = { 4 | client: clientConfig, 5 | 6 | // Proxy your API if using any. 7 | // Also see /build/script.dev.js and search for "proxy api requests" 8 | // https://github.com/chimurai/http-proxy-middleware 9 | proxyTable: {}, 10 | 11 | port: process.env.PORT || 8081, 12 | 13 | apiPath: '/api', 14 | 15 | host: 'localhost', 16 | paginate: { 17 | default: 10, 18 | max: 50 19 | }, 20 | authentication: { 21 | secret: 'b5KqXTye4fVxhGFpwMVZRO3R56wS5LNoJHifwgGOFkB5GfMWvIdrWyQxEJXswhAC', 22 | strategies: [ 23 | 'jwt', 24 | 'local' 25 | ], 26 | path: '/authentication', 27 | service: 'users' 28 | }, 29 | nedb: '../data' 30 | } 31 | 32 | /* 33 | * proxyTable example: 34 | * 35 | proxyTable: { 36 | // proxy all requests starting with /api 37 | '/api': { 38 | target: 'https://some.address.com/api', 39 | changeOrigin: true, 40 | pathRewrite: { 41 | '^/api': '' 42 | } 43 | } 44 | } 45 | */ 46 | -------------------------------------------------------------------------------- /quasar-feathers/api/data/messages.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claustres/quasar-feathers-tutorial/f39501b5468520edcab33af7109c3ef62a370e91/quasar-feathers/api/data/messages.db -------------------------------------------------------------------------------- /quasar-feathers/api/data/users.db: -------------------------------------------------------------------------------- 1 | {"$$indexCreated":{"fieldName":"email","unique":true,"sparse":false}} 2 | -------------------------------------------------------------------------------- /quasar-feathers/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quasar-feathers-app", 3 | "version": "0.0.1", 4 | "author": "Your name", 5 | "description": "Quasar App wrapped with Feathers", 6 | "main": "main.js", 7 | "scripts": { 8 | "dev": "nodemon --watch src --exec npm run debug", 9 | "predebug": "npm run build", 10 | "debug": "cross-env NODE_ENV=development node --inspect dist/main.js", 11 | "clean": "node build/script.clean.js", 12 | "build": "babel src -d dist -s", 13 | "prod": "cross-env NODE_ENV=production node dist/main.js", 14 | "lint": "eslint --ext .js src" 15 | }, 16 | "devDependencies": { 17 | "babel-cli": "^6.24.0", 18 | "babel-plugin-transform-runtime": "^6.0.0", 19 | "babel-preset-es2017-node7": "^0.5.2", 20 | "colors": "^1.1.2", 21 | "cross-env": "^3.1.3", 22 | "eslint": "^3.16.0", 23 | "nodemon": "^1.11.0", 24 | "shelljs": "^0.7.0" 25 | }, 26 | "dependencies": { 27 | "@feathersjs/authentication": "^2.1.1", 28 | "@feathersjs/authentication-jwt": "^2.0.0", 29 | "@feathersjs/authentication-local": "^1.1.0", 30 | "@feathersjs/configuration": "^1.0.2", 31 | "@feathersjs/errors": "^3.2.2", 32 | "@feathersjs/express": "^1.1.2", 33 | "@feathersjs/feathers": "^3.1.0", 34 | "@feathersjs/socketio": "^3.1.0", 35 | "body-parser": "^1.17.1", 36 | "compression": "^1.6.2", 37 | "cors": "^2.8.1", 38 | "feathers-hooks-common": "^3.0.0-pre.1", 39 | "feathers-nedb": "^2.6.1", 40 | "helmet": "^3.5.0", 41 | "http-proxy-middleware": "^0.17.4", 42 | "nedb": "^1.8.0", 43 | "winston": "^2.3.1" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /quasar-feathers/api/src/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const compress = require('compression'); 5 | const cors = require('cors'); 6 | const helmet = require('helmet'); 7 | const bodyParser = require('body-parser'); 8 | 9 | const feathers = require('@feathersjs/feathers'); 10 | const express = require('@feathersjs/express'); 11 | const configuration = require('@feathersjs/configuration'); 12 | const rest = require('@feathersjs/express/rest'); 13 | const socketio = require('@feathersjs/socketio'); 14 | 15 | const middleware = require('./middleware'); 16 | const services = require('./services'); 17 | const channels = require('./channels'); 18 | const appHooks = require('./main.hooks'); 19 | 20 | const authentication = require('./authentication'); 21 | 22 | const app = express(feathers()); 23 | 24 | // Load app configuration 25 | app.configure(configuration(path.join(__dirname, '..'))); 26 | // Enable CORS, security, compression, favicon and body parsing 27 | app.use(cors()); 28 | app.use(helmet()); 29 | app.use(compress()); 30 | app.use(bodyParser.json()); 31 | app.use(bodyParser.urlencoded({ extended: true })); 32 | app.configure(rest()); 33 | app.configure(socketio()); 34 | 35 | app.configure(authentication); 36 | 37 | // Set up our services (see `services/index.js`) 38 | app.configure(services); 39 | app.configure(channels); 40 | // Configure middleware (see `middleware/index.js`) - always has to be last 41 | app.configure(middleware); 42 | app.hooks(appHooks); 43 | 44 | module.exports = app; 45 | -------------------------------------------------------------------------------- /quasar-feathers/api/src/authentication.js: -------------------------------------------------------------------------------- 1 | const authentication = require('@feathersjs/authentication') 2 | const jwt = require('@feathersjs/authentication-jwt') 3 | const local = require('@feathersjs/authentication-local') 4 | 5 | module.exports = function() { 6 | const app = this 7 | const config = app.get('authentication') 8 | 9 | // Set up authentication with the secret 10 | app.configure(authentication(config)) 11 | app.configure(jwt()) 12 | app.configure(local()) 13 | // The `authentication` service is used to create a JWT. 14 | // The before `create` hook registers strategies that can be used 15 | // to create a new valid JWT (e.g. local or oauth2) 16 | app.service('authentication').hooks({ 17 | before: { 18 | create: [ 19 | authentication.hooks.authenticate(config.strategies) 20 | ], 21 | remove: [ 22 | authentication.hooks.authenticate('jwt') 23 | ] 24 | } 25 | }) 26 | }; 27 | -------------------------------------------------------------------------------- /quasar-feathers/api/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 | app.publish((data, hook) => { // eslint-disable-line no-unused-vars 40 | // Here you can add event publishers to channels set up in `channels.js` 41 | // To publish only for a specific event use `app.publish(eventname, () => {})` 42 | 43 | // e.g. to publish all service events to all authenticated users use 44 | return app.channel('authenticated'); 45 | }); 46 | 47 | // Here you can also add service specific event publishers 48 | // e..g the publish the `users` service `created` event to the `admins` channel 49 | // app.service('users').publish('created', () => app.channel('admins')); 50 | 51 | // With the userid and email organization from above you can easily select involved users 52 | // app.service('messages').publish(() => { 53 | // return [ 54 | // app.channel(`userIds/${data.createdBy}`), 55 | // app.channel(`emails/${data.recipientEmail}`) 56 | // ]; 57 | // }); 58 | }; 59 | -------------------------------------------------------------------------------- /quasar-feathers/api/src/hooks/gravatar.js: -------------------------------------------------------------------------------- 1 | // Use this hook to manipulate incoming or outgoing data. 2 | // For more information on hooks see: http://docs.feathersjs.com/api/hooks.html 3 | 4 | // We need this to create the MD5 hash 5 | const crypto = require('crypto') 6 | 7 | // The Gravatar image service 8 | const gravatarUrl = 'https://s.gravatar.com/avatar' 9 | // The size query. Our chat needs 60px images 10 | const query = 's=200' 11 | 12 | module.exports = function() { 13 | return function(hook) { 14 | // The user email 15 | const { email } = hook.data 16 | // Gravatar uses MD5 hashes from an email address to get the image 17 | const hash = crypto.createHash('md5').update(email).digest('hex') 18 | 19 | hook.data.avatar = `${gravatarUrl}/${hash}?${query}` 20 | 21 | // Hooks can either return nothing or a promise 22 | // that resolves with the `hook` object for asynchronous operations 23 | return Promise.resolve(hook); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /quasar-feathers/api/src/hooks/logger.js: -------------------------------------------------------------------------------- 1 | // A hook that logs service method before, after and error 2 | const logger = require('winston') 3 | 4 | module.exports = function() { 5 | return function(hook) { 6 | let message = `${hook.type}: ${hook.path} - Method: ${hook.method}` 7 | 8 | if (hook.type === 'error') { 9 | message += `: ${hook.error.message}` 10 | } 11 | 12 | logger.info(message) 13 | logger.debug('hook.data', hook.data) 14 | logger.debug('hook.params', hook.params) 15 | 16 | if(hook.result) { 17 | logger.debug('hook.result', hook.result) 18 | } 19 | 20 | if(hook.error) { 21 | logger.error(hook.error) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /quasar-feathers/api/src/hooks/process-message.js: -------------------------------------------------------------------------------- 1 | // Use this hook to manipulate incoming or outgoing data. 2 | // For more information on hooks see: http://docs.feathersjs.com/api/hooks.html 3 | 4 | module.exports = function() { 5 | return function(hook) { 6 | // The authenticated user 7 | const user = hook.params.user 8 | // The actual message text 9 | const text = hook.data.text 10 | // Messages can't be longer than 400 characters 11 | .substring(0, 400) 12 | // Do some basic HTML escaping 13 | .replace(/&/g,'&').replace(//g,'>') 14 | 15 | // Override the original data 16 | hook.data = { 17 | text, 18 | // Set the user id 19 | userId: user._id, 20 | // Add the current time via `getTime` 21 | createdAt: new Date().getTime() 22 | } 23 | 24 | // Hooks can either return nothing or a promise 25 | // that resolves with the `hook` object for asynchronous operations 26 | return Promise.resolve(hook) 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /quasar-feathers/api/src/main.hooks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Application hooks that run for every service 4 | const logger = require('./hooks/logger') 5 | 6 | module.exports = { 7 | before: { 8 | all: [], 9 | find: [], 10 | get: [], 11 | create: [], 12 | update: [], 13 | patch: [], 14 | remove: [] 15 | }, 16 | 17 | after: { 18 | all: [ logger() ], 19 | find: [], 20 | get: [], 21 | create: [], 22 | update: [], 23 | patch: [], 24 | remove: [] 25 | }, 26 | 27 | error: { 28 | all: [ logger() ], 29 | find: [], 30 | get: [], 31 | create: [], 32 | update: [], 33 | patch: [], 34 | remove: [] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /quasar-feathers/api/src/main.js: -------------------------------------------------------------------------------- 1 | const logger = require('winston') 2 | const Server = require('./server').Server 3 | 4 | let server = new Server() 5 | 6 | process.on('unhandledRejection', (reason, p) => 7 | logger.error('Unhandled Rejection at: Promise ', p, reason) 8 | ); 9 | 10 | server.run().then( () => { 11 | logger.info(' Server listen at ' + server.app.get('port').toString()) 12 | }) 13 | -------------------------------------------------------------------------------- /quasar-feathers/api/src/middleware/index.js: -------------------------------------------------------------------------------- 1 | const handler = require('@feathersjs/express/errors') 2 | const notFound = require('feathers-errors/not-found') 3 | 4 | module.exports = function() { 5 | // Add your custom middleware here. Remember, that 6 | // in Express the order matters, `notFound` and 7 | // the error handler have to go last. 8 | const app = this 9 | 10 | app.use(notFound()) 11 | app.use(handler()) 12 | } 13 | -------------------------------------------------------------------------------- /quasar-feathers/api/src/models/messages.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, 'messages.db'), 8 | autoload: true 9 | }) 10 | 11 | return Model 12 | } 13 | -------------------------------------------------------------------------------- /quasar-feathers/api/src/models/users.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, 'users.db'), 8 | autoload: true 9 | }) 10 | 11 | Model.ensureIndex({ fieldName: 'email', unique: true }) 12 | 13 | return Model 14 | } 15 | -------------------------------------------------------------------------------- /quasar-feathers/api/src/server.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const compress = require('compression') 3 | const cors = require('cors') 4 | const helmet = require('helmet') 5 | const bodyParser = require('body-parser') 6 | const proxyMiddleware = require('http-proxy-middleware') 7 | 8 | const feathers = require('@feathersjs/feathers') 9 | const express = require('@feathersjs/express'); 10 | const configuration = require('@feathersjs/configuration') 11 | const rest = require('@feathersjs/express/rest') 12 | const socketio = require('@feathersjs/socketio') 13 | 14 | const middleware = require('./middleware') 15 | const services = require('./services') 16 | const channels = require('./channels'); 17 | const appHooks = require('./main.hooks') 18 | 19 | const authentication = require('./authentication') 20 | 21 | export class Server { 22 | constructor() { 23 | this.app = express(feathers()) 24 | // Load app configuration 25 | this.app.configure(configuration(path.join(__dirname, '..'))) 26 | 27 | // Serve pure static assets 28 | if (process.env.NODE_ENV === 'production') { 29 | this.app.use(this.app.get('client').build.publicPath, express.static('../dist')) 30 | } 31 | else { 32 | const staticsPath = path.posix.join(this.app.get('client').dev.publicPath, 'statics/') 33 | this.app.use(staticsPath, express.static('../client/statics')) 34 | } 35 | 36 | // Define HTTP proxies to your custom API backend. See /config/index.js -> proxyTable 37 | // https://github.com/chimurai/http-proxy-middleware 38 | Object.keys(this.app.get('proxyTable')).forEach( (context) => { 39 | let options = this.config.this.app.get('proxyTable')[context] 40 | if (typeof options === 'string') { 41 | options = { target: options } 42 | } 43 | this.app.use(proxyMiddleware(context, options)) 44 | }) 45 | 46 | // Enable CORS, security, compression, favicon and body parsing 47 | this.app.use(cors()) 48 | this.app.use(helmet()) 49 | this.app.use(compress()) 50 | this.app.use(bodyParser.json()) 51 | this.app.use(bodyParser.urlencoded({ extended: true })) 52 | // Set up Plugins and providers 53 | this.app.configure(rest()) 54 | this.app.configure(socketio()) 55 | 56 | this.app.configure(authentication) 57 | 58 | // Set up our services (see `services/index.js`) 59 | this.app.configure(services) 60 | this.app.configure(channels); 61 | // Configure middleware (see `middleware/index.js`) - always has to be last 62 | this.app.configure(middleware) 63 | this.app.hooks(appHooks) 64 | } 65 | 66 | run () { 67 | const port = this.app.get('port'); 68 | 69 | let promise = new Promise((resolve, reject) => { 70 | this.app.listen(port, (err) => { 71 | if (err) { 72 | reject(err) 73 | } 74 | else { 75 | resolve() 76 | } 77 | }) 78 | }) 79 | 80 | return promise 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /quasar-feathers/api/src/services/index.js: -------------------------------------------------------------------------------- 1 | const users = require('./users/users.service.js') 2 | 3 | const messages = require('./messages/messages.service.js') 4 | 5 | module.exports = function() { 6 | const app = this; // eslint-disable-line no-unused-vars 7 | 8 | app.configure(users) 9 | app.configure(messages) 10 | }; 11 | -------------------------------------------------------------------------------- /quasar-feathers/api/src/services/messages/messages.hooks.js: -------------------------------------------------------------------------------- 1 | const { authenticate } = require('@feathersjs/authentication').hooks 2 | const { populate } = require('feathers-hooks-common') 3 | const processMessage = require('../../hooks/process-message') 4 | 5 | module.exports = { 6 | before: { 7 | all: [ authenticate('jwt') ], 8 | find: [], 9 | get: [], 10 | create: [ processMessage() ], 11 | update: [ processMessage() ], 12 | patch: [ processMessage() ], 13 | remove: [] 14 | }, 15 | 16 | after: { 17 | all: [ 18 | populate({ 19 | schema: { 20 | include: [{ 21 | service: 'users', 22 | nameAs: 'user', 23 | parentField: 'userId', 24 | childField: '_id' 25 | }] 26 | } 27 | }) 28 | ], 29 | find: [], 30 | get: [], 31 | create: [], 32 | update: [], 33 | patch: [], 34 | remove: [] 35 | }, 36 | 37 | error: { 38 | all: [], 39 | find: [], 40 | get: [], 41 | create: [], 42 | update: [], 43 | patch: [], 44 | remove: [] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /quasar-feathers/api/src/services/messages/messages.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `messages` service on path `/messages` 2 | const createService = require('feathers-nedb') 3 | const createModel = require('../../models/messages.model') 4 | const hooks = require('./messages.hooks') 5 | 6 | module.exports = function() { 7 | const app = this 8 | const Model = createModel(app) 9 | const paginate = app.get('paginate') 10 | 11 | const options = { 12 | name: 'messages', 13 | Model, 14 | paginate 15 | } 16 | 17 | // Initialize our service with any options it requires 18 | app.use('/messages', createService(options)) 19 | 20 | // Get our initialized service so that we can register hooks and filters 21 | const service = app.service('messages') 22 | 23 | service.hooks(hooks) 24 | } 25 | -------------------------------------------------------------------------------- /quasar-feathers/api/src/services/users/users.hooks.js: -------------------------------------------------------------------------------- 1 | const { authenticate } = require('@feathersjs/authentication').hooks 2 | const { hashPassword, protect } = require('@feathersjs/authentication-local').hooks; 3 | const commonHooks = require('feathers-hooks-common') 4 | const gravatar = require('../../hooks/gravatar') 5 | 6 | module.exports = { 7 | before: { 8 | all: [], 9 | find: [ authenticate('jwt') ], 10 | get: [ authenticate('jwt') ], 11 | create: [hashPassword(), gravatar()], 12 | update: [ authenticate('jwt') ], 13 | patch: [ authenticate('jwt') ], 14 | remove: [ authenticate('jwt') ] 15 | }, 16 | 17 | after: { 18 | all: [ 19 | commonHooks.when( 20 | hook => hook.params.provider, 21 | protect('password') 22 | ) 23 | ], 24 | find: [], 25 | get: [], 26 | create: [], 27 | update: [], 28 | patch: [], 29 | remove: [] 30 | }, 31 | 32 | error: { 33 | all: [], 34 | find: [], 35 | get: [], 36 | create: [], 37 | update: [], 38 | patch: [], 39 | remove: [] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /quasar-feathers/api/src/services/users/users.service.js: -------------------------------------------------------------------------------- 1 | // Initializes the `users` service on path `/users` 2 | const createService = require('feathers-nedb') 3 | const createModel = require('../../models/users.model') 4 | const hooks = require('./users.hooks') 5 | 6 | module.exports = function() { 7 | const app = this; 8 | const Model = createModel(app); 9 | const paginate = app.get('paginate') 10 | 11 | const options = { 12 | name: 'users', 13 | Model, 14 | paginate 15 | } 16 | 17 | // Initialize our service with any options it requires 18 | app.use('/users', createService(options)) 19 | 20 | // Get our initialized service so that we can register hooks and filters 21 | const service = app.service('users') 22 | 23 | service.hooks(hooks) 24 | }; 25 | -------------------------------------------------------------------------------- /quasar-feathers/config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /quasar-feathers/config/index.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | 3 | module.exports = { 4 | // Webpack aliases 5 | aliases: { 6 | quasar: path.resolve(__dirname, '../node_modules/quasar-framework/'), 7 | src: path.resolve(__dirname, '../src'), 8 | assets: path.resolve(__dirname, '../src/assets'), 9 | '@': path.resolve(__dirname, '../src/components'), 10 | variables: path.resolve(__dirname, '../src/themes/quasar.variables.styl') 11 | }, 12 | 13 | // Progress Bar Webpack plugin format 14 | // https://github.com/clessg/progress-bar-webpack-plugin#options 15 | progressFormat: ' [:bar] ' + ':percent'.bold + ' (:msg)', 16 | 17 | // Default theme to build with ('ios' or 'mat') 18 | defaultTheme: 'mat', 19 | 20 | build: { 21 | env: require('./prod.env'), 22 | publicPath: '', 23 | productionSourceMap: false, 24 | 25 | // Remove unused CSS 26 | // Disable it if it has side-effects for your specific app 27 | purifyCSS: true 28 | }, 29 | dev: { 30 | env: require('./dev.env'), 31 | cssSourceMap: true, 32 | // auto open browser or not 33 | openBrowser: true, 34 | publicPath: '/', 35 | port: 8080, 36 | 37 | // If for example you are using Quasar Play 38 | // to generate a QR code then on each dev (re)compilation 39 | // you need to avoid clearing out the console, so set this 40 | // to "false", otherwise you can set it to "true" to always 41 | // have only the messages regarding your last (re)compilation. 42 | clearConsoleOnRebuild: false, 43 | 44 | // Proxy your API if using any. 45 | // Also see /build/script.dev.js and search for "proxy api requests" 46 | // https://github.com/chimurai/http-proxy-middleware 47 | proxyTable: { 48 | '/api': { 49 | target: 'http://localhost:8081', 50 | changeOrigin: true 51 | } 52 | } 53 | } 54 | } 55 | 56 | /* 57 | * proxyTable example: 58 | * 59 | proxyTable: { 60 | // proxy all requests starting with /api 61 | '/api': { 62 | target: 'https://some.address.com/api', 63 | changeOrigin: true, 64 | pathRewrite: { 65 | '^/api': '' 66 | } 67 | } 68 | } 69 | */ 70 | -------------------------------------------------------------------------------- /quasar-feathers/config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /quasar-feathers/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | app: 5 | build: . 6 | image: your-docker-org/your-app-name 7 | command: sh 8 | volumes: 9 | - .:/opt/app 10 | - /opt/app/node_modules 11 | ports: 12 | - 8080:8080 13 | tty: true 14 | -------------------------------------------------------------------------------- /quasar-feathers/dockerfile: -------------------------------------------------------------------------------- 1 | #change this to your own repo, should you have uploaded your image! 2 | FROM quasarframework/client-dev:latest 3 | 4 | MAINTAINER Your Name 5 | 6 | WORKDIR /opt/app 7 | 8 | COPY package.json /opt/app/ 9 | RUN npm install 10 | 11 | COPY . /opt/app 12 | 13 | EXPOSE 8080 14 | 15 | CMD /bin/sh 16 | -------------------------------------------------------------------------------- /quasar-feathers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quasar-feathers", 3 | "version": "1.0.0", 4 | "description": "A Quasar Framework app", 5 | "productName": "Quasar App", 6 | "cordovaId": "org.cordova.quasar.app", 7 | "author": "Your Name ", 8 | "private": true, 9 | "scripts": { 10 | "lint": "eslint --ext .js,.vue src", 11 | "test": "echo \"No test specified\" && exit 0" 12 | }, 13 | "dependencies": { 14 | "@feathersjs/authentication-client": "^1.0.2", 15 | "@feathersjs/feathers": "^3.1.0", 16 | "@feathersjs/socketio-client": "^1.0.2", 17 | "engine.io-client": "^3.1.4", 18 | "moment": "^2.18.1", 19 | "isarray": "^2.0.2", 20 | "socket.io-client": "^1.7.3", 21 | "socket.io-parser": "^3.1.2", 22 | "vue": "^2.5.11", 23 | "vue-router": "^3.0.1" 24 | }, 25 | "devDependencies": { 26 | "babel-eslint": "^8.2.1", 27 | "eslint": "^4.18.2", 28 | "eslint-config-standard": "^11.0.0", 29 | "eslint-friendly-formatter": "^3.0.0", 30 | "eslint-loader": "^2.0.0", 31 | "eslint-plugin-import": "^2.9.0", 32 | "eslint-plugin-node": "^6.0.1", 33 | "eslint-plugin-promise": "^3.7.0", 34 | "eslint-plugin-standard": "^3.0.1", 35 | "eslint-plugin-vue": "^4.3.0", 36 | "quasar-cli": "^0.15.14" 37 | }, 38 | "engines": { 39 | "node": ">= 8.9.0", 40 | "npm": ">= 5.6.0" 41 | }, 42 | "browserslist": [ 43 | "> 1%", 44 | "last 2 versions", 45 | "not ie <= 10" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /quasar-feathers/quasar.conf.js: -------------------------------------------------------------------------------- 1 | // Configuration for your app 2 | 3 | module.exports = function (ctx) { 4 | return { 5 | // app plugins (/src/plugins) 6 | plugins: [ 7 | ], 8 | css: [ 9 | 'app.styl' 10 | ], 11 | extras: [ 12 | ctx.theme.mat ? 'roboto-font' : null, 13 | 'material-icons' 14 | // 'ionicons', 15 | // 'mdi', 16 | // 'fontawesome' 17 | ], 18 | supportIE: false, 19 | vendor: { 20 | add: [], 21 | remove: [] 22 | }, 23 | build: { 24 | scopeHoisting: true, 25 | vueRouterMode: 'hash', //'history', //'hash', 26 | // gzip: true, 27 | // analyze: true, 28 | // extractCSS: false, 29 | // useNotifier: false, 30 | extendWebpack (cfg) { 31 | cfg.module.rules.push({ 32 | enforce: 'pre', 33 | test: /\.(js|vue)$/, 34 | loader: 'eslint-loader', 35 | exclude: /(node_modules|quasar)/ 36 | }) 37 | } 38 | }, 39 | devServer: { 40 | // https: true, 41 | // port: 8080, 42 | open: true // opens browser window automatically 43 | }, 44 | // framework: 'all' --- includes everything; for dev only! 45 | framework: { 46 | components: [ 47 | 'QLayout', 48 | 'QLayoutHeader', 49 | 'QLayoutDrawer', 50 | 'QPageContainer', 51 | 'QPage', 52 | 'QToolbar', 53 | 'QToolbarTitle', 54 | 'QBtn', 55 | 'QIcon', 56 | 'QList', 57 | 'QListHeader', 58 | 'QItem', 59 | 'QItemTile', 60 | 'QItemMain', 61 | 'QItemSide', 62 | 'QTooltip', 63 | 'QDialog', 64 | 'QInput', 65 | 'QChatMessage', 66 | 'QCollapsible', 67 | 'QPageSticky' 68 | ], 69 | directives: [ 70 | 'Ripple' 71 | ], 72 | // Quasar plugins 73 | plugins: [ 74 | 'Notify' 75 | ] 76 | }, 77 | // animations: 'all' --- includes all animations 78 | animations: [ 79 | ], 80 | pwa: { 81 | cacheExt: 'js,html,css,ttf,eot,otf,woff,woff2,json,svg,gif,jpg,jpeg,png,wav,ogg,webm,flac,aac,mp4,mp3', 82 | manifest: { 83 | // name: 'Quasar App', 84 | // short_name: 'Quasar-PWA', 85 | // description: 'Best PWA App in town!', 86 | display: 'standalone', 87 | orientation: 'portrait', 88 | background_color: '#ffffff', 89 | theme_color: '#027be3', 90 | icons: [ 91 | { 92 | 'src': 'statics/icons/icon-128x128.png', 93 | 'sizes': '128x128', 94 | 'type': 'image/png' 95 | }, 96 | { 97 | 'src': 'statics/icons/icon-192x192.png', 98 | 'sizes': '192x192', 99 | 'type': 'image/png' 100 | }, 101 | { 102 | 'src': 'statics/icons/icon-256x256.png', 103 | 'sizes': '256x256', 104 | 'type': 'image/png' 105 | }, 106 | { 107 | 'src': 'statics/icons/icon-384x384.png', 108 | 'sizes': '384x384', 109 | 'type': 'image/png' 110 | }, 111 | { 112 | 'src': 'statics/icons/icon-512x512.png', 113 | 'sizes': '512x512', 114 | 'type': 'image/png' 115 | } 116 | ] 117 | } 118 | }, 119 | cordova: { 120 | // id: 'org.cordova.quasar.app' 121 | }, 122 | electron: { 123 | extendWebpack (cfg) { 124 | // do something with cfg 125 | }, 126 | packager: { 127 | // OS X / Mac App Store 128 | // appBundleId: '', 129 | // appCategoryType: '', 130 | // osxSign: '', 131 | // protocol: 'myapp://path', 132 | 133 | // Window only 134 | // win32metadata: { ... } 135 | } 136 | }, 137 | 138 | // leave this here for Quasar CLI 139 | starterKit: '1.0.2' 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /quasar-feathers/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 37 | -------------------------------------------------------------------------------- /quasar-feathers/src/api.js: -------------------------------------------------------------------------------- 1 | import feathers from '@feathersjs/feathers' 2 | import socketio from '@feathersjs/socketio-client' 3 | import auth from '@feathersjs/authentication-client' 4 | import io from 'socket.io-client' 5 | 6 | const socket = io('http://localhost:8081', {transports: ['websocket']}) 7 | 8 | const api = feathers() 9 | .configure(socketio(socket)) 10 | .configure(auth({ storage: window.localStorage })) 11 | 12 | api.service('/users') 13 | api.service('/messages') 14 | 15 | export default api 16 | -------------------------------------------------------------------------------- /quasar-feathers/src/assets/feathers-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claustres/quasar-feathers-tutorial/f39501b5468520edcab33af7109c3ef62a370e91/quasar-feathers/src/assets/feathers-logo.png -------------------------------------------------------------------------------- /quasar-feathers/src/assets/quasar-logo-full.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 66 | 69 | 75 | 79 | 83 | 87 | 91 | 95 | 99 | 103 | 104 | 105 | 106 | 107 | 113 | 118 | 126 | 133 | 142 | 151 | 160 | 169 | 178 | 187 | 188 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /quasar-feathers/src/assets/quasar-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claustres/quasar-feathers-tutorial/f39501b5468520edcab33af7109c3ef62a370e91/quasar-feathers/src/assets/quasar-logo.png -------------------------------------------------------------------------------- /quasar-feathers/src/assets/sad.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /quasar-feathers/src/auth.js: -------------------------------------------------------------------------------- 1 | import api from 'src/api' 2 | //import { Promise } from 'es6-promise' 3 | 4 | const auth = { 5 | 6 | user: null, 7 | 8 | getUser() { 9 | return this.user 10 | }, 11 | 12 | fetchUser (accessToken) { 13 | 14 | return api.passport.verifyJWT(accessToken) 15 | .then(payload => { 16 | return api.service('users').get(payload.userId) 17 | }) 18 | .then(user => { 19 | return Promise.resolve(user) 20 | }) 21 | }, 22 | 23 | authenticate () { 24 | console.log('auth') 25 | 26 | return api.authenticate() 27 | .then((response) => { 28 | console.log('auth successful') 29 | 30 | return this.fetchUser(response.accessToken) 31 | }) 32 | .then(user => { 33 | console.log('got user') 34 | 35 | this.user = user 36 | 37 | return Promise.resolve(user) 38 | }) 39 | .catch((err) => { 40 | console.log('auth failed', err) 41 | 42 | this.user = null 43 | 44 | return Promise.reject(err) 45 | }) 46 | }, 47 | 48 | authenticated () { 49 | return this.user != null 50 | }, 51 | 52 | signout () { 53 | console.log('signout') 54 | 55 | return api.logout() 56 | .then(() => { 57 | console.log('signout successful') 58 | 59 | this.user = null 60 | }) 61 | .catch((err) => { 62 | console.log('signout failed', err) 63 | 64 | return Promise.reject(err) 65 | }) 66 | }, 67 | 68 | onLogout (callback) { 69 | 70 | api.on('logout', () => { 71 | console.log('onLogout') 72 | 73 | this.user = null 74 | 75 | callback() 76 | }) 77 | }, 78 | 79 | onAuthenticated (callback) { 80 | 81 | api.on('authenticated', response => { 82 | console.log('onAuthenticate', response) 83 | 84 | this.fetchUser(response.accessToken) 85 | .then(user => { 86 | console.log('onAuthenticate got user') 87 | 88 | this.user = user 89 | 90 | callback(this.user) 91 | }) 92 | .catch((err) => { 93 | console.log('onAuthenticate get user failed', err) 94 | 95 | callback(this.user) 96 | }) 97 | }) 98 | }, 99 | 100 | register (email, password) { 101 | return api.service('users').create({ 102 | email: email, 103 | password: password 104 | }) 105 | }, 106 | 107 | login (email, password) { 108 | return api.authenticate({ 109 | strategy: 'local', 110 | email: email, 111 | password: password 112 | }) 113 | } 114 | 115 | } 116 | 117 | export default auth 118 | -------------------------------------------------------------------------------- /quasar-feathers/src/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claustres/quasar-feathers-tutorial/f39501b5468520edcab33af7109c3ef62a370e91/quasar-feathers/src/components/.gitkeep -------------------------------------------------------------------------------- /quasar-feathers/src/css/app.styl: -------------------------------------------------------------------------------- 1 | // app global css 2 | -------------------------------------------------------------------------------- /quasar-feathers/src/css/themes/common.variables.styl: -------------------------------------------------------------------------------- 1 | // App Shared Variables 2 | // -------------------------------------------------- 3 | // To customize the look and feel of this app, you can override 4 | // the Stylus variables found in Quasar's source Stylus files. Setting 5 | // variables before Quasar's Stylus will use these variables rather than 6 | // Quasar's default Stylus variable values. Stylus variables specific 7 | // to the themes belong in either the variables.ios.styl or variables.mat.styl files. 8 | 9 | // Check documentation for full list of Quasar variables 10 | 11 | 12 | // App Shared Color Variables 13 | // -------------------------------------------------- 14 | // It's highly recommended to change the default colors 15 | // to match your app's branding. 16 | 17 | $primary = #027be3 18 | $secondary = #26A69A 19 | $tertiary = #555 20 | 21 | $neutral = #E0E1E2 22 | $positive = #21BA45 23 | $negative = #DB2828 24 | $info = #31CCEC 25 | $warning = #F2C037 26 | -------------------------------------------------------------------------------- /quasar-feathers/src/css/themes/variables.ios.styl: -------------------------------------------------------------------------------- 1 | // App Shared Variables 2 | // -------------------------------------------------- 3 | // Shared Stylus variables go in the common.variables.styl file 4 | @import 'common.variables' 5 | 6 | // iOS only Quasar variables overwrites 7 | // ----------------------------------------- 8 | -------------------------------------------------------------------------------- /quasar-feathers/src/css/themes/variables.mat.styl: -------------------------------------------------------------------------------- 1 | // App Shared Variables 2 | // -------------------------------------------------- 3 | // Shared Stylus variables go in the common.variables.styl file 4 | @import 'common.variables' 5 | 6 | // Material only Quasar variables overwrites 7 | // ----------------------------------------- 8 | -------------------------------------------------------------------------------- /quasar-feathers/src/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%= htmlWebpackPlugin.options.productName %> 10 | 11 | 12 | 13 | 14 | 15 | <% if (htmlWebpackPlugin.options.ctx.mode.pwa) { %> 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | <% } %> 28 | 29 | <%= htmlWebpackPlugin.options.headScripts %> 30 | 31 | 35 | <% if (!['cordova', 'electron'].includes(htmlWebpackPlugin.options.ctx.modeName) && htmlWebpackPlugin.options.ctx.prod) { 36 | for (var chunk of webpack.chunks) { 37 | for (var file of chunk.files) { 38 | if (file.match(/\.(js|css)$/)) { %> 39 | 40 | <% }}}} %> 41 | 42 | 43 | <% if (!htmlWebpackPlugin.options.ctx.mode.electron) { %> 44 | 47 | <% } %> 48 | 49 | 50 |
51 | 52 | 53 | <%= htmlWebpackPlugin.options.bodyScripts %> 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /quasar-feathers/src/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 82 | 83 | 146 | 147 | 149 | -------------------------------------------------------------------------------- /quasar-feathers/src/pages/404.vue: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /quasar-feathers/src/pages/Chat.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 103 | 104 | 110 | -------------------------------------------------------------------------------- /quasar-feathers/src/pages/Home.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 49 | 50 | 53 | -------------------------------------------------------------------------------- /quasar-feathers/src/pages/SignIn.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 86 | 87 | 90 | -------------------------------------------------------------------------------- /quasar-feathers/src/plugins/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claustres/quasar-feathers-tutorial/f39501b5468520edcab33af7109c3ef62a370e91/quasar-feathers/src/plugins/.gitkeep -------------------------------------------------------------------------------- /quasar-feathers/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | 4 | import routes from './routes' 5 | import auth from 'src/auth' 6 | 7 | Vue.use(VueRouter) 8 | 9 | const router = new VueRouter({ 10 | /* 11 | * NOTE! Change Vue Router mode from quasar.conf.js -> build -> vueRouterMode 12 | * 13 | * If you decide to go with "history" mode, please also set "build.publicPath" 14 | * to something other than an empty string. 15 | * Example: '/' instead of '' 16 | */ 17 | 18 | // Leave as is and change from quasar.conf.js instead! 19 | mode: process.env.VUE_ROUTER_MODE, 20 | base: process.env.VUE_ROUTER_BASE, 21 | scrollBehavior: () => ({ y: 0 }), 22 | routes 23 | }) 24 | 25 | router.beforeEach((to, from, next) => { 26 | 27 | if (!to.meta.requiresAuth || auth.authenticated()) { 28 | next() 29 | } else { 30 | console.log('Not authenticated') 31 | 32 | next({ path: '/home' }) 33 | } 34 | }) 35 | 36 | export default router 37 | -------------------------------------------------------------------------------- /quasar-feathers/src/router/routes.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | path: '/', 4 | component: () => import('layouts/default'), 5 | children: [ 6 | { path: '/home', name: 'home', component: () => import('pages/Home') }, 7 | { path: '/signin', name: 'signin', component: () => import('pages/SignIn') }, 8 | { path: '/register', name: 'register', component: () => import('pages/SignIn') }, 9 | { path: '/chat', name: 'chat', component: () => import('pages/Chat'), meta: { requiresAuth: true } } 10 | ] 11 | }, 12 | 13 | { // Always leave this as last one 14 | path: '*', 15 | component: () => import('pages/404') 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /quasar-feathers/src/statics/icons/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claustres/quasar-feathers-tutorial/f39501b5468520edcab33af7109c3ef62a370e91/quasar-feathers/src/statics/icons/apple-icon-152x152.png -------------------------------------------------------------------------------- /quasar-feathers/src/statics/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claustres/quasar-feathers-tutorial/f39501b5468520edcab33af7109c3ef62a370e91/quasar-feathers/src/statics/icons/favicon-16x16.png -------------------------------------------------------------------------------- /quasar-feathers/src/statics/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claustres/quasar-feathers-tutorial/f39501b5468520edcab33af7109c3ef62a370e91/quasar-feathers/src/statics/icons/favicon-32x32.png -------------------------------------------------------------------------------- /quasar-feathers/src/statics/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claustres/quasar-feathers-tutorial/f39501b5468520edcab33af7109c3ef62a370e91/quasar-feathers/src/statics/icons/icon-128x128.png -------------------------------------------------------------------------------- /quasar-feathers/src/statics/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claustres/quasar-feathers-tutorial/f39501b5468520edcab33af7109c3ef62a370e91/quasar-feathers/src/statics/icons/icon-192x192.png -------------------------------------------------------------------------------- /quasar-feathers/src/statics/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claustres/quasar-feathers-tutorial/f39501b5468520edcab33af7109c3ef62a370e91/quasar-feathers/src/statics/icons/icon-256x256.png -------------------------------------------------------------------------------- /quasar-feathers/src/statics/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claustres/quasar-feathers-tutorial/f39501b5468520edcab33af7109c3ef62a370e91/quasar-feathers/src/statics/icons/icon-384x384.png -------------------------------------------------------------------------------- /quasar-feathers/src/statics/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claustres/quasar-feathers-tutorial/f39501b5468520edcab33af7109c3ef62a370e91/quasar-feathers/src/statics/icons/icon-512x512.png -------------------------------------------------------------------------------- /quasar-feathers/src/statics/icons/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claustres/quasar-feathers-tutorial/f39501b5468520edcab33af7109c3ef62a370e91/quasar-feathers/src/statics/icons/ms-icon-144x144.png -------------------------------------------------------------------------------- /quasar-feathers/src/statics/quasar-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claustres/quasar-feathers-tutorial/f39501b5468520edcab33af7109c3ef62a370e91/quasar-feathers/src/statics/quasar-logo.png --------------------------------------------------------------------------------