From b258cc55e568fdb27ba274eb05536d1f4397004e Mon Sep 17 00:00:00 2001 From: guiTonel Date: Tue, 23 Mar 2021 00:08:56 -0300 Subject: [PATCH 1/3] Finalizado Projeto Desafio-backend Projeto Finalizado --- .dockerignore | 1 + .gitignore | 2 + Dockerfile | 7 ++ README.md | 143 +++++++++++++------------ app/.vscode/settings.json | 3 + app/__main__.py | 20 ++++ app/auth/__init__.py | 1 + app/auth/user.py | 43 ++++++++ app/controller/__init__.py | 0 app/controller/simulacao_emprestimo.py | 19 ++++ app/controller/taxa.py | 29 +++++ app/controller/user.py | 29 +++++ app/models/__init__.py | 4 + app/models/emprestimo.py | 13 +++ app/models/taxa.py | 12 +++ app/models/tipo_taxa.py | 15 +++ app/models/user.py | 30 ++++++ app/repository/__init__.py | 5 + app/repository/taxa.py | 16 +++ app/repository/tipo_taxa.py | 9 ++ app/repository/user.py | 17 +++ app/routes/__init__.py | 10 ++ app/routes/simulacao_emprestimo.py | 11 ++ app/routes/taxa.py | 15 +++ app/routes/user.py | 22 ++++ app/service/__init__.py | 0 app/service/emprestimo.py | 19 ++++ app/service/taxa.py | 30 ++++++ app/service/tipo_taxa.py | 11 ++ app/service/user.py | 53 +++++++++ app/utils/__init__.py | 1 + app/utils/taxa.py | 6 ++ app/utils/user.py | 6 ++ clientes.json => dados/clientes.json | 0 taxas.json => dados/taxas.json | 26 ++--- docker-compose.yml | 18 ++++ requirements.txt | 4 + 37 files changed, 566 insertions(+), 84 deletions(-) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 app/.vscode/settings.json create mode 100644 app/__main__.py create mode 100644 app/auth/__init__.py create mode 100644 app/auth/user.py create mode 100644 app/controller/__init__.py create mode 100644 app/controller/simulacao_emprestimo.py create mode 100644 app/controller/taxa.py create mode 100644 app/controller/user.py create mode 100644 app/models/__init__.py create mode 100644 app/models/emprestimo.py create mode 100644 app/models/taxa.py create mode 100644 app/models/tipo_taxa.py create mode 100644 app/models/user.py create mode 100644 app/repository/__init__.py create mode 100644 app/repository/taxa.py create mode 100644 app/repository/tipo_taxa.py create mode 100644 app/repository/user.py create mode 100644 app/routes/__init__.py create mode 100644 app/routes/simulacao_emprestimo.py create mode 100644 app/routes/taxa.py create mode 100644 app/routes/user.py create mode 100644 app/service/__init__.py create mode 100644 app/service/emprestimo.py create mode 100644 app/service/taxa.py create mode 100644 app/service/tipo_taxa.py create mode 100644 app/service/user.py create mode 100644 app/utils/__init__.py create mode 100644 app/utils/taxa.py create mode 100644 app/utils/user.py rename clientes.json => dados/clientes.json (100%) rename taxas.json => dados/taxas.json (95%) create mode 100644 docker-compose.yml create mode 100644 requirements.txt diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..db1828e --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +credito_express \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3cc4618 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.credito_express +__pycache__ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..23866b5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM tiangolo/uwsgi-nginx-flask:python3.6-alpine3.7 +RUN apk --update --no-cache add bash nano python3-dev && pip3 install --upgrade pip +WORKDIR /app +COPY . /app +RUN pip install -r requirements.txt +EXPOSE 3333 +CMD python3 app \ No newline at end of file diff --git a/README.md b/README.md index d76c683..7aae612 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,72 @@ -## Sobre a Crédito Express - -A Crédito Express é uma fintech voltada para servir instituições financeiras. Nosso objetivo é levar TAXAS ATRATIVAS para as pessoas, a partir do uso de tecnologia de ponta. - -VENHA FAZER PARTE DESSA REVOLUÇÃO FINANCEIRA! - - -## Sobre o desafio - -Com o aumento dos casos de COVID-19 muitos cidadãos tiveram aumentos nos gastos e redução nos ganhos, levando a eles endividarem-se em meios que as taxas de juros são extremamente altas. Neste desafio você deve construir uma aplicação para pessoas possam simular empréstimos com as melhores taxas do mercado financeiro. - -A aplicação deve ter uma API para o cliente informar o seu CPF e numero de celular para se identificar e depois disso ele poderá efetuar simulações de empréstimo em outra API. Para simular é necessário informar o valor do empréstimo e o número de parcelas, sendo que podem ser 6, 12, 18, 24 ou 36. - -Existe uma tabela de taxas, o calculo do empréstimo deve ser feito de acordo com ela. Existem 3 tipos diferentes de taxas: **negativado, score alto e score baixo**. Pessoas com score acima de 500 são consideradas com score alto nessa aplicação, pessoas sem cadastro na base recebem score 0. - -Após obter o valor da taxa a ser aplicada para esse cliente, será necessário chamar uma API para fazer o cálculo da simulação. Os dados retornados pelo cálculo devem ser o retorno da sua API. - - -## Considerações - -- O arquivo taxas.json possui uma coleção de taxas por característica. Os dados seguem o formato do exemplo abaixo, mas pode modificar a estrutura no seu projeto se precisar: - -```javascript - { - "tipo": "NEGATIVADO", - "taxas": {"6": 0.04, "12": 0.045, "18": 0.05, "24": 0.053, "36": 0.055} - } -``` -- O arquivo clientes.json possui uma coleção de objetos que representam os clientes que já tem pré-cadastro. Abaixo temos um exemplo do formato do objeto que também pode ter a estrutura modificada caso julgue necessário. - -```javascript -{ - "nome": "Roberto Filipe Figueiredo", - "cpf": "41882728564", - "celular": "6526332774", - "score": 300, - "negativado": false -} -``` - -- Abaixo temos um exemplo de um CURL para API de cálculo. - -```shell -curl --request POST \ - --url https://us-central1-creditoexpress-dev.cloudfunctions.net/teste-backend \ - --header 'Content-Type: application/json' \ - --data '{ - "numeroParcelas": 12, - "valor": 10000, - "taxaJuros": 0.04 -}' -``` - -### Pré-requisitos -- Desenvolvimento de API REST em Python; -- Utilização do MongoDB; -- Desenvolvimento de um Dockerfile/Docker-Compose.yml para rodar o projeto; -- Documentar como rodamos o projeto no README.MD; - -### Diferenciais/Extras -- Implementação de Testes de unidade e/ou integração; -- Clean code; -- Segurança e resiliência; -- Utilização de padrões de projeto; -- Migrations e/ou seeders; -- Script para execução da aplicação; - -## Pronto para começar o desafio? - -- Faça um "fork" deste repositório na sua conta do Github; -- Após completar o desafio, crie um pull request nesse repositório comparando a sua branch com a master com o seu nome no título; \ No newline at end of file +## Sobre a solução + +Solução desenvolvida em python com Flask e mongodb. Aplicando conceitos SOLID e boas praticas de desenvolvimento para manter uma boa estrutura e um codigo limpo. +Além disso também aplicado alguns conceitos como Decorators, upload de arquivos, docker e ORM. + +## Como Rodar + +Para rodar subir a aplicação execute o seguinte comando na pasta raiz do projeto: +```shell +docker-compose up -d +``` +Desta forma, estará rodando os container resposáveis pela API e pelo banco de dados da aplicação. +Obs: o banco de dados estará vazio e precisa ser preenchido. + +# Utilizando +## Preenchendo o banco +- Primeiramente é necessario preencher o banco de dados por meio dos arquivos fornecidos na pasta dados. +Para isso há duas rotas, uma para as taxas e outra para os usuarios. +### Usuarios +- Rota: [POST] http://0.0.0.0:3333/user/insert-file +- Inserir como corpo da requisição o arquivo "clientes.json" com o nome da key como "file" + +### Taxas +- Rota: [POST] http://0.0.0.0:3333/taxa/insert-file +- Inserir como corpo da requisição o arquivo "taxas.json" com o nome da key como "file" + +## Plano B +- Caso não obtenha sucesso em preencher o banco com os arquivos, também é possivel preenche-los informando o array de JSON manualmente no BODY da request. Da seguinte forma: +### Usuarios +- Rota: [POST] http://0.0.0.0:3333/user/insert +- Inserir como corpo da requisição o conteudo do arquivo "clientes.json". + +### Taxas +- Rota: [POST] http://0.0.0.0:3333/taxa/insert +- Inserir como corpo da requisição o conteudo do arquivo "taxas.json". + +## Rotas +- Algumas rotas possivéis da API + +### Login +- Rota: [GET] http://0.0.0.0:3333/user/login +- Corpo: Informar no corpo da requisição um JSON contendo o **cpf** e o **celular** de algum usuario cadastrado anteriormente. Ex: +```javascript +{ + "cpf":"42935087160", + "celular":"41996268329" +} +``` +- Retorno: Retornará um Token JWT possibilitando a simulação de emprestimo. +- **Obs:** Caso não haja usuario cadastrado com esses dados será retornado Um Token JWT correspondente a um Usuario Visitante. + +### Simular Emprestimo +- Rota: [GET] http://0.0.0.0:3333/simulacao_emprestimo/simular +- Header: Deve conter um Header **Authorization** contendo o Token JWT retornado na request de Login. +- Corpo: Informar no corpo da requisição um JSON contendo o valores de **valor** e o **numeroParcelas** que serão usados pra simular o empréstimo. Ex: +```javascript +{ + "valor": 10000, + "numeroParcelas": 12 +} +``` +- Retorno: Retornará um JSON contendo o resultado da simulaçao do Emprestimo. Ex: +```javascript +{ + "numeroParcelas": 12, + "outrasTaxas": 85, + "total": 10335.0, + "valorJuros": 250.0, + "valorParcela": 861.25, + "valorSolicitado": 10000 +} +``` \ No newline at end of file diff --git a/app/.vscode/settings.json b/app/.vscode/settings.json new file mode 100644 index 0000000..f36ca5d --- /dev/null +++ b/app/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.pythonPath": "c:\\Users\\Guilherme\\Documents\\Projects\\flask\\api_credito_express\\credito_express\\Scripts\\python.exe" +} \ No newline at end of file diff --git a/app/__main__.py b/app/__main__.py new file mode 100644 index 0000000..55d5f07 --- /dev/null +++ b/app/__main__.py @@ -0,0 +1,20 @@ +import logging +logging.getLogger().setLevel( logging.INFO ) + +from flask import Flask + +def init_app( app ): + #Database + from repository import init_db + init_db() + + #Rotas + from routes import init_routes + init_routes( app ) + + app.run( host = "0.0.0.0", port = 3333 ) + + +if __name__ == '__main__': + app = Flask(__name__) + init_app( app ) diff --git a/app/auth/__init__.py b/app/auth/__init__.py new file mode 100644 index 0000000..818c3c1 --- /dev/null +++ b/app/auth/__init__.py @@ -0,0 +1 @@ +AUTH_HEADER_NAME = 'Authorization' \ No newline at end of file diff --git a/app/auth/user.py b/app/auth/user.py new file mode 100644 index 0000000..433d20d --- /dev/null +++ b/app/auth/user.py @@ -0,0 +1,43 @@ +import jwt +import traceback + +from time import time +from flask import request + +from auth import AUTH_HEADER_NAME + +SECRET_KEY = 'chave-secreta-do-usuario' +ALGORITHM = 'HS256' + +def generate_user_token( func ): + def a( *args, **kwargs ): + user_id = func( *args, **kwargs ) + + if type(user_id) == tuple: + return user_id + + payload = { + 'uid': user_id, + 'exp': int(time()) + 3600 + } + + return "Bearer {}".format( jwt.encode( payload, SECRET_KEY, algorithm = ALGORITHM ).decode( 'utf-8' ) ) + + return a + +def validate_user_token_and_get_id( func ): + def a( *args, **kwargs ): + try: + user_token = request.headers.get( AUTH_HEADER_NAME ) + user_token = user_token.split()[-1] + user_id = jwt.decode( user_token, SECRET_KEY, algorithms = [ ALGORITHM ] ) + + user_id = user_id[ 'uid' ] + + return func( user_id, *args, **kwargs ) + + except Exception as e: + traceback.print_exc() + return "Authentication fail", 500 + return a + diff --git a/app/controller/__init__.py b/app/controller/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/controller/simulacao_emprestimo.py b/app/controller/simulacao_emprestimo.py new file mode 100644 index 0000000..bc48ca5 --- /dev/null +++ b/app/controller/simulacao_emprestimo.py @@ -0,0 +1,19 @@ +import traceback + +from models.emprestimo import Emprestimo, NUM_PARCELAS, VAL_EMPRESTIMO +from service import user as user_service, tipo_taxa as tipo_taxa_service, taxa as taxa_service, emprestimo as emprestimo_service + +def simular_emprestimo( user_id, emprestimo ): + try: + emprestimo = Emprestimo( user_id, emprestimo[ VAL_EMPRESTIMO ], emprestimo[ NUM_PARCELAS ] ) + + user_tipo_taxa = user_service.get_tipo_taxa_user_by_user_id( user_id ) + taxa_juros = taxa_service.get_val_juros_by_tipo_taxa_and_parcelas( user_tipo_taxa, emprestimo.num_parcelas ) + + response = emprestimo_service.calular_emprestimo_request( emprestimo, taxa_juros ) + + return response + except Exception as e: + traceback.print_exc() + + return "Error ao simular emprestimo", 505 diff --git a/app/controller/taxa.py b/app/controller/taxa.py new file mode 100644 index 0000000..6658201 --- /dev/null +++ b/app/controller/taxa.py @@ -0,0 +1,29 @@ +import traceback, logging + +from service import taxa as taxa_service, tipo_taxa as tipo_taxa_service + +from models.tipo_taxa import JSON_TIPO, JSON_TAXAS +from models.taxa import ID_TIPO_TAXA + +def insert_taxas( taxas ): + try: + for taxa in taxas: + tipo_taxa = taxa[ JSON_TIPO ] + + id_tipo_taxa = tipo_taxa_service.find_tipo_taxa( tipo_taxa ) + if id_tipo_taxa is None: + id_tipo_taxa = tipo_taxa_service.insert_tipo_taxa( tipo_taxa ).id + else: + id_tipo_taxa = id_tipo_taxa.id + + taxas_list = taxa_service.build_taxas_list( taxa[ JSON_TAXAS ], id_tipo_taxa ) + + for taxa in taxas_list: + taxa_service.insert_taxa( taxa ) + + except Exception as e: + logging.error( traceback.format_exc() ) + return "Falha ao inserir taxas", 500 + + else: + return "Inserção de taxas efetuada com sucesso", 200 diff --git a/app/controller/user.py b/app/controller/user.py new file mode 100644 index 0000000..eb7c10c --- /dev/null +++ b/app/controller/user.py @@ -0,0 +1,29 @@ +import json, logging + +from service import user as user_service + +def insert_users( users ): + response = None + try: + response = user_service.insert_users( users ) + + except Exception as e: + logging.error( e ) + return "Falha ao inserir usuarios", 500 + + else: + return json.dumps( response ) + +def get_user_id_by_cpf_and_cellphone( user ): + try: + user = user_service.find_user_by_cpf_and_cellphone( user['cpf'], user['celular'] ) + if user: + return str( user.id ) + else: + logging.info( "Usuario visitante" ) + return 0 + + except Exception as e: + logging.error( e ) + return "Falha ao buscar usuario", 500 + \ No newline at end of file diff --git a/app/models/__init__.py b/app/models/__init__.py new file mode 100644 index 0000000..41573a4 --- /dev/null +++ b/app/models/__init__.py @@ -0,0 +1,4 @@ +from .user import User +from .taxa import Taxa +from .tipo_taxa import Tipo_taxa +from .emprestimo import Emprestimo \ No newline at end of file diff --git a/app/models/emprestimo.py b/app/models/emprestimo.py new file mode 100644 index 0000000..ed06c26 --- /dev/null +++ b/app/models/emprestimo.py @@ -0,0 +1,13 @@ +class Emprestimo(): + id_usuario = int() + val_emprestimo = float() + numParcelas = int() + + def __init__( self, id_usuario, val_emprestimo, num_parcelas ): + self.id_usuario = id_usuario + self.val_emprestimo = val_emprestimo + self.num_parcelas = num_parcelas + +#ATRIBUTOS JSON +NUM_PARCELAS = 'numeroParcelas' +VAL_EMPRESTIMO = 'valor' \ No newline at end of file diff --git a/app/models/taxa.py b/app/models/taxa.py new file mode 100644 index 0000000..eb5da60 --- /dev/null +++ b/app/models/taxa.py @@ -0,0 +1,12 @@ +from mongoengine import Document, IntField, FloatField, ReferenceField, CASCADE + +from .tipo_taxa import Tipo_taxa + +class Taxa( Document ): + num_parcelas = IntField( required = True, max_value = 36, min_value = 6 ) + juros = FloatField( required = True, max_value = 1, min_value = 0 ) + id_tipo_taxa = ReferenceField( Tipo_taxa, reverse_delete_rule = CASCADE ) + +NUM_PARCELAS = 'num_parcelas' +JUROS = 'juros' +ID_TIPO_TAXA = 'id_tipo_taxa' \ No newline at end of file diff --git a/app/models/tipo_taxa.py b/app/models/tipo_taxa.py new file mode 100644 index 0000000..22fe702 --- /dev/null +++ b/app/models/tipo_taxa.py @@ -0,0 +1,15 @@ +from mongoengine import Document, StringField + +class Tipo_taxa( Document ): + descr = StringField( required = True, unique = True ) + +#ATRIBUTOS JSON +JSON_TIPO = "tipo" +JSON_TAXAS = "taxas" + +#TIPOS TAXA +class TipoTaxaEnum(): + NEGATIVADO = 'NEGATIVADO' + SCORE_ALTO = 'SCORE_ALTO' + SCORE_BAIXO = 'SCORE_BAIXO' + VALOR_SCORE_ALTO = 500 diff --git a/app/models/user.py b/app/models/user.py new file mode 100644 index 0000000..c3dd97c --- /dev/null +++ b/app/models/user.py @@ -0,0 +1,30 @@ +from mongoengine import Document, StringField, BooleanField, IntField + +class User( Document ): + nome = StringField( required = True ) + cpf = StringField( required = True, max_length = 11 ) + celular = StringField( required = True, max_length = 11 ) + score = IntField( required = True ) + negativado = BooleanField( required = True, default = False) + + def __str__( self ): + return """Nome: {nome}, + CPF: {cpf}, + Celular: {celular}, + Score: {score}, + Negativado: {negativado} + """.format( nome=self.nome, + cpf=self.cpf, + celular=self.celular, + score=self.score, + negativado=self.negativado ) + +#Atributos +NOME = 'nome' +CPF = 'cpf' +CELULAR = 'celular' +SCORE = 'score' +NEGATIVADO = 'negativado' + +#ENUM TIPO USUARIO +VISITANTE = 0 \ No newline at end of file diff --git a/app/repository/__init__.py b/app/repository/__init__.py new file mode 100644 index 0000000..7e97e09 --- /dev/null +++ b/app/repository/__init__.py @@ -0,0 +1,5 @@ +from mongoengine import connect + +def init_db(): + connect( host = 'mongodb://mymongo:27017/credito_express' ) + \ No newline at end of file diff --git a/app/repository/taxa.py b/app/repository/taxa.py new file mode 100644 index 0000000..4156c36 --- /dev/null +++ b/app/repository/taxa.py @@ -0,0 +1,16 @@ +import json + +from models.taxa import Taxa, NUM_PARCELAS, JUROS, ID_TIPO_TAXA +from utils.taxa import return_taxa_json + +@return_taxa_json +def insert_taxa( taxa_dict ): + taxa = Taxa( num_parcelas = taxa_dict[ NUM_PARCELAS ], + juros = taxa_dict[ JUROS ], + id_tipo_taxa = taxa_dict[ ID_TIPO_TAXA ] ) + taxa.save() + return taxa + +def find_taxa_by_num_parcelas_and_id_tipo_taxa( tipo_taxa, parcelas ): + taxa = Taxa.objects.get( num_parcelas = parcelas, id_tipo_taxa = tipo_taxa ) + return taxa \ No newline at end of file diff --git a/app/repository/tipo_taxa.py b/app/repository/tipo_taxa.py new file mode 100644 index 0000000..ba1a3c3 --- /dev/null +++ b/app/repository/tipo_taxa.py @@ -0,0 +1,9 @@ +from models import Tipo_taxa + +def insert_tipo_taxa( descr_taxa ): + tipo_taxa = Tipo_taxa( descr = descr_taxa ) + tipo_taxa.save() + return tipo_taxa + +def find_tipo_taxa( descr_taxa ): + return Tipo_taxa.objects( descr = descr_taxa ).first() \ No newline at end of file diff --git a/app/repository/user.py b/app/repository/user.py new file mode 100644 index 0000000..cd8b70b --- /dev/null +++ b/app/repository/user.py @@ -0,0 +1,17 @@ +import json + +from models import User +from utils import return_user_json + +@return_user_json +def insert_user( user ): + user = User.from_json( json.dumps( user ) ) + user.save() + return user + +def find_user_by_cpf_and_cellphone( user_cpf, cellphone ): + return User.objects( cpf = user_cpf, celular = cellphone ).first() + +def find_user_by_id( user_id ): + user = User.objects.get( id = user_id ) + return user \ No newline at end of file diff --git a/app/routes/__init__.py b/app/routes/__init__.py new file mode 100644 index 0000000..4863a32 --- /dev/null +++ b/app/routes/__init__.py @@ -0,0 +1,10 @@ +from .user import user_routes +from .taxa import taxa_routes +from .simulacao_emprestimo import simulacao_emprestimo_routes + +def init_routes( app ): + app.register_blueprint( user_routes ) + app.register_blueprint( taxa_routes ) + app.register_blueprint( simulacao_emprestimo_routes ) + + return app \ No newline at end of file diff --git a/app/routes/simulacao_emprestimo.py b/app/routes/simulacao_emprestimo.py new file mode 100644 index 0000000..d0952b0 --- /dev/null +++ b/app/routes/simulacao_emprestimo.py @@ -0,0 +1,11 @@ +from flask import Blueprint, request + +from auth.user import validate_user_token_and_get_id +from controller import simulacao_emprestimo as simulacao_emprestimo_controller + +simulacao_emprestimo_routes = Blueprint( 'simulacao_emprestimo', __name__, url_prefix = '/simulacao_emprestimo' ) + +@simulacao_emprestimo_routes.route( '/simular', methods = ['GET'] ) +@validate_user_token_and_get_id +def simular_emprestimo( id_usuario ): + return simulacao_emprestimo_controller.simular_emprestimo( id_usuario, request.get_json() ) \ No newline at end of file diff --git a/app/routes/taxa.py b/app/routes/taxa.py new file mode 100644 index 0000000..6dc18a7 --- /dev/null +++ b/app/routes/taxa.py @@ -0,0 +1,15 @@ +from flask import Blueprint, request + +import json +from controller import taxa as taxa_controller + +taxa_routes = Blueprint( 'taxa', __name__, url_prefix = '/taxa' ) + +@taxa_routes.route( '/insert', methods = ['POST'] ) +def insert_taxas(): + return taxa_controller.insert_taxas( request.get_json() ) + +@taxa_routes.route( '/insert-file', methods = ['POST'] ) +def insert_taxas_by_file(): + file = json.load( request.files[ 'file' ] ) + return taxa_controller.insert_taxas( file ) \ No newline at end of file diff --git a/app/routes/user.py b/app/routes/user.py new file mode 100644 index 0000000..0d37d60 --- /dev/null +++ b/app/routes/user.py @@ -0,0 +1,22 @@ +from flask import Blueprint, request + +import json + +from controller import user as user_controller +from auth import user as user_auth + +user_routes = Blueprint( 'user', __name__, url_prefix = '/user' ) + +@user_routes.route( '/insert', methods = ['POST'] ) +def insert_users(): + return user_controller.insert_users( request.get_json() ) + +@user_routes.route( '/insert-file', methods = ['POST'] ) +def insert_users_by_file(): + file = json.load( request.files[ 'file' ] ) + return user_controller.insert_users( file ) + +@user_routes.route( '/login', methods = ['GET'] ) +@user_auth.generate_user_token +def login(): + return user_controller.get_user_id_by_cpf_and_cellphone( request.get_json() ) diff --git a/app/service/__init__.py b/app/service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/service/emprestimo.py b/app/service/emprestimo.py new file mode 100644 index 0000000..c2c7025 --- /dev/null +++ b/app/service/emprestimo.py @@ -0,0 +1,19 @@ +import requests, json, logging + +def calular_emprestimo_request( emprestimo, taxa_juros ): + request_data = { + "numeroParcelas": emprestimo.num_parcelas, + "valor": emprestimo.val_emprestimo, + "taxaJuros": taxa_juros + } + + logging.info( "[REQUEST_BODY] << {}".format( request_data ) ) + + url = "https://us-central1-creditoexpress-dev.cloudfunctions.net/teste-backend" + headers = { + 'Content-Type': 'application/json' + } + + response = requests.request("POST", url, headers = headers, data = json.dumps( request_data ) ) + + return response.json() \ No newline at end of file diff --git a/app/service/taxa.py b/app/service/taxa.py new file mode 100644 index 0000000..818d2b0 --- /dev/null +++ b/app/service/taxa.py @@ -0,0 +1,30 @@ +from repository import taxa as taxa_repository + +from service import tipo_taxa as tipo_taxa_service +from models.taxa import ID_TIPO_TAXA, JUROS, NUM_PARCELAS + +def insert_taxa( taxa ): + return taxa_repository.insert_taxa( taxa ) + +def build_taxas_list( taxas: dict, id_tipo_taxa ): + taxas_list = list() + for num_parcelas, juros in zip( taxas.keys(), taxas.values() ): + taxa = build_taxa_dict( num_parcelas, juros, id_tipo_taxa ) + taxas_list.append( taxa ) + + return taxas_list + +def build_taxa_dict( num_parcelas, juros, id_tipo_taxa ): + taxa = dict() + taxa[ NUM_PARCELAS ] = int( num_parcelas ) + taxa[ JUROS ] = float( juros ) + taxa[ ID_TIPO_TAXA ] = id_tipo_taxa + + return taxa + +def get_val_juros_by_tipo_taxa_and_parcelas( tipo_taxa, num_parcelas ): + id_tipo_taxa = tipo_taxa_service.get_id_tipo_taxa( tipo_taxa ) + taxa = taxa_repository.find_taxa_by_num_parcelas_and_id_tipo_taxa( id_tipo_taxa, num_parcelas ) + + return taxa.juros + diff --git a/app/service/tipo_taxa.py b/app/service/tipo_taxa.py new file mode 100644 index 0000000..6998c4a --- /dev/null +++ b/app/service/tipo_taxa.py @@ -0,0 +1,11 @@ +from repository import tipo_taxa as tipo_taxa_repository + +def insert_tipo_taxa( tipo_taxa ): + return tipo_taxa_repository.insert_tipo_taxa( tipo_taxa ) + +def find_tipo_taxa( tipo_taxa ): + return tipo_taxa_repository.find_tipo_taxa( tipo_taxa ) + +def get_id_tipo_taxa( tipo_taxa ): + tipo_taxa = find_tipo_taxa( tipo_taxa ) + return tipo_taxa.id \ No newline at end of file diff --git a/app/service/user.py b/app/service/user.py new file mode 100644 index 0000000..759530b --- /dev/null +++ b/app/service/user.py @@ -0,0 +1,53 @@ +import logging + +from repository import user as user_repository + +from models.user import * +from models.tipo_taxa import TipoTaxaEnum + +def insert_users( users ): + users_response = list() + + for user in users: + try: + user = refactor_user_atributes( user ) + + user_response = user_repository.insert_user( user ) + users_response.append( user_response ) + + except Exception as e: + raise Exception( "[ERROR] << {}\n[USER] << {}".format( e, user ) ) + + return users_response + +def find_user_by_cpf_and_cellphone( cpf, cellphone ): + user = user_repository.find_user_by_cpf_and_cellphone( cpf, cellphone ) + logging.info( "USER << {}".format( user if user else "Not Found" ) ) + return user + +def refactor_user_atributes( user ): + user[ NOME ] = str( user[ NOME ] ) + user[ CPF ] = str( user[ CPF ] ) + user[ CELULAR ] = str( user[ CELULAR ] ) + user[ SCORE ] = int( user[ SCORE ] ) + user[ NEGATIVADO ] = bool( user[ NEGATIVADO ] ) + return user + +def get_tipo_taxa_user_by_user_id( user_id ): + if user_id == VISITANTE: + return validate_tipo_taxa( 0, False ) + + user = user_repository.find_user_by_id( user_id ) + + tipo_taxa = validate_tipo_taxa( user.score, user.negativado ) + + return tipo_taxa + +def validate_tipo_taxa( score, negativado ): + if negativado: + return TipoTaxaEnum.NEGATIVADO + + if score > TipoTaxaEnum.VALOR_SCORE_ALTO: + return TipoTaxaEnum.SCORE_ALTO + else: + return TipoTaxaEnum.SCORE_BAIXO \ No newline at end of file diff --git a/app/utils/__init__.py b/app/utils/__init__.py new file mode 100644 index 0000000..3f28d1e --- /dev/null +++ b/app/utils/__init__.py @@ -0,0 +1 @@ +from .user import * \ No newline at end of file diff --git a/app/utils/taxa.py b/app/utils/taxa.py new file mode 100644 index 0000000..edd0316 --- /dev/null +++ b/app/utils/taxa.py @@ -0,0 +1,6 @@ +from models import Taxa + +def return_taxa_json( func ): + def a( *args, **kwargs ): + return Taxa.to_json( func( *args, **kwargs ) ) + return a \ No newline at end of file diff --git a/app/utils/user.py b/app/utils/user.py new file mode 100644 index 0000000..54e7699 --- /dev/null +++ b/app/utils/user.py @@ -0,0 +1,6 @@ +from models import User + +def return_user_json( func ): + def a( *args, **kwargs ): + return User.to_json( func( *args, **kwargs ) ) + return a \ No newline at end of file diff --git a/clientes.json b/dados/clientes.json similarity index 100% rename from clientes.json rename to dados/clientes.json diff --git a/taxas.json b/dados/taxas.json similarity index 95% rename from taxas.json rename to dados/taxas.json index d84abd6..4c34417 100644 --- a/taxas.json +++ b/dados/taxas.json @@ -1,14 +1,14 @@ -[ - { - "tipo":"NEGATIVADO", - "taxas":{"6": 0.04, "12": 0.045, "18": 0.05, "24": 0.053, "36": 0.055} - }, - { - "tipo":"SCORE_ALTO", - "taxas":{"6": 0.02, "12": 0.025, "18": 0.35, "24": 0.038, "36": 0.04} - }, - { - "tipo":"SCORE_BAIXO", - "taxas":{"6": 0.03, "12": 0.035, "18": 0.45, "24": 0.048, "36": 0.05} - } +[ + { + "tipo":"NEGATIVADO", + "taxas":{"6": 0.04, "12": 0.045, "18": 0.05, "24": 0.053, "36": 0.055} + }, + { + "tipo":"SCORE_ALTO", + "taxas":{"6": 0.02, "12": 0.025, "18": 0.35, "24": 0.038, "36": 0.04} + }, + { + "tipo":"SCORE_BAIXO", + "taxas":{"6": 0.03, "12": 0.035, "18": 0.45, "24": 0.048, "36": 0.05} + } ] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..561d53b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: "3.7" +services: + db: + image: mongo + container_name: mymongo + volumes: + - ./app/db + restart: always + api: + build: "./" + container_name: api + restart: always + volumes: + - ./app + ports: + - "3333:3333" + depends_on: + - db \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2259351 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +Flask==1.1.2 +requests==2.25.1 +mongoengine==0.23.0 +PyJWT==1.7.1 From eb06b12086e8e18b26339bd77fefb8a9a980a03a Mon Sep 17 00:00:00 2001 From: Guilherme Tonello <66282650+GuiTonel@users.noreply.github.com> Date: Tue, 23 Mar 2021 00:09:57 -0300 Subject: [PATCH 2/3] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7aae612..285b04f 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Além disso também aplicado alguns conceitos como Decorators, upload de arquivo ## Como Rodar Para rodar subir a aplicação execute o seguinte comando na pasta raiz do projeto: -```shell +```shell docker-compose up -d ``` Desta forma, estará rodando os container resposáveis pela API e pelo banco de dados da aplicação. @@ -69,4 +69,4 @@ Para isso há duas rotas, uma para as taxas e outra para os usuarios. "valorParcela": 861.25, "valorSolicitado": 10000 } -``` \ No newline at end of file +``` From b011ced5925fd73147bbda6108dd39cdb46b593c Mon Sep 17 00:00:00 2001 From: Guilherme Tonello <66282650+GuiTonel@users.noreply.github.com> Date: Tue, 23 Mar 2021 00:10:44 -0300 Subject: [PATCH 3/3] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 285b04f..baf9db5 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,12 @@ Além disso também aplicado alguns conceitos como Decorators, upload de arquivo ## Como Rodar -Para rodar subir a aplicação execute o seguinte comando na pasta raiz do projeto: +Para rodar e subir a aplicação execute o seguinte comando na pasta raiz do projeto: ```shell docker-compose up -d ``` -Desta forma, estará rodando os container resposáveis pela API e pelo banco de dados da aplicação. -Obs: o banco de dados estará vazio e precisa ser preenchido. +Desta forma, estará rodando os container responsáveis pela API e pelo banco de dados da aplicação. +Obs: o banco de dados estará vazio e precisará ser preenchido. # Utilizando ## Preenchendo o banco