QPANC - Parte 12 - Quasar - Serviços
Tags: quasar, postgres, aspnetcore, docker
Published: 2020-04-19 23:38:06

> **QPANC** são as iniciais de **Q**uasar **P**ostgreSQL **A**SP **N**ET **C**ore.

  * [Source](https://github.com/TobyMosque/qpanc)
  * [Introdução](https://dev.to/quasar-pt-br/arquiteturando-uma-aplicacao-com-quasar-postgresql-asp-net-core-e-docker-introducao-o7o)
  * [Parte I - ASP.NET - Inicializando os Projetos](https://dev.to/quasar-pt-br/qpanc-parte-1-asp-net-inicializando-os-projetos-lkm)
  * [Parte 2 - PostgreSQL](https://dev.to/quasar-pt-br/qpanc-parte-2-postgresql-51pb)
  * [Parte 3 - ASP.NET - Registrando Serviços e Lendo Variáveis de Ambiente](https://dev.to/quasar-pt-br/qpanc-parte-3-asp-net-registrando-servicos-e-lendo-variaveis-de-ambiente-3do2)
  * [Parte 4 - ASP.NET - Entity Framework e ASP.NET Core Identity](https://dev.to/quasar-pt-br/qpanc-parte-4-asp-net-entity-framework-e-asp-net-core-identity-40f9)
  * [Parte 5 - ASP.NET - Documentação Interativa com Swagger](https://dev.to/quasar-pt-br/qpanc-parte-5-asp-net-documentacao-interativa-com-swagger-4n0o)
  * [Parte 6 - ASP.NET - Regionalização](https://dev.to/quasar-pt-br/qpanc-parte-6-asp-net-regionalizacao-13p5)
  * [Parte 7 - ASP.NET - Autenticação e Autorização](https://dev.to/quasar-pt-br/qpanc-parte-7-asp-net-autenticacao-e-autorizacao-bpd)
  * [Parte 8 - ASP.NET - CORS](https://dev.to/quasar-pt-br/qpanc-parte-8-asp-net-cors-52dn)
  * [Parte 9 - Quasar - Criação e Configuração do Projeto](https://dev.to/quasar-pt-br/qpanc-parte-9-quasar-criacao-e-configuracao-do-projeto-5hbi)
  * [Parte 10 - Quasar - Configurações e Customizações](https://dev.to/quasar-pt-br/qpanc-parte-10-quasar-configuracoes-e-customizacoes-2i86)
  * [Parte 11 - Quasar - Componentes - Diferença entre SPA e SSR](https://dev.to/quasar-pt-br/qpanc-parte-11-quasar-componentes-diferenca-entre-spa-e-ssr-1a3c)
  * [Parte 12 - Quasar - Serviços](https://dev.to/quasar-pt-br/qpanc-parte-12-quasar-servicos-4e61)
  * [Parte 13 - Quasar - Regionalização e Stores](https://dev.to/quasar-pt-br/qpanc-parte-13-quasar-regionalizacao-e-stores-1kep)
  * [Parte 14 - Quasar - Consumindo a API](https://dev.to/quasar-pt-br/qpanc-parte-14-quasar-consumindo-a-api-fn6)
  * [Parte 15 - Quasar - Login](https://dev.to/quasar-pt-br/qpanc-parte-15-quasar-login-jc6)
  * [Parte 16 - Quasar - Áreas Protegidas](https://dev.to/quasar-pt-br/qpanc-parte-16-quasar-areas-protegidas-3jkn)
  * [Parte 17 - Quasar - Registro](https://dev.to/quasar-pt-br/qpanc-parte-17-quasar-registro-3b8c)
  * [Parte 18 - Docker - Maquina Virtual Linux](https://dev.to/quasar-pt-br/qpanc-parte-18-docker-maquina-virtual-linux-g5c)
  * [Parte 19 - Docker - Registro e Build](https://dev.to/quasar-pt-br/qpanc-parte-19-docker-registro-e-build-31i4)
  * [Parte 20 - Docker - Traefik e Publicação](https://dev.to/quasar-pt-br/qpanc-parte-20-docker-traefik-e-publicacao-3g3d)
  * [Demo Online](https://qpanc.tobiasmesquita.dev)

##24 Injetar objetos nos componentes, stores e routes.

Para o correto funcionamento de uma aplicação `SSR`, alguns serviços precisam ser instanciados de maneira isolada, de forma, que um usuário não consiga acessar a uma instancia destinada a outro usuário, em resumo, todos os serviços devem está preferencialmente isoladas dentro do escopo do usuário.

O primeiro passo para conseguir este objetivo, é criar um utilitário que irá injetar estes serviços nos `components`, `store` e `routes`.

**QPANC.App/src/boot/inject.js**
```js
import Vue from 'vue'

const mixins = []
const inject = function (bootCb) {
  return async function (ctx) {
    const { app, router, store } = ctx
    let boot
    if (typeof bootCb === 'function') {
      const response = bootCb(ctx)
      boot = response.then ? await response : response
    } else {
      boot = bootCb
    }

    for (const name in boot) {
      const key = `$${name}`
      if (mixins.indexOf(name) === -1) {
        mixins.push(name)
        Vue.mixin({
          beforeCreate () {
            const options = this.$options
            if (options[name]) {
              this[key] = options[name]
            } else if (options.parent) {
              this[key] = options.parent[key]
            }
          }
        })
      }
      app[name] = boot[name]
      store[key] = boot[name]
      router[key] = boot[name]
    }
  }
}

export default inject
```

Agora, um exemplo de utilização.:

Digamos que tenhamos um serviço que faz o print da mensagem `Hello World` no console.:

```js
class DummieService {
  sayHello ({ name }) {
    console.log(`${name}: Hello Wolrd`)
  }
}
```

E agora queremos injetar uma instancia do serviço acima em todos os `componentes`, `stores` e `routes`, poderemos faze-lo em um arquivo de boot.


```js
import inject from './inject'
import DummieService from 'services/dummie'

export default inject(({ Vue }) => {
  const dummie = new DummieService()
  return {
    dummie: dummie
  }
})
```

feito isto, poderemos acessar `this.$dummie` nos `componentes` e `stores`, assim como `router.$dummie` nos navigation guards, segue alguns exemplos.:

**sample/page/dummie.js**
```js
export default {
  data () {
    const initialMsg = this.$dummie.sayHello({ name: 'me' })
    return {
      msg: initialMsg
    }
  },
  methods: {
    changeName ({ name }) {
      this.msg = this.$dummie.sayHello({ name })
    }
  }
}
```
**sample/store/dummie.js**
```js
export default {
  namespaed: true,
  state () {
    return {
      name: 'me',
      msg: ''
    }
  },
  mutations: {
    name (state, value) { state.name = value },
    msg (state, value) { state.msg = value }
  },
  actions: {
    setMessage ({ commit }, name) {
      const msg = this.$dummie.sayHello({ name })
      commit('msg', msg)
    }
  },
  getters: {
    getMessage (state) {
      return this.$dummie.sayHello({ name: state.name })
    }
  }
}
```
**sample/router/dummie.js**
```js
export default function (context) {
  return {
    path: '/dummie',
    beforeEnter (to, from, next) {
      const { $dummie } = context.router
      const msg = $dummie.sayHello({ name: to.params.name })
      if (msg.length > 50) {
        next('/hello-long-name')
      } else {
        next('/hello-short-name')
      }
    }
  }
}
```

##25 Customizando os componentes do Quasar

Para esta tarefa, estarei usando a extensão `@toby-mosque/utils`, porém não irei mostrar um código equivalente sem o uso da extensão, pois envolve um `transparent wrapper` bem intricado, onde um pequeno deslize, pode levar a um bug difícil de rastrear ou a um comportamento indesejado.

Para esta demostração, estaremos personalizando apenas o `QInput`, mas podemos customizar qual quer componente, inclusive aqueles que são instalados através de extensões.

O primeiro passo, é criar um boot, aqui chamaremos ele de `brand`

```
quasar new boot brand
```
**QPANC.App/quasar.config.js**
```js
module.exports = function (ctx) {
  return {
    boots: [
      'brand'
    ]
  }
}
```
**QPANC.App/src/boot/inject.js**
```js
import { factory } from '@toby.mosque/utils'
import inject from './inject'
import { QInput } from 'quasar'

// "async" is optional
export default inject(({ Vue }) => {
  const brand = {}
  brand.input = Vue.observable({
    /*
    style: {
      'font-size': '12px'
    },
    class: {
      'custom-input': true
    },
    */
    props: {
      outlined: true
    }
  })

  factory.reBrand('q-input', QInput, brand.input)
  return {
    brand
  }
})
```

O objeto `brand` será injetado nos `componentes` e `stores`, então poderemos acessa-lo futuramente.

A propriedade `input` é um `Vue.observable`, estão qual quer alteração nele, será refletido para os componentes que fazem uso dele. `input` é apenas um nome, poderia ser qual quer coisa no lugar.

O `factory.reBrand`, é o responsável por injetar o `brand.input` em todos os `q-input`. Ele fará uso apenas das propriedades `style`, `class` e `props`, onde estas propriedades serão injetadas no `style`, `class` e `props` do respectivo componente, sendo que nenhum deles é obrigatório.

Caso execute a aplicação agora, verá que todos os inputs estarão com a propriedade `:outlined="true"`

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/n7aeea8sam0tar3o6euu.png)

Agora, vamos adaptar o exemplo acima, para usar o `Dark Mode`, onde os inputs deverão ser `filled` no modo `dark` e `outlined` no `light`

**QPANC.App/src/boot/inject.js**
```js
import { factory } from '@toby.mosque/utils'
import inject from './inject'
import { QInput, Dark } from 'quasar'

// "async" is optional
export default inject(({ Vue }) => {
  const brand = {}
  brand.input = Vue.observable({
    /*
    style: {
      'font-size': '12px'
    },
    class: {
      'custom-input': true
    },
    */
    props: {
      filled: Dark.isActive
      outlined: !Dark.isActive
    }
  })

  factory.reBrand('q-input', QInput, brand.input)
  return {
    brand
  }
})
```

O problema aqui, é que o `Dark.isActive` está sendo usado apenas como o valor inicial para o `brand.input`, porém quando o `Dark.isActive` é alterado, ele não é propagado para o `brand.input`.

Então, precisaremos de um `watch` no `App.vue`.

**QPANC.App/src/App.vue**
```js
export default {
  name: 'App',
  watch: {
    '$q.dark.isActive' () {
      this.$brand.input.props.filled = this.$q.dark.isActive
      this.$brand.input.props.outlined = !this.$q.dark.isActive
    }
  }
}
```

E por fim, algumas prints.:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/f02bpj2wrvsrav1g4dsk.png)
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/xukf5vxur4h2y3s85haq.png)

##25 Persistência em Cookies

Como se trata de uma aplicação `SSR`, é natural que alguns dados serão persistidos no lado do cliente, coisas como o `Token JWT`, o tema e o idioma preferido.

Porém, alguns destes dados precisam ser acessados no lado do servidor, e como não podem ser recuperados de outra forma, teremos de usar Cookies.

O primeiro passo, será ativar o plugin responsável por ler os Cookies.

**QPANC.App/quasar.config.js**
```js
module.exports = function (ctx) {
  return {
    framework: 
      plugins: [
        'Cookies'
      ]
    }
  }
}
```

Agora, iremos adicionar um plugin para o vuex, no caso o `vuex-persistedstate`

```
yarn add vuex-persistedstate
```

Agora, adicione o boot `persist`, e não deixe e adicionar ele ao `quasar.config.js` > `boots`, é vital que ele seja adicionado antes do boot do `axios` e do `i18n`.

```
quasar new boot persist
```
**QPANC.App/quasar.config.js**
```js
module.exports = function (ctx) {
  return {
    boots: [
      'persist',
      'i18n',
      'axios'
    ]
  }
}
```

Antes de codificamos o boot `persist`, precisamos criar um pequeno serviço, que será responsável por detectar o idioma recomendado para o usuário.

**QPANC.App/src/services/locales.js**
```js
const locales = ['en-us', 'pt-br']
const regions = {
  en: 'en-us',
  pt: 'pt-br'
}
const fallback = regions.en
const detectLocale = function () {
  if (process.env.CLIENT) {
    const locale = navigator.language.toLowerCase()
    if (locales.includes(locale)) {
      return locale
    }
    const region = locale.split('-')[0]
    if (region in regions) {
      return regions[region]
    }
    return regions.en
  } else {
    return fallback
  }
}

export {
  locales,
  regions,
  fallback,
  detectLocale
}
```

Agora, vamos ao boot:

**QPANC.App/src/boot/persist.js**
```js
import { Cookies, Quasar } from 'quasar'
import createPersistedState from 'vuex-persistedstate'

const persistState = function ({ name, store, storage }) {
  createPersistedState({
    key: name,
    paths: [name],
    filter ({ type }) {
      return type.startsWith(name)
    },
    storage
  })(store)
}

export default function ({ store, ssrContext }) {
  const cookies = process.env.SERVER
    ? Cookies.parseSSR(ssrContext)
    : Cookies

  const cookieStorage = {
    getItem (key) {
      return JSON.stringify(cookies.get(key))
    },
    setItem (key, value) {
      cookies.set(key, value, { path: '/' })
    },
    removeItem (key) {
      cookies.remove(key, { path: '/' })
    }
  }

  persistState({ name: 'app', store, storage: cookieStorage })
  if (process.env.CLIENT) {
    // persistState({ name: 'local', store, storage: window.localStorage })
    store.commit('app/localeOs', detectLocale())
  }
}
```

Um pequeno detalhamento sobre o que está sendo feito:

```js
persistState({ name: 'app', store, storage: cookieStorage })
```

Estamos instruindo o `vuex-persistedstate` à persistir todo o modulo `app` em um `Cookie`

```js
if (process.env.CLIENT) {
  // persistState({ name: 'local', store, storage: window.localStorage })
}
```

Caso o comentário seja removido, estamos instruindo o `vuex-persistedstate` à persistir todo o modulo `local` no `localStorage`

```js
if (process.env.CLIENT) {
  store.commit('app/localeOs', detectLocale())
}
```

Estamos atualizando o `localeOs` para que ele seja igual ao `locale` disponível mais próximo ao que é utilizado pelo browser, este vai ser o idioma da aplicação, caso o usuário também não especifique o `localeUser`.

Porém vale lembrar, que o código no cliente é executado após a execução no servidor, então, na primeira requisição, o servidor irá sempre utilizar a linguagem padrão, no caso, o inglês (isto é configurável em `quasar.config.js > framework > lang`).

Desta forma, na primeira requisição, o app será carregado usando a linguagem padrão, e irá alternar para a linguagem informada pelo browser após a conclusão do carregamento da pagina.

Agora, vamos criar o nosso modulo `app`

**QPANC.App/src/store/app.js**
```js
import { factory } from '@toby.mosque/utils'

class AppStoreModel {
  constructor ({
    token = '',
    localeOs = '',
    localeUser = ''
  } = {}) {
    this.token = token
    this.localeOs = localeOs
    this.localeUser = localeUser
  }
}

const options = {
  model: AppStoreModel
}

export default factory.store({
  options,
  getters: {
    locale (state) {
      return state.localeUser || state.localeOs
    }
  }
})

export { options, AppStoreModel }
```

Como se trata de um modulo global, não esqueça de registra-lo no `QPANC.App/src/store/index.js`

**QPANC.App/src/store/index.js**
```js
import Vue from 'vue'
import Vuex from 'vuex'
import app from './app'

Vue.use(Vuex)

export default function (context) {
  const Store = new Vuex.Store({
    modules: {
      app
    },
    strict: process.env.DEV
  })

  return Store
}
```