DEV Community

Ruanitto
Ruanitto

Posted on

Criando um sistema de Chamados com AdonisJS - Parte 1

Alguns anos atrás entrei para uma empresa onde o sistema estava sendo migrado de Laravel - PHP para Javascript, não conhecia basicamente nada de javascript no backend, porém conhecia Laravel e resolvi procurar algo parecido.
Foi aí que encontrei o AdonisJS, um excelente framework Laravel Like que me ajudou muito nessa migração.
Mas, como desbravar o desconhecido sem ter uma bússola pra indicar a direção?
Resolvi procurar algum tutorial bem básico, pra entender o mínimo. Naquela época, a versão do Adonis era a 4, e encontrei um tutorial ensinando um sistema de abertura de chamados, porém na versão 3. Foi aí que começou a diversão, resolvi seguir o tutorial, porém reescrevendo com Adonis 4, e hoje, resolvi reescrever esse tutorial por aqui, com a ver~sao 6 desse framework que me ajuda diariamente.

Então vamos lá, espero que assim como aquele tutorial me foi útil, a reescrita dele por aqui possa também auxiliar mais alguém.

Seguindo a documentação do AdonisJS, em sua atual versão (6) o requisito mínimo é possuir a versão 20 do Node.js

$ node -v
# v20.0.0
Enter fullscreen mode Exit fullscreen mode

Iniciar um projeto com o Adonis é muito simple, basta executar o comando abaixo:

$ npm init adonisjs@latest adonis-support-ticket
Enter fullscreen mode Exit fullscreen mode

Explicando:

  • npm - gerenciador de pacotes do node
  • init - argumento fornecido para o npm, sinalizando a criação de um novo projeto
  • adonisjs@latest - pacote do AdonisJS responsável por iniciar um novo projeto do framework.
  • adonis-support-ticket - este será o nome do projeto adotado para este tutorial, porém, fica a seu critério escolher o melhor nome nesse seu estudo.

Ao executar o comando acma em seu terminal, será exibido o prompt abaixo para escolha do tipo de projeto a ser criado:

     _       _             _         _ ____  
    / \   __| | ___  _ __ (_)___    | / ___| 
   / _ \ / _` |/ _ \| '_ \| / __|_  | \___ \ 
  / ___ \ (_| | (_) | | | | \__ \ |_| |___) |
 /_/   \_\__,_|\___/|_| |_|_|___/\___/|____/ 


❯ Which starter kit would you like to use …  Press <ENTER> to select
  Slim Starter Kit A lean AdonisJS application with just the framework core
▸ Web Starter Kit Everything you need to build a server render app
  API Starter Kit AdonisJS app tailored for creating JSON APIs
  Inertia Starter Kit Inertia app with a frontend framework of your choice
Enter fullscreen mode Exit fullscreen mode

Para o nosso tutorial, utilizaremos Web Starter Kit como boilerplate.
Em seguida, é hora de escolher o método de autenticação a ser utilizado (Talvez em um futuro não tão distante eu escreva sobre, de qualquer forma na documentação oficial do AdonisJS é tudo bem explicado), utilizaremos aqui Session

❯ Which authentication guard you want to use …  Press <ENTER> to select
▸ Session Authenticate users using cookies and session
  Access Token Authenticate clients using API tokens
  Basic Auth Authenticate users using HTTP Basic Auth
  Skip I want to configure the Auth package manually
Enter fullscreen mode Exit fullscreen mode

Agora é a hora de escolhermos a configuração de banco de dados, para fins de desenvolvimento, nesse tutorial utilizaremos sqlite:

❯ Which database driver you want to use …  Press <ENTER> to select
▸ SQLite
  LibSQL
  MySQL
  PostgreSQL
  MS SQL
  Skip I want to configure Lucid manually
Enter fullscreen mode Exit fullscreen mode

Após finalizado, será exibido a mensagem abaixo no painel:

╭──────────────────────────────────────────────────────────────────╮
│    Your AdonisJS project has been created successfully!          │
│──────────────────────────────────────────────────────────────────│
│                                                                  │
│    ❯ cd adonis-support-ticket                                    │
│    ❯ npm run dev                                                 │
│    ❯ Open http://localhost:3333                                  │
│    ❯                                                             │
│    ❯ Have any questions?                                         │
│    ❯ Join our Discord server - https://discord.gg/vDcEjq6        │
│                                                                  │
╰──────────────────────────────────────────────────────────────────╯
Enter fullscreen mode Exit fullscreen mode

Abra na IDE ou editor de texto de sua preferência, e vamos começar.

Primeiramente vamos editar o arquivo de migration da tabela users criado por padrão com o boilerplate web do Adonis, o arquivo encontrase no diretório database->migrations, vamos adicionar a coluna user_type para o controle de acesso do nosso sistema, e também alterar o limite de caracteres para as colunas full_name e email, ficando assim:

// database/migrations/....create_users_table.ts

import { BaseSchema } from '@adonisjs/lucid/schema'

export default class extends BaseSchema {
  protected tableName = 'users'

  async up() {
    this.schema.createTable(this.tableName, (table) => {
      table.increments('id').notNullable()
      table.string('full_name', 150).nullable()
      table.string('email', 100).notNullable().unique()
      table.string('password').notNullable()
      table.enum('user_type', ['admin', 'user']).defaultTo('user').notNullable()

      table.timestamp('created_at').notNullable()
      table.timestamp('updated_at').nullable()
    })
  }

  async down() {
    this.schema.dropTable(this.tableName)
  }
}
Enter fullscreen mode Exit fullscreen mode

Feito isso, precisamos também realizar a alteração em nosso arquivo model da tabela, localizado em app->models

// app/models/users.ts

import { DateTime } from 'luxon'
import hash from '@adonisjs/core/services/hash'
import { compose } from '@adonisjs/core/helpers'
import { BaseModel, column } from '@adonisjs/lucid/orm'
import { withAuthFinder } from '@adonisjs/auth/mixins/lucid'

const AuthFinder = withAuthFinder(() => hash.use('scrypt'), {
  uids: ['email'],
  passwordColumnName: 'password',
})

export type UserType = 'admin' | 'user'

export default class User extends compose(BaseModel, AuthFinder) {
  @column({ isPrimary: true })
  declare id: number

  @column()
  declare fullName: string | null

  @column()
  declare email: string

  @column({ serializeAs: null })
  declare password: string

  @column()
  declare userType: UserType

  @column.dateTime({ autoCreate: true })
  declare createdAt: DateTime

  @column.dateTime({ autoCreate: true, autoUpdate: true })
  declare updatedAt: DateTime | null
}
Enter fullscreen mode Exit fullscreen mode

Arquivos editados, é hora de publicarmos nossa tabela através do comando node ace migration:run

$ node ace migration:run
[ info ] Upgrading migrations version from "1" to "2"
❯ migrated database/migrations/1721143497721_create_users_table

Migrated in 69 ms
Enter fullscreen mode Exit fullscreen mode

Nosso arquivo de banco sql será atualizado com a tabela user.

Feito isso, podemos partir para algo visual no sistema, vamos editar nossa home page para começar a dar uma cara ao nosso pequeno sistema.
O Adonis, segue o modelo de arquitetura MVC, que resumidamente é composta por:

  • Model: classes que se comunicam com o banco
  • View: arquivos de renderização de páginas, como o nome diz, arquivos de visualização
  • Controller: classes que em resumo, são responsáveis por receber dados de requisições da view, e entregar para a view dados do banco através dos models, é claro que é bem mais complexo que isso, porém este não é o foco desse tutorial.

Para construção de views, o Adonis utiliza o Edge.js, uma biblioteca de renderização html com vários recursos interessantes, então vamos criar nosso arquivo principal de layout que vai servir como base para todo o projeto.

A criação de um arquivo de view no Adonis é simples, basta executar o comando node ace make:view MinhaView, então vamos criar nosso arquivo main dentro do diretório layout:

$ node ace make:view components/layout/master       
DONE:    create resources/views/components/layout/master.edge
Enter fullscreen mode Exit fullscreen mode

Como não sou focado em frontend, não vou entrar em muitos detalhes sobre as views, apenas explicando o básico sobre a biblioteca Edge.js.

Vamos editar nosso arquivo da seguinte forma:

// resources/views/components/layout/master.edge
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

    <title>Adonis Support - {{ title || 'Home Page' }}</title>

    <link rel="icon" href="/assets/favicon.png" type="image/x-icon">

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

    @if ($slots.meta)
      {{{ await $slots.meta() }}}
    @endif

    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
    <nav class="navbar navbar-default">
      <div class="container">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse" aria-expanded="false">
            <span class="sr-only">Menu</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="/">Adonis Support</a>
        </div>

        <div class="collapse navbar-collapse" id="navbar-collapse">
          <ul class="nav navbar-nav navbar-right">
            @if(auth.user)
              <li><a href="/new_ticket">Open Ticket</a></li>
              <li class="dropdown">
                <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ auth.user.fullName }} <span class="caret"></span></a>
                <ul class="dropdown-menu">
                  <li><a href="/my_tickets">My Tickets</a></li>
                  <li role="separator" class="divider"></li>
                  <li><a href="/logout">Logout</a></li>
                </ul>
              </li>
            @else
              <li><a href="/login">Login</a></li>
              <li><a href="/register">Register</a></li>
            @endif
          </ul>
        </div>
      </div>
    </nav>

    <div class="container">
      {{{ await $slots.main() }}}
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Esse será o layout principal do nosso sistema, será utilizado como base em todas as páginas.

Vamos editar a view padrão para que utilize o nosso template, para isso vamos editar o arquivo views->home.edge, alterando para o seguinte conteúdo:

@layout.master({ title: "AdonisJS - A fully featured web framework for Node.js" })
  @slot('meta')
    <meta name="description" content="Home Page">
  @endslot

  <h1> It Works! </h1>
@end
Enter fullscreen mode Exit fullscreen mode

Vamos ver como está ficando?

Para isso, vamos executar nosso projeto através do comando node ace serve --watch

$ node ace serve --watch
Enter fullscreen mode Exit fullscreen mode

No console, será exibido o seguinte conteúdo:

[ info ] starting HTTP server...
╭─────────────────────────────────────────────────╮
│                                                 │
│    Server address: http://localhost:3333        │
│    Watch Mode: Legacy                           │
│    Ready in: 808 ms                             │
│                                                 │
╰─────────────────────────────────────────────────╯
[20:05:18.208] INFO (75529): started HTTP server on localhost:3333
[ info ] watching file system for changes...
Enter fullscreen mode Exit fullscreen mode

Ou seja, está indicando que nosso projeto está em execução em localhost na porta 3333, sendo assim, basta abrir o navegador e acessar http://localhost:3333

Será possível visualizar a seguinte página:

Image description

Caso abra corretamente, significa que até o momento, obtivemos sucesso!

Então vamos dar continuidade.

O AdonisJS conta com um arquivo de rotas que controla cada requisição do navegador, este arquivo está localizado em start->routes.ts

Dentro dele por padrão, existe o seguinte conteúdo:

/*
|--------------------------------------------------------------------------
| Routes file
|--------------------------------------------------------------------------
|
| The routes file is used for defining the HTTP routes.
|
*/

import router from '@adonisjs/core/services/router'

router.on('/').render('pages/home')
Enter fullscreen mode Exit fullscreen mode

A última linha desse arquivo, mostra que ao acessar a raiz do sistema em execução, será renderizado o conteúdo de views->pages->home.edge

Então vamos alterar nosso home.edge com o conteúdo abaixo:

@layout.master({ title: 'Welcome' })
  @slot('meta')
    <meta name="description" content="Welcome">
  @endslot

  <div class="container">
    <div class="row">
        <div class="col-md-10 col-md-offset-1">
            <div class="panel panel-default">
                <div class="panel-body">
                    <p>Welcome!</p>

                    @if(auth.user?.userType === 'admin')
                        <p>
                            See all <a href="/admin/tickets">tickets</a>
                        </p>
                    @else
                        <p>
                            See all your <a href="/my_tickets">tickets</a> or <a href="/new_ticket">open new ticket</a>
                        </p>
                    @endif
                </div>
            </div>
        </div>
    </div>
  </div>
@end
Enter fullscreen mode Exit fullscreen mode

Nossa home ficará assim:

Image description

Vamos então, criar nossa página para registro de usuários, para que assim possamos autenticar no nosso sistema e dar seguimento nas demais partes do nosso projeto.

Para isso, precisaremos criar um controller para cuidar de nossos registros e autenticações.
Trabalhar com controllers no Adonis é bem simples, basta usarmos o comando node ace make:controller MeuController que o controller será criado dentro da estrutura do nosso projeto.

Então vamos criar nosso controller de autenticação, chamado Auth:

$ node ace make:controller Auth
DONE:    create app/controllers/auth_controller.ts
Enter fullscreen mode Exit fullscreen mode

Vamos abrir o arquivo criado e adicionar um método para exibição da nossa página de registro que criaremos mais adiante:

// app/controllers/auth_controller.ts
import type { HttpContext } from '@adonisjs/core/http'

export default class AuthController {
  showRegisterPage({ view }: HttpContext) {
    return view.render('pages/register')
  }
}
Enter fullscreen mode Exit fullscreen mode

Adicionamos o método showRegisterPage, responsável por entregar ao navegador a página de registro renderizada.
Precisamos então, criar nossa página de registro:

$ node ace make:view pages/register
Enter fullscreen mode Exit fullscreen mode

Adicionamos ao arquivo gerado, o seguinte conteúdo:

@layout.master({ title: 'Register' })
  @slot('meta')
    <meta name="description" content="Register Page">
  @endslot

  <div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Register</div>
                <div class="panel-body">

                </div>
              </div>
          </div>
      </div>
  </div>
@end
Enter fullscreen mode Exit fullscreen mode

E em nosso arquivo de rotas start->routes.ts, o direcionamento para a página register:

// start/routes.ts
/*
|--------------------------------------------------------------------------
| Routes file
|--------------------------------------------------------------------------
|
| The routes file is used for defining the HTTP routes.
|
*/

const AuthController = () => import('#controllers/auth_controller')
import router from '@adonisjs/core/services/router'
import { middleware } from './kernel.js'

router.on('/').render('pages/home')

router.get('register', [AuthController, 'showRegisterPage']).as('register.show').use(middleware.guest())
Enter fullscreen mode Exit fullscreen mode

O que resultará na seguinte página:

Image description

Como podemos ver, precisamos acrescentar a nossa página, um formulário para que o usuário preencha com as informaçõe sde cadastro.
Vamos então, criar alguns componentes que serão reutilizados em nosso sistema, como botões, inputs, textarea, etc.

Vamos começar com nosso componente de form input:

$ node ace make:view components/form/input
Enter fullscreen mode Exit fullscreen mode

Adicionaremos o seguinte conteúdo:

@let(valueOld = name ? old(name) : '')
@let(value = $props.value || valueOld)

<div class="form-group">
  @if ($slots.label || label)
    <label class="col-md-4 control-label">
      @if ($slots.label)
        {{{ await $slots.label() }}}
      @else
        {{ label }}
      @endif
    </label>
  @endif

  <div class="col-md-6">
    @if (type === 'select')
      <select name="{{ name || '' }}" required="{{ required || false}}">
        {{{ await $slots.main() }}}
      </select>
    @elseif (type === 'textarea')
      <textarea name="{{ name || '' }}" required="{{ required || false}}">{{ value || '' }}</textarea>
    @else
      <input type="{{ type || 'text' }}" name="{{ name || '' }}" value="{{ value || '' }}" required="{{ required || false}}" class="form-control"/>
    @endif
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Vamos voltar a nossa register view, e acrescentar dentro da div central com a class panel-body, nosso form e inputs:

                <div class="panel-body">

                  <form method="POST" action="" class="form-horizontal">
                    {{ csrfField() }}

                    @!form.input({
                      label: 'Full Name',
                      name: 'fullName',
                      required: true
                    })

                    @!form.input({
                      label: 'E-mail',
                      name: 'email',
                      type: 'email',
                      required: true
                    })

                    @!form.input({
                      label: 'Password',
                      name: 'password',
                      type: 'password',
                      required: true
                    })

                    @!form.input({
                      label: 'Password confirmation',
                      name: 'password_confirmation',
                      type: 'password',
                      required: true
                    })

                  </form>
                </div>
Enter fullscreen mode Exit fullscreen mode

Teremos então a seguinte página:

Image description

Vamos agora adicionar ao nosso formulário, o botão de ação:

$ node ace make:view components/form/button
Enter fullscreen mode Exit fullscreen mode

Com o seguinte conteúdo:

<div class="form-group">
  <div class="col-md-6 col-md-offset-4">
      <button class="btn btn-primary" type="{{ type || 'submit' }}">
        @if ($slots.text)
          {{{ await $slots.text() }}}
        @else
          {{ text }}
        @endif
      </button>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

E depois adicionamos ao final do nosso formulário da seguinte forma:

                    @!form.button({
                      text: 'Register',
                      type: 'submit'
                    })
Enter fullscreen mode Exit fullscreen mode

Resultado:

Image description

Observe que ao clicar em register, apresentará um erro, pois ainda não temos a rota de registro criada em nosso sistema. Então vamos adicionar ao nosso controller:

// app/controllers/auth_controller.ts
...
  async store({ request, response }: HttpContext) {
    await User.create({
      fullName: request.input('fullName'),
      email: request.input('email'),
      password: request.input('password'),
    })

    // redirect to homepage
    response.redirect('/')
  }
Enter fullscreen mode Exit fullscreen mode

E em nosso arquivo de rotas start->routes.ts a rota responsável por direcionar a requisição ao controller:

// start/routes.ts
...

router.post('register', [AuthController, 'store']).as('register.store')
Enter fullscreen mode Exit fullscreen mode

Agora em action do nosso form, adicionamos o seguinte:

...
 <form method="POST" action="{{ route('register.store') }}"
...
Enter fullscreen mode Exit fullscreen mode

Explicando:

route('register.store') traduz após a renderização da página para a URL de POST responsável por receber a requisição de registro de novo usuário, registe.store trata-se de um aliás adicionado a nossa rota /register cadastrada no arquivo de rotas.

Ao preenchermos o cadastro, já teremos um usuário no banco de dados:

Image description

Porém, precisamos acrescentar alguns items de segurança e validação ao nosso formulário de registro.
Para isso, o AdonisJS utiliza a biblioteca VineJS, que possui uma ampla abrangência de validações.

Vamos adicionar ela em nosso projeto com o comando abaixo, que realiza a instalação e configuração básica da mesma:

$ node ace add vinejs
Enter fullscreen mode Exit fullscreen mode

Em seguida, criaremos a classe validator responsável por tratar essa requisição de cadastro, validando os dados de entrada e retornando para o usuário os erros, caso ocorram.

O comando para criar uma classe validator é node ace make:validator MeuValitator

Então vamos criar o RegisterValidator:

$ node ace make:validator Auth
DONE:    create app/validators/auth.ts
Enter fullscreen mode Exit fullscreen mode

Vamos abrir nosso arquivo, e criar nossa regra de validação para cadastro de novos usuários do sistema, adicionando noddo registrationUserValidator:

// app/validators/auth.ts
import vine from '@vinejs/vine'

export const registrationUserValidator = vine.compile(
  vine.object({
    fullName: vine.string().trim().minLength(5).maxLength(150),
    email: vine.string().trim().email(),
    password: vine.string().trim().minLenght(8).confirmed()
  })
)
Enter fullscreen mode Exit fullscreen mode

Nosso validator contém a seguinte estrutura:

  • fullName: responsável por validar o nome digitado, formato string, removendo espaços (trim), tamanho mínimo de 5 caracteres e máximo 150 (Conforme nossa coluna no banco de dados)
  • email: valida se foi preenchido com um endereço de e-mail válido
  • password: valida a senha digitada e com confirmação preenchida, nesse caso é validado automaticamente se foi enviado também um campo password_confirmation.

Agora vamos adicionar o validator no método store do nosso AuthController, adicionando aos imports e utilizando na nossa requisição, ficando assim:

// app/controllers/auth_controller.ts
...
import { registrationUserValidator } from '#validators/auth'
...
  async store({ request, response }: HttpContext) {
    const payload = await request.validateUsing(registrationUserValidator)

    await User.create(payload)

    // redirect to homepage
    response.redirect('/')
  }
Enter fullscreen mode Exit fullscreen mode

E caso ocorram erros? Onde serão exibidos?

Deveremos retornar na tela para o usuários, os erros de validação, caso contrário nossa página não irá exibir nada. Como estamos utilizando form em nossa página, por padrão o AdonisJS vai inserir essas mensagens na session do navegador, então vamos fazer a leitura dessas mensagens e exibir para o usuário, para isso utilizaremos dois métodos disponíveis no template:

  • @inputError(value): método reponsável por obter a mensagem de erro na session, onde value será o nome do nosso campo no form.
  • @each(): onde vamos obter cada mensagem disponível na session para o campo do formulário.

Então vamos adicionar ao final do nosso form input o seguinte item, para que os erros possam ser visualizados:

// components/form/input
...
  <div class="col-md-6">
    @if (type === 'select')
      <select name="{{ name || '' }}" required="{{ required || false}}">
        {{{ await $slots.main() }}}
      </select>
    @elseif (type === 'textarea')
      <textarea name="{{ name || '' }}" required="{{ required || false}}">{{ value || '' }}</textarea>
    @else
      <input type="{{ type || 'text' }}" name="{{ name || '' }}" value="{{ value || '' }}" required="{{ required || false}}" class="form-control"/>
    @endif

    @inputError(name)
      @each(message in $messages)
        <h5 class="text-danger">
          {{ message }}
        </h5>
      @end
    @end
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Caso ocorram erros, ficará da seguinte forma:

Image description

E caso alguém tente realizar cadastro com um email já utilizado?

Opa, um erro não previsto:

Image description

O campo email em nossa migration do banco de dados é unique, sendo assim um endereço e-mail só pode ser cadastrado uma vez no sistema. Então teremos de adicionar esta validação ao nosso registrationUserValidator:

// app/validators/auth.ts
import vine from '@vinejs/vine'

export const registrationUserValidator = vine.compile(
  vine.object({
    fullName: vine.string().trim().minLength(5).maxLength(150),
    email: vine.string().trim().email().unique(async (db, value) => {
      const user = await db
        .from('users')
        .where('email', value)
        .first()

      return !user
    }),
    password: vine.string().trim().confirmed()
  })
)
Enter fullscreen mode Exit fullscreen mode

Erro tratado:

Image description

Sabia que além das várias regras de validação, é possível criar regras personalizadas atendendo aos critérios da aplicação a ser desenvolvida? O VineJS, no link da documentação explica como criar a própria custom rule, deixo então um desafio para que seja criado uma regra de validação de senha segura.

Vamos seguir então para a próxima etapa do nosso cadastro de usuário, realizar a autenticação do usuário após o cadastro efetuado com sucesso.
Para isso, vamos atualizar algumas coisas no nosso método store do nosso AuthController:

  • salvar os dados do usuário cadastrado em uma variável
  • adicionar classe auth, responsável pela autenticação
  • passar no método de autenticação, o usuário cadastrado
// app/controllers/auth_controller.ts
...
import { registrationUserValidator } from '#validators/auth'
...
  async store({ auth, request, response }: HttpContext) {
    const payload = await request.validateUsing(registrationUserValidator)

    const user = await User.create(payload)

    await auth.use('web').login(user)

    // redirect to homepage
    response.redirect('/')
  }
Enter fullscreen mode Exit fullscreen mode

Em await auth.use('web').login(user) o sistema realizará automaticamente a autenticação do nosso usuário cadastrado, então precisaremos exibir essa informação em nosso sistema.
Para isso, em nosso arquivo master.edge, vamos adicionar mais um método que verifica se o usuário está autenticado, caso verdadeiro, será exibido no topo da página o nome do usuário autenticado:

// resources/views/components/layout/master.edge
...
</head>
@eval(await auth.check())
<body>
...
Enter fullscreen mode Exit fullscreen mode

Ficando assim:

Image description

Ao clicar no nome de usuário, é aberto um menu, com as opções "My tickets" e "Logout", vamos primeiro criar nossa funcionalidade de logout, e em seguida, criar nossa tela de login.

Em nosso AuthController, vamos acrescentar o método de logout:

// app/controllers/auth_controller.ts
...
  async logout({ auth, response }: HttpContext) {
    await auth.use('web').logout()

    // redirect to homepage
    response.redirect('/')
  }
Enter fullscreen mode Exit fullscreen mode

E em nosso arquivo de rotas:

// start/routes.ts
...
router.get('logout', [AuthController, 'logout']).as('user.logout')
Enter fullscreen mode Exit fullscreen mode

Agora ao clicar em Logout, o usuário é "desautenticado" do sistema.
Então vamos criar nossa tela de login.

$ node ace make:view pages/login
DONE:    create resources/views/pages/login.edge
Enter fullscreen mode Exit fullscreen mode

Adicionando o seguinte:

// resources/views/pages/login.edge
@layout.master({ title: 'Login' })
  @slot('meta')
    <meta name="description" content="Login Page">
  @endslot

  <div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Login</div>
                <div class="panel-body">

                  <form method="POST" action="{{ route('user.login') }}" class="form-horizontal">
                    {{ csrfField() }}

                    @!form.input({
                      label: 'E-mail',
                      name: 'email',
                      type: 'email',
                      required: true
                    })

                    @!form.input({
                      label: 'Password',
                      name: 'password',
                      type: 'password',
                      required: true
                    })

                    @!form.button({
                      text: 'Login',
                      type: 'submit'
                    })

                  </form>
                </div>
              </div>
          </div>
      </div>
  </div>
@end

Enter fullscreen mode Exit fullscreen mode

Em nosso arquivo de rotas:

// start/routes.ts
...
router.get('login', [AuthController, 'showLoginPage']).use(middleware.guest())
router.post('login', [AuthController, 'login']).as('user.login')
router.get('logout', [AuthController, 'logout']).as('user.logout')
Enter fullscreen mode Exit fullscreen mode

Em nosso AuthController:

// app/controllers/auth_controller.ts
...

  showRegisterPage({ view }: HttpContext) {
    return view.render('pages/register')
  }

  showLoginPage({ view }: HttpContext) {
    return view.render('pages/login')
  }
...

  async login({ auth, request, response }: HttpContext) {
    const { email, password } = request.only(['email', 'password'])
    const user = await User.verifyCredentials(email, password)

    await auth.use('web').login(user)

    // redirect to homepage
    response.redirect('/')
  }

  async logout({ auth, response }: HttpContext) {
    await auth.use('web').logout()

    // redirect to homepage
    response.redirect('/')
  }
...
Enter fullscreen mode Exit fullscreen mode

Ao acessarmos http://localhost:3333/login, teremos a seguinte tela:

Image description

Agora é possível realizar login em nosso sistema.

Mas e se as credenciais de autenticação forem inválidas? Como exibir para o usuário?

Vamos adicionar a nossa tela de login o seguinte:

// resources/views/pages/login.edge
@layout.master({ title: 'Login' })
  @slot('meta')
    <meta name="description" content="Login Page">
  @endslot

  @error('E_INVALID_CREDENTIALS')
    <div class="alert alert-danger" role="alert">
      {{ $message }}
    </div>
  @end

  <div class="container">
...
Enter fullscreen mode Exit fullscreen mode

Então caso as credenciais sejam inválidas, teremos a seguinte visualização:

Image description

Lembra do arquivo de migration da nossa tabela de usuários? Lá temos dois tipos de usuário:

  • admin
  • user

Por padrão, todos os usuários cadastrados em tela no sistema são do tipo admin, já que esse é o valor default em nossa coluna user_type.

Vamos então adicionar um usuário admin padrão ao nosso sistema.
Para isso, utilizaremos um recurso presente no Lucid (ORM Padrão utilizado no AdonisJS) chamado Seeder.

Criaremos uma migration de users com o seguinte comando:

$ node ace make:seeder User
DONE:    create database/seeders/user_seeder.ts
Enter fullscreen mode Exit fullscreen mode

Vou utilizar como exemplo, os dados abaixo:

// database/seeders/user_seeder.ts
import { BaseSeeder } from '@adonisjs/lucid/seeders'
import User from '#models/user'

export default class UserSeeder extends BaseSeeder {
  async run() {
    await User.create({
        fullName: 'System Admin',
        email: 'admin@admin.com',
        password: 'supersecretpassword',
        userType: 'admin'
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

É possível adicionar mais de 1 usuário ao mesmo tempo, basta alterar o método create para createMany e adicionar mais items ao array do objeto create, exemplo:

// database/seeders/user_seeder.ts
import { BaseSeeder } from '@adonisjs/lucid/seeders'
import User from '#models/user'

export default class UserSeeder extends BaseSeeder {
  async run() {
    await User.createMany([
      {
        fullName: 'System Admin',
        email: 'admin@admin.com',
        password: 'supersecretpassword',
        userType: 'admin'
      },
      {
        fullName: 'User 1',
        email: 'user@user.com',
        password: 'supersecretpassword',
        userType: 'user'
      },
      {
        fullName: 'User 2',
        email: 'user2@user.com',
        password: 'supersecretpassword',
        userType: 'user'
      },
      { ... },
    ])
  }
}
Enter fullscreen mode Exit fullscreen mode

Porém, nesse tutorial vamos criar apenas o System Admin.
Para inserir no banco esses dados, basta utilizar o comando abaixo:

$ node ace db:seed
❯ completed database/seeders/user_seeder
Enter fullscreen mode Exit fullscreen mode

Mais detalhes na documentação.

Vamos agora testar o login com nosso usuário admin:

Image description

Vamos finalizar por aqui essa parte 1 do nosso tutorial, espero que esteja escrito de uma forma simples e objetiva, em caso de dúvidas, sugestões, correções, podem deixar nos comentários que assim que possível, responderei.
Na parte 2, vamos começar a criar nosso cadastro de chamados.

Nos vemos em breve!

Top comments (1)

Collapse
 
caiquelaurentino profile image
Caique da Silva Laurentino

boa