Projeto final
Você chegou ao final, PARABAINS 🎉
No aprendizado, nada melhor que praticar! Para isso, vamos fazer nosso "TCC" ou como gostam de chamar no mundo "interprize bizines": um teste técnico.
A ideia deste projeto final é simplesmente extrair tudo que aprendemos no curso para um grande exercício de fixação em formato de projeto.
O projeto
Neste projeto vamos construir uma API que segue os mesmos moldes da que desenvolvemos durante o curso, porém, com outra proposta. Iremos fazer uma versão simplificado de um acervo digital de livros. Chamaremos de MADR
(Mader), uma sigla para "Meu Acervo Digital de Romances".
O objetivo do projeto é criarmos um gerenciador de livros e relacionar com seus autores. Tudo isso em um contexto bastante simplificado. Usando somente as funcionalidades que aprendemos no curso.
A implementação será baseada em 3 pilares:
graph
MADR --> A["Controle de acesso / Gerenciamento de contas"]
MADR --> B["Gerenciamento de Livros"]
MADR --> C["Gerenciamento de Romancistas"]
A --> D["Gerenciamento de contas"]
D --> Criação
D --> Atualização
A --> G["Acesso via JWT"]
D --> Deleção
B --> E["CRUD"]
C --> F["CRUD"]
A API
Dividiremos os endpoints em três routers
:
contas
: Gerenciamento de contas e de acesso à APIlivros
: Gerenciamento de livrosromancistas
: Gerenciamento de romancistas
Contas
O router de conta deve ser responsável pelas operações referentes a criação, alteração e deleção de contas. Os endpoints:
-
POST
/conta
: deve ser responsável pela criação de uma nova conta- O schema responsável para criação desse endpoint deve ser:
- Esses schema deve ser validado com pydantic
- O retorno para o caso de sucesso deve ser
201
e com o schema de exemplo: - A senha deve ser criptografada antes de ser inserida no banco de dados
- obs: Não é necessário fazer o login no sistema para enviar uma requisição para esse enpoint
- 🚨 Caso o registro já exista na base, conflito
- Antes de inserir no banco, o nome deve ser sanitizado
-
PUT
/conta/{id}
: deve ser responsável pela alteração de uma conta especificada porid
- O schema responsável para criação desse endpoint deve ser:
- Esses schema deve ser validado com pydantic
- O retorno para o caso de sucesso deve ser
200
e com o schema de exemplo: - 🚨 O acesso só pode ocorrer via um
Bearer token
válido enviado nos headers, erro - 🚨 Somente a pessoa detentora da sua própria conta pode alterar seus dados
- 🚨 Caso as alterações no registro já existam na base, conflito
- Antes de inserir no banco, o nome deve ser sanitizados
-
DELETE
/conta/{id}
: deve ser responsável pela deleção de uma conta especificada porid
- O retorno para o caso de sucesso deve ser
200
e com o schema de exemplo: - 🚨 O acesso só pode ocorrer via um
Bearer token
válido enviado nos headers, erro - 🚨 Somente a pessoa detentora da sua própria conta pode alterar seus dados
- O retorno para o caso de sucesso deve ser
-
POST
/token
: Responsável pelo login- O endpoint deverá receber o seguinte schema via
OAuth2PasswordRequestForm
: - O conta deve ser validada com "username" e "password"
- 🚨 O acesso só pode ocorrer via um
Bearer token
válido enviado nos headers, erro - O retorno para o caso de sucesso deve ser
200
e com o schema de exemplo:
- O endpoint deverá receber o seguinte schema via
-
POST
/refresh-token
: Responsável por atualizar o token- O endpoint deverá receber os headers:
- O retorno para o caso de sucesso deve ser
200
e com o schema de exemplo: - 🚨 Caso as coisas não ocorram como o esperado: Erros
As condições do token JWT
O tempo de expiração do token deve ser de 60
minutos, o algorítimo usado deve ser HS256
e o subject deve ser o email
.
Livros
-
POST
/livro
: Responsável pela adição de um livro no MADR- O livro deve ser criado com base no seguinte schema:
- O retorno de sucesso
200
deve ser: - Disponível somente via autenticação, caso contrário erro
- Antes de inserir no banco, os nomes devem ser sanitizados
- 🚨 Caso o novo nome já exista na base, conflito
-
DELETE
/livro/{id}
: Responsável por deletar um livro usando oid
como base -
PATCH
/livro/{id}
: Responsável por alterar um livro usando oid
como base- O livro deve ser alterado com o seguinte schema:
- O schema para o caso de sucesso
200
deve ser: - Disponível somente via autenticação, caso contrário erro
- Antes de inserir no banco, os nomes devem ser sanitizados
- 🚨 Caso o
id
não exista no MADR, erro - 🚨 Caso o novo nome já exista na base, conflito
-
GET
/livro/{id}
: Busca um livro porid
- O retorno deve ser
200 OK
com o schema: - 🚨 Caso o
id
não exista no MADR, erro
- O retorno deve ser
-
GET
/livro?nome=xxxx&ano=xxxx
: Busca por livros quando query parameters- Deve ser capaz de filtrar por nome de forma parcial
- Deve ser capaz de filtrar por ano
- Deve paginar os resultados maiores que 20
- Exemplo de chamada:
- Exemplo do schema de resposta:
- Caso não encontre nenhuma correspondência, deverá retornar
200 OK
com a lista vazia:
Romancistas
-
POST
/romancista
: Responsável pela adição de romancistas no MADR- Romancista devem ser criadas com base no seguinte schema:
- A resposta padrão deve retornar
201
com o schema: - Disponível somente via autenticação, caso contrário erro
- Antes de inserir no banco, os nomes devem ser sanitizados
- 🚨 Caso o novo nome já exista na base, conflito
-
DELETE
/romancista/{id}
: responsável pela deleção de romancistas porid
-
PATCH
/romancista/{id}
: responsável pela alteração de romancistas porid
- Romancista devem ser alteradas com base no seguinte schema:
- A resposta padrão deve retornar
200
com o schema: - Disponível somente via autenticação, caso contrário erro
- Antes de inserir no banco, os nomes devem ser sanitizados
- 🚨 Caso o
id
não exista no MADR, erro - 🚨 Caso o novo nome já exista na base, conflito
-
GET
/romancista/{id}
: Busca um romancista porid
- O retorno deve ser
200 OK
com o schema: - 🚨 Caso o
id
não exista no MADR, erro
- O retorno deve ser
-
GET
/romancista?
: Busca romancistas baseado em nomes parciais- Deve ser capaz de filtrar por nome de forma parcial
- Deve paginar os resultados maiores que 20
- Exemplo de chamada:
- Exemplo do schema de resposta:
- Caso não encontre nenhuma correspondência, deverá retornar
200 OK
com a lista vazia:
Sanitização de dados
Antes de inserir no banco, os nomes de romancistas ou livros devem ser sanitizados.
Exemplos para os nomes:
Entrada | Sanitizado |
---|---|
"Machado de Assis" | machado de assis |
"Manuel Bandeira" | manuel bandeira |
"Edgar Alan Poe " | edgar alan poe |
"Androides Sonham Com Ovelhas Elétricas?" | androides sonham com ovelhas elétricas |
" breve história do tempo " | breve história do tempo |
"O mundo assombrado pelos demônios" | o mundo assombrado pelos demônios |
Erros
Erros de autenticação
Todos os erros relativos à autenticação devem retornar o status code 400 BAD REQUEST
com o seguinte schema:
Erros de permissão
Caso uma pessoa tente fazer uma operação sem a permissão necessária, o status code401 Unauthorized
deverá ser retornado com o json:
Erro não encontrado
Caso o id
não exista no MADR, um erro 404 NOT FOUND
deve ser retornado com o json:
ou então
Erro de conflito
Caso o recurso já exista, devemos retornar 409 CONFLICT
com o json:
Onde a variável recurso
é relativa ao recurso que está duplicado. Exemplos para:
- contas:
"conta já consta no MADR"
- livros:
"livro já consta no MADR"
- romancista:
"romancista já consta no MADR"
O banco de dados / ORM
A modelagem do banco deve contar com três tabelas: User
, Livro
e Romancista
. Onde Livro
e Romancista
se relacionam da forma que romancistas podem estar relacionado a diversos livros e diversos livros devem ser associados a uma única romancista. Como sugere o DER:
erDiagram
Romancista |o -- |{ Livro : livros
User {
int id PK
string email UK
string username UK
string senha
}
Livro {
int id PK
string ano
string titulo UK
string id_romancista FK
}
Romancista {
int id PK
string nome UK
string livros
}
Relacionamentos no ORM
Alguns problemas podem ser encontrados durante a criação dos relacionamentos com SQLAlchemy, então segue uma cola simples caso sinta que travou.
Em caso de emergência quebre o vidro
Cenários de teste
O ideal é que esse projeto tenha uma cobertura de testes de 100%. Afinal, foi dessa forma que passamos nosso tempo no curso, testando absolutamente tudo e garantindo que o código funcione da maneira como deveria.
Nesse tópico separei alguns cenários de testes usando a linguagem gherkin para te ajudar a pensar em como as requisições serão recebidas e devem ser respondidas pela aplicação.
Esses cenários podem te guiar tanto para escrever a aplicação, quanto os testes.
Gerenciamento de contas
Funcionalidade: Gerenciamento de conta
Cenário: Criação de conta
Quando enviar um "POST" em "/user"
"""
{
"username": "dunossauro",
"email": "dudu@dudu.com",
"password": "123456"
}
"""
Então devo receber o status "201"
E o json contendo
"""
{
"email": "dudu@dudu.com",
"username": "dunossauro"
}
"""
Cenário: Alteração de conta
Quando enviar um "POST" em "/user"
"""
{
"username": "dunossauro",
"email": "dudu@dudu.com",
"password": "123456"
}
"""
Quando enviar um "PUT" em "/user/1"
"""
{
"username": "dunossauro",
"email": "dudu@dudu.com",
"password": "654321"
}
"""
Então devo receber o status "200"
E o json contendo
"""
{
"username": "dunossauro",
"email": "dudu@dudu.com"
}
"""
Cenário: Deleção da conta
Quando enviar um "POST" em "/user"
"""
{
"username": "dunossauro",
"email": "dudu@dudu.com",
"password": "123456"
}
"""
Quando enviar um "DELETE" em "/user/1"
Então devo receber o status "200"
E o json contendo
"""
{
"message": "Conta deletada com sucesso"
}
"""
Cenário: Criação de conta já existente
Quando enviar um "POST" em "/user"
"""
{
"username": "dunossauro",
"email": "dudu@dudu.com",
"password": "123456"
}
"""
Quando enviar um "POST" em "/user"
"""
{
"username": "dunossauro",
"email": "dudu@dudu.com",
"password": "123456"
}
"""
Então devo receber o status "400"
E o json contendo
"""
{
"message": "Conta já cadastrada"
}
"""
Gerenciamento de livros
Funcionalidade: Livro
Cenário: Registro de livro
Quando enviar um "POST" em "/livro/"
"""
{
"ano": 1973,
"titulo": "Café Da Manhã Dos Campeões",
"romancista_id": 1
}
"""
Então devo receber o status "201"
E o json contendo
"""
{
"ano": 1973,
"titulo": "café da manhã dos campeões",
"romancista_id": 1
}
"""
Cenário: Alteração de livro
Quando enviar um "PATCH" em "/livro/1"
"""
{
"ano": 1974
}
"""
Então devo receber o status "200"
E o json contendo
"""
{
"ano": 1974,
"titulo": "café da manhã dos campeões",
"romancista_id": 1
}
"""
Cenário: Buscar livro por ID
Quando enviar um "GET" em "/livro/1"
Então devo receber o status "200"
E o json contendo
"""
{
"ano": 1974,
"titulo": "café da manhã dos campeões",
"romancista_id": 1
}
"""
Cenário: Deleção de livro
Quando enviar um "DELETE" em "/livro/1"
Então devo receber o status "200"
E o json contendo
"""
{
"message": "Livro deletado no MADR"
}
"""
Cenário: Filtro de livros
Quando enviar um "POST" em "/livro/"
"""
{
"ano": 1900,
"titulo": "Café Da Manhã Dos Campeões",
"romancista_id": 1
}
"""
E enviar um "POST" em "/livro/"
"""
{
"ano": 1900,
"titulo": "Memórias Póstumas de Brás Cubas",
"romancista_id": 2
}
"""
E enviar um "POST" em "/livro/"
"""
{
"ano": 1865,
"titulo": "Iracema",
"romancista_id": 3
}
"""
E enviar um "GET" em "/livro/?titulo=a&ano=1900"
Então devo receber o status "200"
E o json contendo
"""
{
"livros": [
{"ano": 1900, "titulo": "café da manhã dos campeões", "romancista_id": 1, "id": 1},
{"ano": 1900, "titulo": "memórias póstumas de brás cubas", "romancista_id": 2, "id": 2}
]
}
"""
Gerenciamento de romancistas
Funcionalidade: Romancistas
Cenário: Criação de Romancista
Quando enviar um "POST" em "/romancista"
"""
{
"nome": "Clarice Lispector"
}
"""
Então devo receber o status "201"
E o json contendo
"""
{
"nome": "clarice lispector"
}
"""
Cenário: Buscar romancista por ID
Quando enviar um "GET" em "/romancista/1"
Então devo receber o status "200"
E o json contendo
"""
{
"nome": "clarice lispector"
}
"""
Cenário: Alteração de Romancista
Quando enviar um "PUT" em "/romancista/1"
"""
{
"nome": "manuel bandeira"
}
"""
Então devo receber o status "200"
E o json contendo
"""
{
"nome": "manuel bandeira"
}
"""
Cenário: Deleção de Romancista
Quando enviar um "DELETE" em "/romancista/1"
Então devo receber o status "200"
E o json contendo
"""
{
"message": "Romancista deletada no MADR"
}
"""
Cenário: Busca de romancistas por filtro
Quando enviar um "POST" em "/romancista"
"""
{
"nome": "Clarice Lispector"
}
"""
E enviar um "POST" em "/romancista"
"""
{
"nome": "Manuel Bandeira"
}
"""
E enviar um "POST" em "/romancista"
"""
{
"nome": "Paulo Leminski"
}
"""
Quando enviar um "GET" em "/romancista?nome=a"
Então devo receber o status "200"
E o json contendo
"""
{
"romancistas": [
{"nome": "clarice lispector", "id": 1},
{"nome": "manuel bandeira", "id": 2},
{"nome": "paulo leminski", "id": 3}
]
}
"""
Ferramentas
Gostaria que você se sentissem livres para escolher o conjunto de ferramentas que mais gostarem para fazer esse projeto. O formatador preferido, o servidor de aplicação preferido, projeto de variáveis de ambiente preferido, etc.
As únicas coisas exigidas para a criação desse projeto são:
- Python 3.11+
- FastAPI
- SQLAlchemy
- Alguma ferramenta para gerenciamento de projeto que suporte
pyproject.toml
- PostgreSQL
- Containers (a ferramenta que preferir. Podman/docker/k8s/...)
- Pytest
Entrega do projeto final
Criar um projeto utilizando git e hospedado em alguma plataforma (github/gitlab/codeberg/...) e postar nessa issue. Ao final, juntarei todos os projetos finais em uma tabela nesse site para que as pessoas possam aprender com as diferenças entre os projetos.
É imprescindível que seu projeto tenha um README.md
explicando quais foram as suas escolhas e como executar o seu projeto. Para podermos rodar e aprender com ele.