Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/LucasOliveira #45

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 26 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,26 @@
![WATTIO](http://wattio.com.br/web/image/1204-212f47c3/Logo%20Wattio.png)

#### Descrição

O desafio consiste em implementar um CRUD de filmes, utilizando [python](https://www.python.org/ "python") integrando com uma API REST e uma possível persistência de dados.

Rotas da API:

- `/filmes` - [GET] deve retornar todos os filmes cadastrados.
- `/filmes` - [POST] deve cadastrar um novo filme.
- `/filmes/{id}` - [GET] deve retornar o filme com ID especificado.

O Objetivo é te desafiar e reconhecer seu esforço para aprender e se adaptar. Qualquer código enviado, ficaremos muito felizes e avaliaremos com toda atenção!

#### Sugestão de Ferramentas
Não é obrigatório utilizar todas as as tecnologias sugeridas, mas será um diferencial =]

- Orientação a objetos (utilizar objetos, classes para manipular os filmes)
- [FastAPI](https://fastapi.tiangolo.com/) (API com documentação auto gerada)
- [Docker](https://www.docker.com/) / [Docker-compose](https://docs.docker.com/compose/install/) (Aplicação deverá ficar em um container docker, e o start deverá seer com o comando ``` docker-compose up ```
- Integração com banco de dados (persistir as informações em json (iniciante) /[SqLite](https://www.sqlite.org/index.html) / [SQLAlchemy](https://fastapi.tiangolo.com/tutorial/sql-databases/#sql-relational-databases) / outros DB)


#### Como começar?

- Fork do repositório
- Criar branch com seu nome ``` git checkout -b feature/ana ```
- Faça os commits de suas alterações ``` git commit -m "[ADD] Funcionalidade" ```
- Envie a branch para seu repositório ``` git push origin feature/ana ```
- Navegue até o [Github](https://github.com/), crie seu Pull Request apontando para a branch **```main```**
- Atualize o README.md descrevendo como subir sua aplicação

#### Dúvidas?

Qualquer dúvida / sugestão / melhoria / orientação adicional só enviar email para [email protected]

Salve!
### Sobre a API

A API, desenvolvida com FASTAPI e pymongo, possui nove rotas:
- ```/filmes``` - [GET] retorna todos os filmes do banco de dados
- ```/filmes/{id}``` - [GET] retorna o filme com a id {id}
- ```/filmes/``` - [POST] adiciona um filme ao banco de dados
- ```/filmes/{id}``` - [PUT] altera informações sobre um filme no banco de dados
- ```/filmes/{id}``` - [DELETE] remove o filme de id {id} do banco de dados

- ```/generos``` - [GET] retorna todos os generos do banco de dados
- ```/generos``` - [POST] adiciona um gênero ao banco de dados
- ```/generos/{id}``` - [PUT] altera informações sobre um gênero no banco de dados
- ```/generos/{id}``` - [DELETE] remove o gênero de id {id} do banco de dados

Atualmente ela roda apenas localmente. Para rodá-la, basta abrir a pasta do repositório e executar os seguintes comandos:
- ```pip install -r requirements.txt``` - para instalar os pacotes necessários
- ```cd src``` - para entrar na pasta do código fonte
- ```uvicorn main:app```

### Sobre o Banco de Dados

O Banco de Dados escolhido para esse desafio foi o MongoDB, principalmente pela possibilidade de acesso à nuvem. A API possui conexão constante e aberta ao banco de dados. Apesar de tal abordagem não ser recomendada pelos riscos à segurança do banco, optei visando a facilidade de observação do mesmo. O banco de dados conta com quatro tabelas:
- Control - Tabela de controle que visa quantificar e facilitar o controle dos filmes e gêneros
- Filmes - Tabela dos filmes; arquiva os seguintes dados: [ID], [Título], [Duração] e [Link]
- Generos - Tabela dos gêneros; arquiva os seguintes dados: [ID] e [Nome]
- FilmesGeneros - Tabela que arquiva a relação entre os filmes e generos
18 changes: 18 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
anyio==3.6.2
click==8.1.3
colorama==0.4.6
dnspython==2.3.0
fastapi==0.89.1
h11==0.14.0
idna==3.4
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.2
protobuf==3.20.3
pydantic==1.10.4
pymongo==4.3.3
sniffio==1.3.0
starlette==0.22.0
typing_extensions==4.4.0
uvicorn==0.20.0
Werkzeug==2.2.2
6 changes: 6 additions & 0 deletions src/classes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class Filmes:
def __init__(self, title: str, duration: str, link: str, genres: list):
self.title = title
self.duration = duration
self.link = link
self.genres = genres
150 changes: 150 additions & 0 deletions src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from pydantic import BaseModel
import models

class Filmes(BaseModel):
_id: int = 0
Title: str
Duration: str
Link: str

def set_id(self, id):
return {
'_id': id,
'Title': self.Title,
'Duration': self.Duration,
'Link': self.Link
}

class Generos(BaseModel):
_id: int = 0
Name: str

def set_id(self, id):
return {
'_id': id,
'Name': self.Name
}

app = FastAPI()

@app.get("/filmes")
def getFilmes():
f_data = models.filmes_collection.find().sort("Title", 1)
g_data = models.generos_collection.find().sort("_id", 1)
filmes = []
generos = []
for genero in g_data:
generos.append(genero.get("Name"))
for doc in f_data:
f_gen = []
for gen in doc.get("Genres"):
f_gen.append(generos[int(gen)-1])
filme = Filmes(Title=doc.get("Title"), Duration=doc.get("Duration"), Link=doc.get("Link"), Genres=f_gen)
filmes.append(dict(filme))
return JSONResponse(content={"Data": filmes}, status_code=200)

@app.get("/filmes/{id}")
def getFilme(id: int):
data = models.filmes_collection.find_one({"_id": id}, {"_id":0})
if not data:
return JSONResponse(content={"Data": "Filme não encontrado"}, status_code=404)
generos = []
for gen in data.get("Genres"):
g_data = models.generos_collection.find_one({"_id": int(gen)}, {"_id": 0})
generos.append(g_data.get("Name"))
filme = Filmes(Title=data.get("Title"), Duration=data.get("Duration"), Link=data.get("Link"), Genres=generos)
return JSONResponse(content={"Data": dict(filme)}, status_code=200)

@app.post("/filmes")
def postFilme(filme: Filmes, generos: list[str]):
f_data = models.filmes_collection.find({"Title": filme.Title})
for filme in f_data:
return JSONResponse(content={"Data": "Filme já cadastrado"}, status_code=406)
g_data = list(models.generos_collection.find().sort("_id", 1))
c_data = list(models.control_collection.find({"Type": "Filme"}))[0].get("Number")
size = len(g_data)
g_array = []
for genre in generos:
for i in range(size):
if genre == g_data[i].get("Name"):
g_array.append(g_data[i].get("_id"))
break
if len(generos) == len(g_array):
new_filme = dict(filme.set_id(c_data+1))
relations = []
for gen in g_array:
relations.append({"Movie": c_data+1, "Genre": gen})
models.filmes_collection.insert_one(new_filme)
models.filmes_generos_collection.insert_many(relations)
models.control_collection.find_one_and_update({"Type": "Filme"}, {"$set":{"Number": c_data+1}})
return JSONResponse(content={"Data": "Filme " + filme.Title + " cadastrado com sucesso"}, status_code=201)
else:
return JSONResponse(content = {"Data": "Gêneros não cadastrados"}, status_code=404)

@app.put("/filmes/{id}")
def updateFilme(id: int, filme: Filmes, generos: list[str] = [""]):
updatedFilme = models.filmes_collection.find_one_and_replace({"_id": id}, filme.set_id(id))
if not generos[0] == "":
g_data = list(models.generos_collection.find().sort("_id", 1))
size = len(g_data)
g_array = []
for genre in generos:
for i in range(size):
if genre == g_data[i].get("Name"):
g_array.append(g_data[i].get("_id"))
break
if len(generos) != len(g_array):
return JSONResponse(content = {"Data": "Gêneros não cadastrados"}, status_code=404)
models.filmes_generos_collection.delete_many({"Movie": id})
relations = []
for gen in g_array:
relations.append({"Movie": id, "Genre": gen})
models.filmes_generos_collection.insert_many(relations)
if not updatedFilme:
return JSONResponse(content={"Data": "Filme não encontrado"}, status_code=404)
return JSONResponse(content={"Data": "Filme " + filme.Title + " atualizado com sucesso"}, status_code=200)

@app.delete("/filmes/{id}")
def deleteFilme(id: int):
f_data = models.filmes_collection.find_one_and_delete({"_id": id})
if not f_data:
return JSONResponse(content={"Data": "Filme não encontrado"}, status_code=404)
models.filmes_generos_collection.delete_many({"Movie": id})
return JSONResponse(content={"Data": "Filme " + f_data.get("Title") + " foi deletado"}, status_code=200)

@app.get("/generos")
def getGenero():
g_data = models.generos_collection.find().sort("_id", 1)
generos = []
for genero in g_data:
generos.append(genero.get("Name"))
return JSONResponse(content={"Data": generos}, status_code=200)

@app.post("/generos")
def postGenero(genero: Generos):
g_data = list(models.generos_collection.find({"Name": genero.Name}))
for genero in g_data:
return JSONResponse(content={"Data": "Genero já cadastrado"}, status_code=406)
c_data = list(models.control_collection.find({"Type": "Genero"}))[0].get("Number")
new_genero = dict(genero.set_id(c_data+1))
models.generos_collection.insert_one(new_genero)
models.control_collection.find_one_and_update({"Type": "Genero"}, {"$set":{"Number": c_data+1}})
return JSONResponse(content={"Data": "Genero " + genero.Name + " cadastrado com sucesso"}, status_code=201)

@app.put("/generos/{id}")
def updateGenero(id: int, genero: str):
updatedGenero = models.generos_collection.find_one_and_update({"_id":id}, {"$set":{"Name":genero}})
if updatedGenero:
return JSONResponse(content={"Data": "Gênero " + genero + " atualizado com sucesso"}, status_code=200)
return JSONResponse(content={"Data": "Gênero não encontrado"}, status_code=404)

@app.delete("/generos/{id}")
def deleteGenero(id: int):
g_data = models.generos_collection.find_one({"_id": id})
if not g_data:
return JSONResponse(content={"Data": "Esse genero não existe no nosso banco de dados"}, status_code=404)
models.filmes_generos_collection.delete_many({"Genre": id})
genero_deletado = models.generos_collection.find_one_and_delete({"_id": id})
return JSONResponse(content={"Data": "Genero " + genero_deletado.get("Name") + " deletado com sucesso"})
33 changes: 33 additions & 0 deletions src/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from pymongo import MongoClient

try:
client = MongoClient("mongodb+srv://LucasOliveira:[email protected]/?retryWrites=true&w=majority")
db = client.Backend
print("Bd conectado")
except:
print("A conexão com o bd falhou")

filmes_collection = db.Filmes
generos_collection = db.Generos
control_collection = db.Control
filmes_generos_collection = db.FilmesGeneros

filmes_schema = {
"Title": {"type": "string", "required": "true"},
"Duration": {"type": "string", "required": "true"},
"Link": {"type": "string", "required": "true"}
}

filmes_generos_schema ={
"Movie": {"yype": "int32", "required": "true"},
"Genre": {"yype": "int32", "required": "true"}
}

generos_schema = {
"Name": {"type": "string", "required": "true"}
}

control_schema = {
"Type": {"type": "string", "required": "true"},
"Number": {"type": "int32", "required": "true"}
}