banner_2_jhipster

JHipster: Criando uma Aplicação de Finanças Pessoais. – Parte 2

Olá jovens gafanhotos. Depois de um tempo sem escrever artigos, estou eu aqui de volta para dar continuidade ao nosso aplicativo de finanças pessoais. O motivo de ter demorado tanto? Mudei do Rio de Janeiro para Maringá (De volta para minha terra do Gugu).

Neste artigo, irei abordar a modelagem de dados do aplicativo assim como a customização do JpaRepository. Será um artigo breve porém essencial para o sucesso do nosso aplicativo. Nos próximos artigos, irei abordar o ElasticSearch e a implementação dos cálculos e validações do nosso aplicativo para que a partir dai, o mesmo seja implantado em um ambiente de “produção” com as autenticações necessárias para o próximo passo que será a construção de um dashboard de informações e o aplicativo mobile.

Sem mais delongas algumas coisas mudaram no jHipster de lá para cá. Criei um novo App com a versão mais atual do gerador e se você quiser fazer o mesmo, segue abaixo as configurações.

Atualização para criação da App

Atualização para criação da App

Mas o que mudou de lá para cá? Não mudou muita coisa, porém, há um ponto bacana de observar, agora a autenticação com JWT conta com autenticação nativa de redes sociais, ou seja, jovem aprendiz, sua aplicação poderá autenticar via Facebook, Twitter e Gmail.

Uma novidade é que a jetbrains.com me forneceu licenças de todas as IDEs dela, então em gratidão vou começar a usar a mesma nos meus tutoriais sempre dando aquele help para quem precisa configurar o ambiente no eclipse. Uma coisa bacana é que a Jetbrains fornece licenças para estudantes e professores, se você se enquadra aqui, acesse este link e solicite a sua https://www.jetbrains.com/student/.

Agora que já temos uma IDE bacana para trabalhar, vamos ao que interessa. Se você teve alguma dificuldade para criar o app, ou então quer começar deste ponto, neste link você encontra o aplicativo na versão inicial. GITHUB – FinAPP parte I

Vamos a modelagem de dados. Como nosso aplicativo é simples (sua imaginação pode ir longe a partir daqui), vamos criar 4 Tabelas:

Carteira (Será a nossa “conta” onde poderemos ter uma ou mais contas).

Clientes (Será quem nos envia ou recebe as movimentações)

Lançamentos (Os nossos lançamentos de receitas e despesas)

Categorias (As categorias ex: Lazer, Alimentação, Transporte etc.)

Lembre-se que isto é um tutorial de estudo, portanto, estou simplificando o máximo pois no decorrer dos tutoriais iremos criar um Dashboard e um aplicativo mobile para nosso sistema de finanças pessoais.

Vamos então criar nossas tabelas. Porque começar por elas? Simples, a partir daí o esqueleto do nosso sistema será totalmente gerado. Há algumas maneiras de fazer isso, são elas:

– Linha de comando utilizando o comando yo jhipster:entity <Nome da Entidade>

– JHipster UML que pode ser conferido aqui JHipster UML

– JDL Studio o qual vamos utilizar neste tutorial.

Mãos à Obra

Abra o link JDL STUDIO e vamos começar a modelar nosso banco de dados.

Por padrão, o JHipster já irá gerar os Ids nas nossas entidades, portanto, para modelagem inicial vamos ignorar estes atributos.

Nosso modelo ficou assim:

finapp

Diagrama de Modelo de Dados

E nosso JDL ficou assim:

/* Carteira ex: Itau, Caixa, Pessoal*/
entity Carteira {
	descricao String required,
    saldo BigDecimal required
}

/* Responsavel pelo recebimento ou pagamento do lancamento*/
entity Cliente {
	/* Campo obrigatorio com mais de 5 caracteres */
	nome String required minlength(5)
}

/* Categoria do lancamento ex: Alimentacao, Lazer, Presentes */
entity Categoria {
	descricao String required
}

/* Classe principal do nosso sistema de financas */
entity Lancamento {
	dataLancamento ZonedDateTime required,
    valorLancamento BigDecimal required,
    descricao String required, 
    pago Boolean,
    tipoLancamento TipoLancamento
}

/* Tipo de lancamento se o mesmo e uma despesa ou uma receita */
enum TipoLancamento {
	RECEITA, DESPESA
}

/* Relacionamentos entre entidades */
relationship ManyToOne {
	Lancamento{categoria} to Categoria,
    Lancamento{cliente} to Cliente,
    Lancamento{carteira} to Carteira
}

/* Paginacao com Scroll infinito */
paginate Carteira, Cliente, Categoria with infinite-scroll

/* Paginacao com paginas ex 1,2,3 */
paginate Lancamento with pagination

/* Quero que o JHipster crie o service de lancamentos para utilizacao */
service Lancamento with serviceClass

Se você quiser saber mais sobre o JDL como exemplos, tipos de relacionamentos, tipo de dados, validações etc. acesse este link: https://jhipster.github.io/jdl/

Terminado esta parte, vamos deixar que o JHipster faça o trabalho “árduo” para nós.

Coloque o arquivo finapp.jh na pasta principal do seu projeto. Abra o terminal na pasta e execute o comando:

H:\TiCarpa\finapp>npm install -g jhipster-uml

Ele será responsável por instalar o jhipster-uml na sua máquina.
Agora basta executar o comando para executar a geração conforme nosso modelo de dados.

H:\TiCarpa\finapp>jhipster-uml finapp.jh

Aguarde a execução do mesmo.
Após a finalização, basta iniciarmos a aplicação para verificar o resultado. Há duas maneiras de fazer isso:

Executando o comando do maven:

mvn spring-boot:run

Ou então executar o Run na classe:

FinappApp.java

run

Run da Aplicação

Pronto! Temos a aplicação rodando com nossas tabelas, repositórios, serviços RESTful e nosso Frontend prontos conforme a imagem.
Vamos agora implementar algumas melhorias permitindo que a aplicação funcione independentemente da quantidade de usuários que quiserem se cadastrar, você vai poder mostrar que é um foderoso garoto da computação e liberar a aplicação para aquele seu sogro cheio de dinheiro usar enquanto vigia a sua filha linda nas mãos deste virgem programador.

O que vamos fazer é relacionar os seus registros, ao seu usuário logado. Ou seja, todas as vezes que algo novo for inserido, o sistema irá atribuir o seu usuário autenticado aos registros. Isto irá impedir que pessoas mal-intencionadas, acessem as suas informações.

No geral, uma pequena porcentagem de usuários iria se atentar a descobrir informações, porém como a ideia é ensinar corretamente, vale a atenção na hora de analisar este tipo de problema. Imagine o seguinte cenário:

Você libera a aplicação para sua namorada utilizar para lançar as finanças pessoais dela e ver o quão foda você é na programação, o problema é que ela também é uma fodenda nerd como você, e descobre que se fizer uma chamada GET, ela consiguirá ver todos os seus lançamentos descobrindo que você deixou de ir com ela no cinema por falta de dinheiro, mas comprou aquele boneco caríssimo do Game Of Thrones? Melhor se prevenir, não é?

Vamos alterar nossas classes físicas:

Insira o relacionamento nas classes Carteira.java, Cliente.java, Categoria.java e Lancamento.java

    @JsonIgnore
    @ManyToOne
    private User user;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

Salve tudo e suba a aplicação.

Se você conectar no seu banco local (url para o h2: http://127.0.0.1:8080/h2-console), você verá que o relacionamento com o usuário não foi criado.

Isso ocorre, pois, o responsável pela atualização do banco de dados, não “enxergou a modificação”. Para que o liquidbase, gere a atualização para o banco. Para isso, abra o terminal na raiz do projeto e execute o comando:

mvn liquibase:diff

Veja a pasta src/main/resources/config/liquibase/changelog, um novo arquivo foi gerado, abra o mesmo, confira se tudo deu certo e inclua o mesmo no arquivo master.xml do liquid base no caminho: src/main/resources/config/liquibase/master.xml.

Suba a aplicação novamente. Verifique que agora, os campos de id do usuário foram criados nas tabelas conforme imagem:

h2_com_user

Por fim, o que fiz foi criar duas classes para fazer com que nossas listas, inserções de registros, edição e remoção dos mesmos, sejam baseadas no usuário logado.

Crie a classe CustomJpaRepository.java e utilize o código abaixo. Verifique que a classe é genérica e estende a classe JpaRepository.

Veja que os métodos de busca e delete foram alterados para realizar as queries baseadas nos usuários.

package br.com.ticarpa.repository;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.NoRepositoryBean;

import java.io.Serializable;

/**
 * Created by Tiago on 03/07/2016.
 */
@NoRepositoryBean
public interface CustomJpaRepository<T, Long extends Serializable> extends JpaRepository<T, Long> {

    @Query("select obj from #{#entityName} obj where obj.user.login = ?1 ")
    Page<T> findAllByUserIsCurrentUser(String userLogin, Pageable pageable);

    @Query("select obj from #{#entityName} obj where obj.user.login = ?1 and obj.id = ?2 ")
    T findOneByUserIsCurrentUserAndId(String userLogin, Long id);

    @Query("delete from #{#entityName} obj where obj.user.login = ?1 and obj.id = ?2 ")
    void deleteByUserIsCurrentUserAndId(String userLogin, Long id);

}

Faça o mesmo criando uma classe CustomSearchRepository.java

package br.com.ticarpa.repository.search;

import br.com.ticarpa.domain.User;
import org.elasticsearch.index.query.QueryBuilder;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.core.FacetedPage;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.data.repository.NoRepositoryBean;

import java.io.Serializable;

/**
 * Custom Search Repository
 */
@NoRepositoryBean
public interface CustomSearchRepository<T, Long extends Serializable> extends ElasticsearchRepository<T, Long> {

    @Query("delete from #{#entityName} obj where obj.user.login = ?1 and obj.id = ?2 ")
    void deleteByUserIsCurrentUserAndId(String userLogin, Long id);

}

Agora abra as suas classes de resources e modifique-as colocando os seguintes itens:

Primeiramente, vamos atribuir o usuário logado sempre que um registro seja salvo ou editado. Nossos métodos de inserção e edição ficarão assim:

    /**
     * POST  /carteiras : Create a new carteira.
     *
     * @param carteira the carteira to create
     * @return the ResponseEntity with status 201 (Created) and with body the new carteira, or with status 400 (Bad Request) if the carteira has already an ID
     * @throws URISyntaxException if the Location URI syntax is incorrect
     */
    @RequestMapping(value = "/carteiras",
        method = RequestMethod.POST,
        produces = MediaType.APPLICATION_JSON_VALUE)
    @Timed
    public ResponseEntity<Carteira> createCarteira(@Valid @RequestBody Carteira carteira) throws URISyntaxException {
        log.debug("REST request to save Carteira : {}", carteira);
        if (carteira.getId() != null) {
            return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert("carteira", "idexists", "A new carteira cannot already have an ID")).body(null);
        }
        carteira.setUser(userRepository.findOneByLogin(SecurityUtils.getCurrentUserLogin()).get());
        Carteira result = carteiraRepository.save(carteira);
        carteiraSearchRepository.save(result);
        return ResponseEntity.created(new URI("/api/carteiras/" + result.getId()))
            .headers(HeaderUtil.createEntityCreationAlert("carteira", result.getId().toString()))
            .body(result);
    }

    /**
     * PUT  /carteiras : Updates an existing carteira.
     *
     * @param carteira the carteira to update
     * @return the ResponseEntity with status 200 (OK) and with body the updated carteira,
     * or with status 400 (Bad Request) if the carteira is not valid,
     * or with status 500 (Internal Server Error) if the carteira couldnt be updated
     * @throws URISyntaxException if the Location URI syntax is incorrect
     */
    @RequestMapping(value = "/carteiras",
        method = RequestMethod.PUT,
        produces = MediaType.APPLICATION_JSON_VALUE)
    @Timed
    public ResponseEntity<Carteira> updateCarteira(@Valid @RequestBody Carteira carteira) throws URISyntaxException {
        log.debug("REST request to update Carteira : {}", carteira);
        if (carteira.getId() == null) {
            return createCarteira(carteira);
        }

        //Verifico se a carteira atualizada e do usuario logado
        Carteira carteiraUser = carteiraRepository.findOneByUserIsCurrentUserAndId(SecurityUtils.getCurrentUserLogin(), carteira.getId());
        if(!SecurityUtils.getCurrentUserLogin().equals(carteiraUser.getUser().getLogin())) {
           return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
        }
        carteira.setUser(userRepository.findOneByLogin(SecurityUtils.getCurrentUserLogin()).get());
        Carteira result = carteiraRepository.save(carteira);
        carteiraSearchRepository.save(result);
        return ResponseEntity.ok()
            .headers(HeaderUtil.createEntityUpdateAlert("carteira", carteira.getId().toString()))
            .body(result);
    }

Veja agora, que nossos métodos de busca e delete, passam o nosso método customizado no Repository:

  /**
     * GET  /carteiras : get all the carteiras.
     *
     * @param pageable the pagination information
     * @return the ResponseEntity with status 200 (OK) and the list of carteiras in body
     * @throws URISyntaxException if there is an error to generate the pagination HTTP headers
     */
    @RequestMapping(value = "/carteiras",
        method = RequestMethod.GET,
        produces = MediaType.APPLICATION_JSON_VALUE)
    @Timed
    public ResponseEntity<List<Carteira>> getAllCarteiras(Pageable pageable)
        throws URISyntaxException {
        log.info("REST request to get a page of Carteiras");
        Page<Carteira> page = carteiraRepository.findAllByUserIsCurrentUser(SecurityUtils.getCurrentUserLogin(), pageable);
        HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/carteiras");
        return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
    }

    /**
     * GET  /carteiras/:id : get the "id" carteira.
     *
     * @param id the id of the carteira to retrieve
     * @return the ResponseEntity with status 200 (OK) and with body the carteira, or with status 404 (Not Found)
     */
    @RequestMapping(value = "/carteiras/{id}",
        method = RequestMethod.GET,
        produces = MediaType.APPLICATION_JSON_VALUE)
    @Timed
    public ResponseEntity<Carteira> getCarteira(@PathVariable Long id) {
        log.debug("REST request to get Carteira : {}", id);
        Carteira carteira = carteiraRepository.findOneByUserIsCurrentUserAndId(SecurityUtils.getCurrentUserLogin(), id);
        return Optional.ofNullable(carteira)
            .map(result -> new ResponseEntity<>(
                result,
                HttpStatus.OK))
            .orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }

    /**
     * DELETE  /carteiras/:id : delete the "id" carteira.
     *
     * @param id the id of the carteira to delete
     * @return the ResponseEntity with status 200 (OK)
     */
    @RequestMapping(value = "/carteiras/{id}",
        method = RequestMethod.DELETE,
        produces = MediaType.APPLICATION_JSON_VALUE)
    @Timed
    public ResponseEntity<Void> deleteCarteira(@PathVariable Long id) {
        log.debug("REST request to delete Carteira : {}", id);
        carteiraRepository.deleteByUserIsCurrentUserAndId(SecurityUtils.getCurrentUserLogin(), id);
        carteiraSearchRepository.deleteByUserIsCurrentUserAndId(SecurityUtils.getCurrentUserLogin(), id);
        return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert("carteira", id.toString())).build();
    }

Lembre-se, ao invés das classes de repository estender o JpaRepository, elas terão que estender nossa classe customizada.

package br.com.ticarpa.repository;

import br.com.ticarpa.domain.Carteira;

/**
 * Spring Data JPA repository for the Carteira entity.
 */
@SuppressWarnings("unused")
public interface CarteiraRepository extends CustomJpaRepository<Carteira,Long> {

}

Lembre-se de fazer isso para as classes Resources e Repository das suas funcionalidades.

Faça o teste da sua aplicação e veja se tudo está funcionando corretamente. Veja que os itens foram criados no menu assim como toda a camada de visão.

2

Resultado da Aplicação

Conforme dito anteriormente, no próximo artigo vamos falar de ElasticSearch além de criar toda parte de regras, validações e funcionalidades dos lançamentos na nossa aplicação.

Espero que tenham gostado e em caso de dúvidas, basta deixar um comentário que o mesmo será respondido o quanto antes.

Quem quiser colaborar, fique a vontade.

Tiago Carpanese
Tiago Carpanese
Criador deste site, apaixonado por tecnologia, busco contribuir para a segregação do conhecimento. Não deixe de curtir este site no Facebook clicando em Curtir na Barra da Direita. Siga-me no Twitter para ficar por dentro das novidades. Não deixe de comentar !
  • Paulo Jorge Nascimento

    Muito bom, mas aqui deu erro de injeção. Baixei o seu codigo do github pra ver e até ele deu o mesmo erro de injeção do LancamentoSearchRepository. Sabe como arrumar?

  • Wilton Ribeiro

    Muito bom, estarei aguardando pelos próximos posts. ^^

    • Tiago Carpanese

      Muito obrigado. Irei ter novidades. Próximo post será autenticação e autorização com o Angular 2.

  • Júlio Serra

    Muito bom, está me ajudando muito, legal ter mudado para Maringá, sou vizinho, daqui de Cianorte, parabéns!!!

    • Tiago Carpanese

      Muito obrigado. Irei ter novidades. Próximo post será autenticação e autorização com o Angular 2. Conseguiu evoluir?

  • Miquéias Fernandes

    Show, excelente tutorial parabénss!

    • Tiago Carpanese

      Muito obrigado

  • Jairo Nascimento de Sousa

    Olá Tiago parabéns pela iniciativa ótimo tutorial, mais comecei agora a conhecer jHipster e estou usando a versão 4.5.2 e ja houve algumas mudanças no comando mai seu tutorial é muito importante e imprescindível para entender esse poderosa ferramenta.