Skip to content

Commit

Permalink
feat: versão 1.0.0 da API banking (#2)
Browse files Browse the repository at this point in the history
* 🎉 hello world iniciando o projeto

* 📦 adicionado go mod para versionamento semantico

* ✨ iniciando um servidor basico

* 📦 adicionada dependencia gorilla mux

* ✨ mudando o roteador para o gorilla mux

* chore: adicionado makefile para build do binario

* chore: adicionado o driver do mysql

* refactor: organizando estrutura do projeto

* feat: adicionada conexao com o mysql e logging

* chore: adicionado docker compose com live-reload

* style: renomeando config para arquivo oculto

* chore: atualizando makefile para build e live-reload

* refactor: refatorando a estrutura do projeto em modulos pkg

* refactor: criando servidor roteador e logger para a API

* chore: atualizando dependencias de logging

* feat: adicionado middleware para log dos requests na API

* chore: movendo o middleware de logging para o modulo banking

* feat: estruturando as rotas com middlewares da API via negroni

* feat: gerenciando conexão com o DB via gorm

* feat: migrando base de dados no main da API

* fix: consertando padrões de roteamento das rotas da API

* chore: cleanup de algumas funções usando logrus

* fix: alterando daemon de live-reload e processo de build no docker e makefile

* chore: comentario dos arquivos no gitignore

* feat: implementados os modelos e handlers de accounts na API

* refactor: init da main refatorado para uso do debugmode

* feat: criado module em pkg para hash dos secrets

* fix: refatoração de models para evitar cycle import

* feat: implementada logica de transferencia entre contas

* feat: funcionalidade de login retornando token JWT implementada

* style: formatação e tradução dos erros para o usuário

* feat: adicionado autenticação pelo token JWT em transferências

* fix: consertado bug na listagem de transferências

* docs: atualizando o README com detalhes do projeto

* fix: retorna json no post da conta e transfer

* docs: atualizando com documentação do postman

* style: removendo rota hello de testes
  • Loading branch information
YohanAlexander authored Dec 3, 2020
1 parent 001b9b0 commit d6c384c
Show file tree
Hide file tree
Showing 17 changed files with 855 additions and 86 deletions.
3 changes: 2 additions & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
BUILD_TARGET="development"
DEBUG_MODE="true"
DEBUG_MODE="false"
TOKEN_KEY="gophers"
SERVER_ADDRESS="8080"
POSTGRES_PASSWORD="root"
POSTGRES_USER="postgres"
Expand Down
115 changes: 113 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,113 @@
# desafio-banking-go
API Restful simulando um banco digital usando Golang
# API Restful simulando um banco digital

## Sobre o projeto

O projeto consiste em uma API de transferência entre contas internas de um banco digital que usa o formato JSON para leitura e escrita.

## Entidades da API

### A entidade `Account` possui os seguintes atributos:

* `id`
* `name`
* `cpf`
* `secret`
* `balance`
* `created_at`

### A entidade `Login` possui os seguintes atributos:

* `cpf`
* `secret`

### A entidade `Transfer` possui os seguintes atributos:

* `id`
* `account_origin_id`
* `account_destination_id`
* `amount`
* `created_at`

## Rotas da API

| Metódo | URL | Descrição | Autenticação |
|--------|--------------------------------|--------------------------------------------------|--------------|
| GET | /accounts | retorna a lista de contas no banco | Não |
| GET | /accounts/{account_id}/balance | retorna o saldo da conta no banco | Não |
| POST | /accounts | cria uma conta no banco | Não |
| POST | /login | autentica a conta no banco e retorna o token JWT | Não |
| GET | /transfers | retorna as transferências da conta no banco | Sim |
| POST | /transfers | transfere de uma conta para outra no banco | Sim |


## Documentação

Uma documentação online completa das rotas da API e dos corpos de requisição e resposta pode ser vista em [Postman](https://documenter.getpostman.com/view/12847022/TVmMgxjU).

## Dependências

O projeto foi estruturado em containers e para o deploy são necessárias as dependências:

* [Docker](https://docs.docker.com/engine/install/)
* [Docker-Compose](https://docs.docker.com/compose/install/)
* [GNU Make](https://www.gnu.org/software/make/)

## Configurações da API

O projeto faz uso de variáveis de ambiente que são definidas no arquivo `.env`:

```
BUILD_TARGET="development"
DEBUG_MODE="false"
TOKEN_KEY="gophers"
SERVER_ADDRESS="8080"
POSTGRES_PASSWORD="root"
POSTGRES_USER="postgres"
POSTGRES_PORT="5432"
POSTGRES_HOST="db"
POSTGRES_DB=""
```

### Build target

É o target para o build multi-stage do docker-compose. "development" faz live-reload do código fonte, "production" faz compilação do binário estático.

### Debug mode

É o modo que será utilizado no ambiente. "true" faz uso do modo Debug que mostra as consultas SQL, "false" faz uso do modo Silent sem retorno das consultas.

### Token key

É a chave `salt` usada para gerar a criptografia do token JWT.

### Server address

É a porta na qual o servidor será disponibilizado.

### Postgres

São as configurações para o DSN do banco de dados: usuário, senha, host, porta, database.

## Uso da API

Para iniciar a API localmente com as dependências já instaladas use o comando:

``` sh
make run
```

Para ver os logs da API use o comando:

``` sh
make logs
```

Para desligar a API use o comando:

``` sh
make stop
```

## Testando a API

Para fazer requisições e testar a API use alguma ferramenta como o [HTTPie](https://httpie.io/) ou [Postman](https://www.postman.com/). A documentação já conta com as [collections](https://documenter.getpostman.com/view/12847022/TVmMgxjU) para os testes.
92 changes: 92 additions & 0 deletions cmd/banking/handlers/account/account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package account

import (
"encoding/json"
"fmt"
"net/http"

"github.com/gorilla/mux"
"github.com/yohanalexander/desafio-banking-go/cmd/banking/models"
"github.com/yohanalexander/desafio-banking-go/pkg/app"
)

// ListAccounts handler para listar accounts no DB
func ListAccounts(app *app.App) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()

// capturando accounts no DB
var a []models.Account
if err := app.DB.Client.Find(&a); err.Error != nil {
// caso tenha erro ao procurar no banco retorna 500
http.Error(w, "Erro na listagem das contas", http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(a)

}
}

// PostAccount handler para criar account no DB
func PostAccount(app *app.App) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()

// capturando account no request
a := &models.Account{}
if err := json.NewDecoder(r.Body).Decode(&a); err != nil {
// caso tenha erro no decode do request retorna 400
http.Error(w, "Formato JSON inválido", http.StatusBadRequest)
return
}

// validando json do struct account
if err := app.Vld.Struct(a); err != nil {
// traduzindo os erros do JSON inválido
errs := app.TranslateErrors(err)
// caso o corpo do request seja inválido retorna 400
w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, errs)
return
}

// armazenando struct account no DB
account, err := a.CreateAccount(app)
if err != nil {
// caso tenha erro ao armazenar no banco retorna 500
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(account)

}
}

// BalanceAccount handler para retornar o saldo da account no DB
func BalanceAccount(app *app.App) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()

// capturando id na url
id := mux.Vars(r)["id"]

// capturando account no DB
a := &models.Account{}
if err := app.DB.Client.First(&a, &id); err.Error != nil {
// caso tenha erro ao procurar no banco retorna 404
http.Error(w, "Conta não encontrada", http.StatusNotFound)
return
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]float64{"balance": a.Balance})

}
}
15 changes: 0 additions & 15 deletions cmd/banking/handlers/hello/hello.go

This file was deleted.

83 changes: 83 additions & 0 deletions cmd/banking/handlers/login/login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package login

import (
"encoding/json"
"fmt"
"net/http"
"time"

"github.com/dgrijalva/jwt-go"
"github.com/yohanalexander/desafio-banking-go/cmd/banking/models"
"github.com/yohanalexander/desafio-banking-go/pkg/app"
"github.com/yohanalexander/desafio-banking-go/pkg/secret"
)

// HandlerLogin handler para login na API e retorno do token JWT
func HandlerLogin(app *app.App) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()

// criando a chave JWT usada para verificar a assinatura
var jwtKey = []byte(app.Cfg.GetTokenKey())

// capturando as credenciais no request
creds := &models.Credentials{}
if err := json.NewDecoder(r.Body).Decode(&creds); err != nil {
// caso tenha erro no decode do request retorna 400
http.Error(w, "Formato JSON inválido", http.StatusBadRequest)
return
}

// validando json das credenciais
if err := app.Vld.Struct(creds); err != nil {
// traduzindo os erros do JSON inválido
errs := app.TranslateErrors(err)
// caso o corpo do request seja inválido retorna 400
w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, errs)
return
}

// capturando account no DB
a := &models.Account{}
if err := app.DB.Client.First(&a, "cpf = ?", creds.CPF); err.Error != nil {
// caso tenha erro ao procurar no banco retorna 401
http.Error(w, "Conta não encontrada", http.StatusUnauthorized)
return
}

// se a senha está incorreta
if !secret.CheckPasswordHash(creds.Secret, a.Secret) {
// caso tenha erro ao verificar o hash retorna 401
http.Error(w, "Senha incorreta", http.StatusUnauthorized)
return
}

// definindo o tempo de validade do token para 6 horas
expirationTime := time.Now().Add(6 * time.Hour)
// criando o JWT claims que contém o CPF e tempo de validade
claims := &models.Claims{
CPF: creds.CPF,
StandardClaims: jwt.StandardClaims{
// no JWT o tempo de validade é dado em milisegundos unix
ExpiresAt: expirationTime.Unix(),
},
}

// declarando o token com o algoritmo usado para login
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// criando a string do token JWT
tokenString, err := token.SignedString(jwtKey)
if err != nil {
// caso tenha erro ao criar o JWT retorna 500
http.Error(w, "Erro de autenticação", http.StatusInternalServerError)
return
}

// retorna o token em formato JSON
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"token": tokenString})

}
}
Loading

0 comments on commit d6c384c

Please sign in to comment.