-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
1 parent
001b9b0
commit d6c384c
Showing
17 changed files
with
855 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}) | ||
|
||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}) | ||
|
||
} | ||
} |
Oops, something went wrong.