Skip to content

Commit d6c384c

Browse files
feat: versão 1.0.0 da API banking (#2)
* 🎉 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
1 parent 001b9b0 commit d6c384c

File tree

17 files changed

+855
-86
lines changed

17 files changed

+855
-86
lines changed

.env

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
BUILD_TARGET="development"
2-
DEBUG_MODE="true"
2+
DEBUG_MODE="false"
3+
TOKEN_KEY="gophers"
34
SERVER_ADDRESS="8080"
45
POSTGRES_PASSWORD="root"
56
POSTGRES_USER="postgres"

README.md

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,113 @@
1-
# desafio-banking-go
2-
API Restful simulando um banco digital usando Golang
1+
# API Restful simulando um banco digital
2+
3+
## Sobre o projeto
4+
5+
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.
6+
7+
## Entidades da API
8+
9+
### A entidade `Account` possui os seguintes atributos:
10+
11+
* `id`
12+
* `name`
13+
* `cpf`
14+
* `secret`
15+
* `balance`
16+
* `created_at`
17+
18+
### A entidade `Login` possui os seguintes atributos:
19+
20+
* `cpf`
21+
* `secret`
22+
23+
### A entidade `Transfer` possui os seguintes atributos:
24+
25+
* `id`
26+
* `account_origin_id`
27+
* `account_destination_id`
28+
* `amount`
29+
* `created_at`
30+
31+
## Rotas da API
32+
33+
| Metódo | URL | Descrição | Autenticação |
34+
|--------|--------------------------------|--------------------------------------------------|--------------|
35+
| GET | /accounts | retorna a lista de contas no banco | Não |
36+
| GET | /accounts/{account_id}/balance | retorna o saldo da conta no banco | Não |
37+
| POST | /accounts | cria uma conta no banco | Não |
38+
| POST | /login | autentica a conta no banco e retorna o token JWT | Não |
39+
| GET | /transfers | retorna as transferências da conta no banco | Sim |
40+
| POST | /transfers | transfere de uma conta para outra no banco | Sim |
41+
42+
43+
## Documentação
44+
45+
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).
46+
47+
## Dependências
48+
49+
O projeto foi estruturado em containers e para o deploy são necessárias as dependências:
50+
51+
* [Docker](https://docs.docker.com/engine/install/)
52+
* [Docker-Compose](https://docs.docker.com/compose/install/)
53+
* [GNU Make](https://www.gnu.org/software/make/)
54+
55+
## Configurações da API
56+
57+
O projeto faz uso de variáveis de ambiente que são definidas no arquivo `.env`:
58+
59+
```
60+
BUILD_TARGET="development"
61+
DEBUG_MODE="false"
62+
TOKEN_KEY="gophers"
63+
SERVER_ADDRESS="8080"
64+
POSTGRES_PASSWORD="root"
65+
POSTGRES_USER="postgres"
66+
POSTGRES_PORT="5432"
67+
POSTGRES_HOST="db"
68+
POSTGRES_DB=""
69+
```
70+
71+
### Build target
72+
73+
É 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.
74+
75+
### Debug mode
76+
77+
É 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.
78+
79+
### Token key
80+
81+
É a chave `salt` usada para gerar a criptografia do token JWT.
82+
83+
### Server address
84+
85+
É a porta na qual o servidor será disponibilizado.
86+
87+
### Postgres
88+
89+
São as configurações para o DSN do banco de dados: usuário, senha, host, porta, database.
90+
91+
## Uso da API
92+
93+
Para iniciar a API localmente com as dependências já instaladas use o comando:
94+
95+
``` sh
96+
make run
97+
```
98+
99+
Para ver os logs da API use o comando:
100+
101+
``` sh
102+
make logs
103+
```
104+
105+
Para desligar a API use o comando:
106+
107+
``` sh
108+
make stop
109+
```
110+
111+
## Testando a API
112+
113+
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.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package account
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
8+
"github.com/gorilla/mux"
9+
"github.com/yohanalexander/desafio-banking-go/cmd/banking/models"
10+
"github.com/yohanalexander/desafio-banking-go/pkg/app"
11+
)
12+
13+
// ListAccounts handler para listar accounts no DB
14+
func ListAccounts(app *app.App) http.HandlerFunc {
15+
return func(w http.ResponseWriter, r *http.Request) {
16+
defer r.Body.Close()
17+
18+
// capturando accounts no DB
19+
var a []models.Account
20+
if err := app.DB.Client.Find(&a); err.Error != nil {
21+
// caso tenha erro ao procurar no banco retorna 500
22+
http.Error(w, "Erro na listagem das contas", http.StatusInternalServerError)
23+
return
24+
}
25+
26+
w.Header().Set("Content-Type", "application/json")
27+
w.WriteHeader(http.StatusOK)
28+
json.NewEncoder(w).Encode(a)
29+
30+
}
31+
}
32+
33+
// PostAccount handler para criar account no DB
34+
func PostAccount(app *app.App) http.HandlerFunc {
35+
return func(w http.ResponseWriter, r *http.Request) {
36+
defer r.Body.Close()
37+
38+
// capturando account no request
39+
a := &models.Account{}
40+
if err := json.NewDecoder(r.Body).Decode(&a); err != nil {
41+
// caso tenha erro no decode do request retorna 400
42+
http.Error(w, "Formato JSON inválido", http.StatusBadRequest)
43+
return
44+
}
45+
46+
// validando json do struct account
47+
if err := app.Vld.Struct(a); err != nil {
48+
// traduzindo os erros do JSON inválido
49+
errs := app.TranslateErrors(err)
50+
// caso o corpo do request seja inválido retorna 400
51+
w.WriteHeader(http.StatusBadRequest)
52+
fmt.Fprint(w, errs)
53+
return
54+
}
55+
56+
// armazenando struct account no DB
57+
account, err := a.CreateAccount(app)
58+
if err != nil {
59+
// caso tenha erro ao armazenar no banco retorna 500
60+
http.Error(w, err.Error(), http.StatusInternalServerError)
61+
return
62+
}
63+
64+
w.Header().Set("Content-Type", "application/json")
65+
w.WriteHeader(http.StatusCreated)
66+
json.NewEncoder(w).Encode(account)
67+
68+
}
69+
}
70+
71+
// BalanceAccount handler para retornar o saldo da account no DB
72+
func BalanceAccount(app *app.App) http.HandlerFunc {
73+
return func(w http.ResponseWriter, r *http.Request) {
74+
defer r.Body.Close()
75+
76+
// capturando id na url
77+
id := mux.Vars(r)["id"]
78+
79+
// capturando account no DB
80+
a := &models.Account{}
81+
if err := app.DB.Client.First(&a, &id); err.Error != nil {
82+
// caso tenha erro ao procurar no banco retorna 404
83+
http.Error(w, "Conta não encontrada", http.StatusNotFound)
84+
return
85+
}
86+
87+
w.Header().Set("Content-Type", "application/json")
88+
w.WriteHeader(http.StatusOK)
89+
json.NewEncoder(w).Encode(map[string]float64{"balance": a.Balance})
90+
91+
}
92+
}

cmd/banking/handlers/hello/hello.go

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package login
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"time"
8+
9+
"github.com/dgrijalva/jwt-go"
10+
"github.com/yohanalexander/desafio-banking-go/cmd/banking/models"
11+
"github.com/yohanalexander/desafio-banking-go/pkg/app"
12+
"github.com/yohanalexander/desafio-banking-go/pkg/secret"
13+
)
14+
15+
// HandlerLogin handler para login na API e retorno do token JWT
16+
func HandlerLogin(app *app.App) http.HandlerFunc {
17+
return func(w http.ResponseWriter, r *http.Request) {
18+
defer r.Body.Close()
19+
20+
// criando a chave JWT usada para verificar a assinatura
21+
var jwtKey = []byte(app.Cfg.GetTokenKey())
22+
23+
// capturando as credenciais no request
24+
creds := &models.Credentials{}
25+
if err := json.NewDecoder(r.Body).Decode(&creds); err != nil {
26+
// caso tenha erro no decode do request retorna 400
27+
http.Error(w, "Formato JSON inválido", http.StatusBadRequest)
28+
return
29+
}
30+
31+
// validando json das credenciais
32+
if err := app.Vld.Struct(creds); err != nil {
33+
// traduzindo os erros do JSON inválido
34+
errs := app.TranslateErrors(err)
35+
// caso o corpo do request seja inválido retorna 400
36+
w.WriteHeader(http.StatusBadRequest)
37+
fmt.Fprint(w, errs)
38+
return
39+
}
40+
41+
// capturando account no DB
42+
a := &models.Account{}
43+
if err := app.DB.Client.First(&a, "cpf = ?", creds.CPF); err.Error != nil {
44+
// caso tenha erro ao procurar no banco retorna 401
45+
http.Error(w, "Conta não encontrada", http.StatusUnauthorized)
46+
return
47+
}
48+
49+
// se a senha está incorreta
50+
if !secret.CheckPasswordHash(creds.Secret, a.Secret) {
51+
// caso tenha erro ao verificar o hash retorna 401
52+
http.Error(w, "Senha incorreta", http.StatusUnauthorized)
53+
return
54+
}
55+
56+
// definindo o tempo de validade do token para 6 horas
57+
expirationTime := time.Now().Add(6 * time.Hour)
58+
// criando o JWT claims que contém o CPF e tempo de validade
59+
claims := &models.Claims{
60+
CPF: creds.CPF,
61+
StandardClaims: jwt.StandardClaims{
62+
// no JWT o tempo de validade é dado em milisegundos unix
63+
ExpiresAt: expirationTime.Unix(),
64+
},
65+
}
66+
67+
// declarando o token com o algoritmo usado para login
68+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
69+
// criando a string do token JWT
70+
tokenString, err := token.SignedString(jwtKey)
71+
if err != nil {
72+
// caso tenha erro ao criar o JWT retorna 500
73+
http.Error(w, "Erro de autenticação", http.StatusInternalServerError)
74+
return
75+
}
76+
77+
// retorna o token em formato JSON
78+
w.Header().Set("Content-Type", "application/json")
79+
w.WriteHeader(http.StatusOK)
80+
json.NewEncoder(w).Encode(map[string]string{"token": tokenString})
81+
82+
}
83+
}

0 commit comments

Comments
 (0)