From 899d5b98428726d23b48e9c26ef842cd59bb597c Mon Sep 17 00:00:00 2001 From: lucasaguiar-la Date: Mon, 30 Jun 2025 19:07:36 -0300 Subject: [PATCH 01/18] [ADD] Initial commit --- .gitignore | 1 + main.py | 1 + 2 files changed, 2 insertions(+) create mode 100644 .gitignore create mode 100644 main.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..f5e96dbfa --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +venv \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 000000000..1daab9acc --- /dev/null +++ b/main.py @@ -0,0 +1 @@ +# Grato pela oportunidade! \ No newline at end of file From 74d49092a01023c6f8309314dbae600f82112370 Mon Sep 17 00:00:00 2001 From: lucasaguiar-la Date: Mon, 30 Jun 2025 19:15:17 -0300 Subject: [PATCH 02/18] [ADD] Frist function to record a film --- .gitignore | 2 ++ main.py | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f5e96dbfa..c5387ff74 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ +__pycache__ + venv \ No newline at end of file diff --git a/main.py b/main.py index 1daab9acc..1fb0a3a84 100644 --- a/main.py +++ b/main.py @@ -1 +1,16 @@ -# Grato pela oportunidade! \ No newline at end of file +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + +class MovieModel(BaseModel): + id: int + movie_name: str + synopsis: str + duration: int + year_release: int + movie_genre: str + +@app.post('/filmes') +def register_movie(movie: MovieModel): + return {'Mensagem': 'Filme cadastrado com sucesso!'} \ No newline at end of file From 2b1a9f5fe4d0c64a3b355caad31059e125595efe Mon Sep 17 00:00:00 2001 From: lucasaguiar-la Date: Mon, 30 Jun 2025 19:20:09 -0300 Subject: [PATCH 03/18] [ADD] Function to consult all films --- main.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index 1fb0a3a84..4eb9a9c35 100644 --- a/main.py +++ b/main.py @@ -3,6 +3,8 @@ app = FastAPI() +movies_db = [] + class MovieModel(BaseModel): id: int movie_name: str @@ -13,4 +15,9 @@ class MovieModel(BaseModel): @app.post('/filmes') def register_movie(movie: MovieModel): - return {'Mensagem': 'Filme cadastrado com sucesso!'} \ No newline at end of file + movies_db.append(movie) + return {'Mensagem': 'Filme cadastrado com sucesso!'} + +@app.get('/filmes') +def get_all_movies(): + return movies_db \ No newline at end of file From 234a10326c64327deda4055cc4651a932ced85fe Mon Sep 17 00:00:00 2001 From: lucasaguiar-la Date: Mon, 30 Jun 2025 19:32:37 -0300 Subject: [PATCH 04/18] [ADD] Function to consult one movie --- main.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index 4eb9a9c35..7aa3c475b 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,4 @@ -from fastapi import FastAPI +from fastapi import FastAPI, HTTPException from pydantic import BaseModel app = FastAPI() @@ -18,6 +18,13 @@ def register_movie(movie: MovieModel): movies_db.append(movie) return {'Mensagem': 'Filme cadastrado com sucesso!'} +@app.get('/filmes/{id}') +def get_one_movie(id: int): + for movie in movies_db: + if movie.id == id: + return movie + HTTPException(status_code=404, detail='Filme não encontrado :(') + @app.get('/filmes') def get_all_movies(): return movies_db \ No newline at end of file From 1710bf69abca07329d9f371abaf7643cba7fd640 Mon Sep 17 00:00:00 2001 From: lucasaguiar-la Date: Mon, 30 Jun 2025 19:42:27 -0300 Subject: [PATCH 05/18] [ADD] Starting to model the datebase --- .gitignore | 1 + main.py | 11 ----------- src/db.py | 5 +++++ src/models.py | 8 ++++++++ src/schema.sql | 8 ++++++++ 5 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 src/db.py create mode 100644 src/models.py create mode 100644 src/schema.sql diff --git a/.gitignore b/.gitignore index c5387ff74..b8be4ea6a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ __pycache__ +.env venv \ No newline at end of file diff --git a/main.py b/main.py index 7aa3c475b..efaf70a31 100644 --- a/main.py +++ b/main.py @@ -1,18 +1,7 @@ from fastapi import FastAPI, HTTPException -from pydantic import BaseModel app = FastAPI() -movies_db = [] - -class MovieModel(BaseModel): - id: int - movie_name: str - synopsis: str - duration: int - year_release: int - movie_genre: str - @app.post('/filmes') def register_movie(movie: MovieModel): movies_db.append(movie) diff --git a/src/db.py b/src/db.py new file mode 100644 index 000000000..bf0d98a61 --- /dev/null +++ b/src/db.py @@ -0,0 +1,5 @@ +import psycopg2 +import os + +def get_connection(): + return psycopg2.connect(os.environ['DATABASE_URL']) \ No newline at end of file diff --git a/src/models.py b/src/models.py new file mode 100644 index 000000000..c7e259e0d --- /dev/null +++ b/src/models.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel + +class MovieModel(BaseModel): + movie_name: str + synopsis: str + duration: int + year_release: int + movie_genre: str \ No newline at end of file diff --git a/src/schema.sql b/src/schema.sql new file mode 100644 index 000000000..039c45d49 --- /dev/null +++ b/src/schema.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS movies( + id SERIAL PRIMARY KEY, + movieName TEXT NOT NULL, + synopsis TEXT, + duration INT, + yearRelease INT, + movie_gender TEXT +); \ No newline at end of file From 75a80c54db185c61070747154e72d46125c38c2d Mon Sep 17 00:00:00 2001 From: lucasaguiar-la Date: Mon, 30 Jun 2025 21:07:23 -0300 Subject: [PATCH 06/18] [ADD] Testing docker --- main.py | 34 ++++++++++++++++++++++++---------- requirements.txt | Bin 0 -> 600 bytes src/db.py | 6 +++++- src/{models.py => model.py} | 2 +- src/schema.sql | 2 ++ 5 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 requirements.txt rename src/{models.py => model.py} (86%) diff --git a/main.py b/main.py index efaf70a31..f67eacfef 100644 --- a/main.py +++ b/main.py @@ -1,19 +1,33 @@ +from src.model import MovieModel +from src.db import get_connection from fastapi import FastAPI, HTTPException app = FastAPI() @app.post('/filmes') def register_movie(movie: MovieModel): - movies_db.append(movie) + conn = get_connection() + cur = conn.cursor() + + cur.execute(''' + INSERT INTO movies (movieName, synopsis, duration, yearRelease, movie_gender) + VALUES (%s, %s, %s, %s, %s); + ''', (movie.movie_name, movie.synopsis, movie.duration, movie.year_release, movie.movie_gender)) + + movie.id = cur.fetchone()[0] + conn.commit() + cur.close() + conn.close() + return {'Mensagem': 'Filme cadastrado com sucesso!'} -@app.get('/filmes/{id}') -def get_one_movie(id: int): - for movie in movies_db: - if movie.id == id: - return movie - HTTPException(status_code=404, detail='Filme não encontrado :(') +# @app.get('/filmes/{id}') +# def get_one_movie(id: int): +# for movie in movies_db: +# if movie.id == id: +# return movie +# HTTPException(status_code=404, detail='Filme não encontrado :(') -@app.get('/filmes') -def get_all_movies(): - return movies_db \ No newline at end of file +# @app.get('/filmes') +# def get_all_movies(): +# return movies_db diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..35baf46a87b282ac4f8bb2d1cbed56b779f327a1 GIT binary patch literal 600 zcmZ9J?{2~{48;8zX^#RTw6N{No&}+nKh;>M8a9oWXZxMg078*T$(_$<`|rB3$T`{B zHpJFin{r>Q=R4U4XKpilCFb^G6Ic

r(wjngeqx?8;PkabutDK-I`ZTW(D^+K;oPnnTKo_s({lGFM@_ zhm31G1M}OWBAqgwn%R|#d21b7dn)QqzQx9Sl8<{<*~(+i8r|Q~WpXD{P>yyK{5MbZ Qn=UjZZS$^L`JOW4ABw_QT>t<8 literal 0 HcmV?d00001 diff --git a/src/db.py b/src/db.py index bf0d98a61..dac413dcd 100644 --- a/src/db.py +++ b/src/db.py @@ -1,5 +1,9 @@ import psycopg2 +from dotenv import load_dotenv import os +load_dotenv() + def get_connection(): - return psycopg2.connect(os.environ['DATABASE_URL']) \ No newline at end of file + print(repr(os.getenv("DATABASE_URL"))) + return psycopg2.connect(os.getenv('DATABASE_URL')) \ No newline at end of file diff --git a/src/models.py b/src/model.py similarity index 86% rename from src/models.py rename to src/model.py index c7e259e0d..36407ca70 100644 --- a/src/models.py +++ b/src/model.py @@ -5,4 +5,4 @@ class MovieModel(BaseModel): synopsis: str duration: int year_release: int - movie_genre: str \ No newline at end of file + movie_gender: str \ No newline at end of file diff --git a/src/schema.sql b/src/schema.sql index 039c45d49..a7747885c 100644 --- a/src/schema.sql +++ b/src/schema.sql @@ -1,3 +1,5 @@ +CREATE DATABASE IF NOT EXISTS movie_db; + CREATE TABLE IF NOT EXISTS movies( id SERIAL PRIMARY KEY, movieName TEXT NOT NULL, From 134901c27181945fa5a8b76aa7045c6c1ceef37f Mon Sep 17 00:00:00 2001 From: lucasaguiar-la Date: Mon, 30 Jun 2025 22:24:31 -0300 Subject: [PATCH 07/18] [ADD] Building containers and images with docker/compose --- Dockerfile | 11 +++++++++++ docker-compose.yml | 31 +++++++++++++++++++++++++++++++ main.py | 5 ++--- requirements.txt | Bin 600 -> 614 bytes src/schema.sql => schema.sql | 6 ++---- src/db.py | 1 - 6 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 Dockerfile create mode 100644 docker-compose.yml rename src/schema.sql => schema.sql (57%) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..1347371a4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY . . + +RUN pip install --no-cache-dir -r requirements.txt + +EXPOSE 8000 + +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..ac819a1ec --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,31 @@ +version: "3.8" + +services: + db: + image: postgres:15 + container_name: postgres_movies + restart: always + environment: + POSTGRES_USER: user_movie + POSTGRES_PASSWORD: senha123 + POSTGRES_DB: moviesdb + volumes: + - postgres_data:/var/lib/postgresql/data + - ./schema.sql:/docker-entrypoint-initdb.d/schema.sql + ports: + - "5433:5432" + + api: + build: . + container_name: fastapi_movies + depends_on: + - db + ports: + - "8000:8000" + env_file: + - .env + volumes: + - .:/app + +volumes: + postgres_data: diff --git a/main.py b/main.py index f67eacfef..3e1fea8e1 100644 --- a/main.py +++ b/main.py @@ -10,11 +10,10 @@ def register_movie(movie: MovieModel): cur = conn.cursor() cur.execute(''' - INSERT INTO movies (movieName, synopsis, duration, yearRelease, movie_gender) - VALUES (%s, %s, %s, %s, %s); + INSERT INTO movies (movie_name, synopsis, duration, year_release, movie_gender) + VALUES (%s, %s, %s, %s, %s); ''', (movie.movie_name, movie.synopsis, movie.duration, movie.year_release, movie.movie_gender)) - movie.id = cur.fetchone()[0] conn.commit() cur.close() conn.close() diff --git a/requirements.txt b/requirements.txt index 35baf46a87b282ac4f8bb2d1cbed56b779f327a1..e18021ac6e24511f9389047124ffb374effc1fd8 100644 GIT binary patch delta 24 fcmcb?@{DD|7hYY4B!*0eJcdMuB8JM1pHmqDX($L3 delta 11 ScmaFHa)V{Um(5I!DU1Lg$OL8p diff --git a/src/schema.sql b/schema.sql similarity index 57% rename from src/schema.sql rename to schema.sql index a7747885c..58d5abca3 100644 --- a/src/schema.sql +++ b/schema.sql @@ -1,10 +1,8 @@ -CREATE DATABASE IF NOT EXISTS movie_db; - CREATE TABLE IF NOT EXISTS movies( id SERIAL PRIMARY KEY, - movieName TEXT NOT NULL, + movie_name TEXT NOT NULL, synopsis TEXT, duration INT, - yearRelease INT, + year_release INT, movie_gender TEXT ); \ No newline at end of file diff --git a/src/db.py b/src/db.py index dac413dcd..d3a86d6d4 100644 --- a/src/db.py +++ b/src/db.py @@ -5,5 +5,4 @@ load_dotenv() def get_connection(): - print(repr(os.getenv("DATABASE_URL"))) return psycopg2.connect(os.getenv('DATABASE_URL')) \ No newline at end of file From a562b2c1dd68cdfceb9e3dbe848e3c98e06397b6 Mon Sep 17 00:00:00 2001 From: lucasaguiar-la Date: Mon, 30 Jun 2025 22:44:31 -0300 Subject: [PATCH 08/18] [ADD] Requests and final endpoints for the database --- main.py | 65 ++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 10 deletions(-) diff --git a/main.py b/main.py index 3e1fea8e1..53031ff72 100644 --- a/main.py +++ b/main.py @@ -20,13 +20,58 @@ def register_movie(movie: MovieModel): return {'Mensagem': 'Filme cadastrado com sucesso!'} -# @app.get('/filmes/{id}') -# def get_one_movie(id: int): -# for movie in movies_db: -# if movie.id == id: -# return movie -# HTTPException(status_code=404, detail='Filme não encontrado :(') - -# @app.get('/filmes') -# def get_all_movies(): -# return movies_db +@app.get('/filmes/{id}') +def get_one_movie(id: int): + conn = get_connection() + cur = conn.cursor() + + cur.execute(''' + SELECT id, movie_name, synopsis, duration, year_release, movie_gender + FROM movies + WHERE id = %s; + ''', (id,)) + row = cur.fetchone() + + cur.close() + conn.close() + + if row: + movie = { + 'id': row[0], + 'movie_name': row[1], + 'synopsis': row[2], + 'duration': row[3], + 'year_release': row[4], + 'movie_gender': row[5], + } + return movie + + else: + raise HTTPException(status_code=404, detail='Filme não encontrado :(') + +@app.get('/filmes') +def get_all_movies(): + conn = get_connection() + cur = conn.cursor() + + cur.execute(''' + SELECT id, movie_name, synopsis, duration, year_release, movie_gender + FROM movies; + ''') + rows = cur.fetchall() + + cur.close() + conn.close() + + movies = [] + for row in rows: + movies.append({ + 'id': row[0], + 'movie_name': row[1], + 'synopsis': row[2], + 'duration': row[3], + 'year_release': row[4], + 'movie_gender': row[5], + }) + + return movies From 61d67dfae2633c1340682e363b1d0754ee89683f Mon Sep 17 00:00:00 2001 From: lucasaguiar-la Date: Tue, 1 Jul 2025 08:39:27 -0300 Subject: [PATCH 09/18] [ADD] Movie repository class --- docker-compose.yml | 2 +- main.py | 4 +- src/controller/movie_controller.py | 0 src/{db.py => database/connection.py} | 0 schema.sql => src/database/schema.sql | 0 src/{model.py => models/movie.py} | 0 src/repositories/movie_repository.py | 67 +++++++++++++++++++++++++++ 7 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 src/controller/movie_controller.py rename src/{db.py => database/connection.py} (100%) rename schema.sql => src/database/schema.sql (100%) rename src/{model.py => models/movie.py} (100%) create mode 100644 src/repositories/movie_repository.py diff --git a/docker-compose.yml b/docker-compose.yml index ac819a1ec..84aa3929c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ services: POSTGRES_DB: moviesdb volumes: - postgres_data:/var/lib/postgresql/data - - ./schema.sql:/docker-entrypoint-initdb.d/schema.sql + - ./src/database/schema.sql:/docker-entrypoint-initdb.d/schema.sql ports: - "5433:5432" diff --git a/main.py b/main.py index 53031ff72..f596195f3 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,5 @@ -from src.model import MovieModel -from src.db import get_connection +from models.movie import MovieModel +from database.connection import get_connection from fastapi import FastAPI, HTTPException app = FastAPI() diff --git a/src/controller/movie_controller.py b/src/controller/movie_controller.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/db.py b/src/database/connection.py similarity index 100% rename from src/db.py rename to src/database/connection.py diff --git a/schema.sql b/src/database/schema.sql similarity index 100% rename from schema.sql rename to src/database/schema.sql diff --git a/src/model.py b/src/models/movie.py similarity index 100% rename from src/model.py rename to src/models/movie.py diff --git a/src/repositories/movie_repository.py b/src/repositories/movie_repository.py new file mode 100644 index 000000000..c217e055a --- /dev/null +++ b/src/repositories/movie_repository.py @@ -0,0 +1,67 @@ +from src.database.connection import get_connection +from src.models.movie import MovieModel + +class MovieRepository: + def get_all(self): + conn = get_connection() + cur = conn.cursor() + + cur.execute(''' + SELECT id, movie_name, synopsis, duration, year_release, movie_gender + FROM movies; + ''') + + rows = cur.fetchall() + cur.close() + conn.close() + + movies = [] + for row in rows: + movies.append({ + 'id': row[0], + 'movie_name': row[1], + 'synopsis': row[2], + 'duration': row[3], + 'year_release': row[4], + 'movie_gender': row[5] + }) + + return movies + + def get_by_id(self, id: int): + conn = get_connection() + cur = conn.cursor() + + cur.execute(''' + SELECT id, movie_name, synopsis, duration, year_release, movie_gender + FROM movies + WHERE id = %s; + ''', (id,)) + + row = cur.fetchone() + cur.close() + conn.close() + + if row: + return { + 'id': row[0], + 'movie_name': row[1], + 'synopsis': row[2], + 'duration': row[3], + 'year_release': row[4], + 'movie_gender': row[5] + } + return None + + def create(self, movie: MovieModel): + conn = get_connection() + cur = conn.cursor() + + cur.execute(''' + INSERT INTO movies (movie_name, synopsis, duration, year_release, movie_gender) + VALUES (%s, %s, %s, %s, %s); + ''', (movie.movie_name, movie.synopsis, movie.duration, movie.year_release, movie.movie_gender)) + + conn.commit() + cur.close() + conn.close() \ No newline at end of file From b3957878eacad36cb9dbbd2f139f6e7c1161d5c5 Mon Sep 17 00:00:00 2001 From: lucasaguiar-la Date: Tue, 1 Jul 2025 08:47:41 -0300 Subject: [PATCH 10/18] [ADD] Adding movie controller --- src/controller/movie_controller.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/controller/movie_controller.py b/src/controller/movie_controller.py index e69de29bb..47cee589e 100644 --- a/src/controller/movie_controller.py +++ b/src/controller/movie_controller.py @@ -0,0 +1,25 @@ +from src.models.movie import MovieModel +from src.repositories.movie_repository import MovieRepository + +from fastapi import FastAPI, HTTPException + +router = FastAPI() +movie_repository = MovieRepository + +@router.post('/filmes', status_code=201) +def register_movie(movie: MovieModel): + movie_repository.create(movie) + return { + 'Mensagem': 'Filme registrado com sucesso!' + } + +@router.get('/filmes') +def get_all_movies(): + return movie_repository.get_all() + +def get_one_movie(id: int): + movie = movie_repository.get_by_id(id) + if movie: + return movie + else: + raise HTTPException(status_code=404, detail='Filme não encontrado :(') \ No newline at end of file From 1e29c5d6e4104c0c5ea3422715a8bc28be676586 Mon Sep 17 00:00:00 2001 From: lucasaguiar-la Date: Tue, 1 Jul 2025 08:57:46 -0300 Subject: [PATCH 11/18] [ADD] Testing the application --- main.py | 81 ++++++--------------------------------------------------- 1 file changed, 8 insertions(+), 73 deletions(-) diff --git a/main.py b/main.py index f596195f3..4dab91cd6 100644 --- a/main.py +++ b/main.py @@ -1,77 +1,12 @@ -from models.movie import MovieModel -from database.connection import get_connection -from fastapi import FastAPI, HTTPException +from fastapi import FastAPI +from src.controller import movie_controller app = FastAPI() -@app.post('/filmes') -def register_movie(movie: MovieModel): - conn = get_connection() - cur = conn.cursor() +app.include_router(movie_controller.router) - cur.execute(''' - INSERT INTO movies (movie_name, synopsis, duration, year_release, movie_gender) - VALUES (%s, %s, %s, %s, %s); - ''', (movie.movie_name, movie.synopsis, movie.duration, movie.year_release, movie.movie_gender)) - - conn.commit() - cur.close() - conn.close() - - return {'Mensagem': 'Filme cadastrado com sucesso!'} - -@app.get('/filmes/{id}') -def get_one_movie(id: int): - conn = get_connection() - cur = conn.cursor() - - cur.execute(''' - SELECT id, movie_name, synopsis, duration, year_release, movie_gender - FROM movies - WHERE id = %s; - ''', (id,)) - row = cur.fetchone() - - cur.close() - conn.close() - - if row: - movie = { - 'id': row[0], - 'movie_name': row[1], - 'synopsis': row[2], - 'duration': row[3], - 'year_release': row[4], - 'movie_gender': row[5], - } - return movie - - else: - raise HTTPException(status_code=404, detail='Filme não encontrado :(') - -@app.get('/filmes') -def get_all_movies(): - conn = get_connection() - cur = conn.cursor() - - cur.execute(''' - SELECT id, movie_name, synopsis, duration, year_release, movie_gender - FROM movies; - ''') - rows = cur.fetchall() - - cur.close() - conn.close() - - movies = [] - for row in rows: - movies.append({ - 'id': row[0], - 'movie_name': row[1], - 'synopsis': row[2], - 'duration': row[3], - 'year_release': row[4], - 'movie_gender': row[5], - }) - - return movies +@app.get('/') +def root(): + return { + 'Status': 'API rodando...' + } \ No newline at end of file From f1f9ab210f7932c0ca77508ce6b00efb4b30d5da Mon Sep 17 00:00:00 2001 From: lucasaguiar-la Date: Tue, 1 Jul 2025 09:32:40 -0300 Subject: [PATCH 12/18] [ADD] Router fixing on controller --- Dockerfile | 4 +++- docker-compose.yml | 4 ++-- main.py | 1 - src/controller/movie_controller.py | 7 ++++--- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1347371a4..d959494ef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,10 +2,12 @@ FROM python:3.11-slim WORKDIR /app -COPY . . +COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt +COPY . . + EXPOSE 8000 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] diff --git a/docker-compose.yml b/docker-compose.yml index 84aa3929c..382fce9fc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,8 +6,8 @@ services: container_name: postgres_movies restart: always environment: - POSTGRES_USER: user_movie - POSTGRES_PASSWORD: senha123 + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres POSTGRES_DB: moviesdb volumes: - postgres_data:/var/lib/postgresql/data diff --git a/main.py b/main.py index 4dab91cd6..a0a913e91 100644 --- a/main.py +++ b/main.py @@ -2,7 +2,6 @@ from src.controller import movie_controller app = FastAPI() - app.include_router(movie_controller.router) @app.get('/') diff --git a/src/controller/movie_controller.py b/src/controller/movie_controller.py index 47cee589e..dad35272a 100644 --- a/src/controller/movie_controller.py +++ b/src/controller/movie_controller.py @@ -1,10 +1,10 @@ from src.models.movie import MovieModel from src.repositories.movie_repository import MovieRepository -from fastapi import FastAPI, HTTPException +from fastapi import APIRouter, HTTPException -router = FastAPI() -movie_repository = MovieRepository +router = APIRouter() +movie_repository = MovieRepository() @router.post('/filmes', status_code=201) def register_movie(movie: MovieModel): @@ -17,6 +17,7 @@ def register_movie(movie: MovieModel): def get_all_movies(): return movie_repository.get_all() +@router.get('/filmes/{id}') def get_one_movie(id: int): movie = movie_repository.get_by_id(id) if movie: From c8ee06edbb70898a16df7f435afe5aba6721868c Mon Sep 17 00:00:00 2001 From: lucasaguiar-la Date: Tue, 1 Jul 2025 09:41:14 -0300 Subject: [PATCH 13/18] [ADD] Adding self.conn for a unique connection --- src/repositories/movie_repository.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/repositories/movie_repository.py b/src/repositories/movie_repository.py index c217e055a..25b8e20b0 100644 --- a/src/repositories/movie_repository.py +++ b/src/repositories/movie_repository.py @@ -2,9 +2,11 @@ from src.models.movie import MovieModel class MovieRepository: + def __init__(self): + self.conn = get_connection() + def get_all(self): - conn = get_connection() - cur = conn.cursor() + cur = self.conn.cursor() cur.execute(''' SELECT id, movie_name, synopsis, duration, year_release, movie_gender @@ -13,7 +15,6 @@ def get_all(self): rows = cur.fetchall() cur.close() - conn.close() movies = [] for row in rows: @@ -29,8 +30,7 @@ def get_all(self): return movies def get_by_id(self, id: int): - conn = get_connection() - cur = conn.cursor() + cur = self.conn.cursor() cur.execute(''' SELECT id, movie_name, synopsis, duration, year_release, movie_gender @@ -40,7 +40,6 @@ def get_by_id(self, id: int): row = cur.fetchone() cur.close() - conn.close() if row: return { @@ -54,14 +53,16 @@ def get_by_id(self, id: int): return None def create(self, movie: MovieModel): - conn = get_connection() - cur = conn.cursor() + cur = self.conn.cursor() cur.execute(''' INSERT INTO movies (movie_name, synopsis, duration, year_release, movie_gender) VALUES (%s, %s, %s, %s, %s); ''', (movie.movie_name, movie.synopsis, movie.duration, movie.year_release, movie.movie_gender)) - conn.commit() + self.conn.commit() cur.close() - conn.close() \ No newline at end of file + + def __del__(self): + if self.conn: + self.conn.close() \ No newline at end of file From a35856a699411548bc545525eda143f96ca0eac5 Mon Sep 17 00:00:00 2001 From: lucasaguiar-la Date: Tue, 1 Jul 2025 11:27:39 -0300 Subject: [PATCH 14/18] [ADD] Duplicate treatment for repeated film --- src/controller/movie_controller.py | 14 ++++++++++---- src/database/schema.sql | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/controller/movie_controller.py b/src/controller/movie_controller.py index dad35272a..362af2983 100644 --- a/src/controller/movie_controller.py +++ b/src/controller/movie_controller.py @@ -2,16 +2,22 @@ from src.repositories.movie_repository import MovieRepository from fastapi import APIRouter, HTTPException +from psycopg2.errors import UniqueViolation router = APIRouter() movie_repository = MovieRepository() @router.post('/filmes', status_code=201) def register_movie(movie: MovieModel): - movie_repository.create(movie) - return { - 'Mensagem': 'Filme registrado com sucesso!' - } + try: + movie_repository.create(movie) + return { + 'Mensagem': 'Filme registrado com sucesso!' + } + except UniqueViolation: + raise HTTPException(status_code=409, detail=f'O filme {movie.movie_name} já está cadastrado.') + except Exception as e: + raise HTTPException(status_code=500, detail='Ocorreu um erro interno no servidor.') @router.get('/filmes') def get_all_movies(): diff --git a/src/database/schema.sql b/src/database/schema.sql index 58d5abca3..a31412d9e 100644 --- a/src/database/schema.sql +++ b/src/database/schema.sql @@ -1,6 +1,6 @@ CREATE TABLE IF NOT EXISTS movies( id SERIAL PRIMARY KEY, - movie_name TEXT NOT NULL, + movie_name TEXT NOT NULL UNIQUE, synopsis TEXT, duration INT, year_release INT, From 8cd78acd33e40473954ad2fef3cd347b26c35450 Mon Sep 17 00:00:00 2001 From: lucasaguiar-la Date: Tue, 1 Jul 2025 11:54:59 -0300 Subject: [PATCH 15/18] [ADD] Adding helthcheck to ensure the database is connected --- docker-compose.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 382fce9fc..f7824e9c2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,12 +14,18 @@ services: - ./src/database/schema.sql:/docker-entrypoint-initdb.d/schema.sql ports: - "5433:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 api: build: . container_name: fastapi_movies depends_on: - - db + db: + condition: service_healthy ports: - "8000:8000" env_file: From c7347948572747ec96cc2282bf4b68c81106ad09 Mon Sep 17 00:00:00 2001 From: lucasaguiar-la Date: Tue, 1 Jul 2025 11:59:40 -0300 Subject: [PATCH 16/18] [ADD] Adding a hasattr to avoid errors in repository conn --- src/repositories/movie_repository.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/repositories/movie_repository.py b/src/repositories/movie_repository.py index 25b8e20b0..8aa7710d7 100644 --- a/src/repositories/movie_repository.py +++ b/src/repositories/movie_repository.py @@ -64,5 +64,8 @@ def create(self, movie: MovieModel): cur.close() def __del__(self): - if self.conn: - self.conn.close() \ No newline at end of file + try: + if hasattr(self, 'conn') and self.conn: + self.conn.close() + except: + pass \ No newline at end of file From adf56a6fb180cc7e2d7bab987a89f20e4ef56597 Mon Sep 17 00:00:00 2001 From: lucasaguiar-la Date: Tue, 1 Jul 2025 12:25:09 -0300 Subject: [PATCH 17/18] [ADD] README.md --- README.md | 47 +++++++++++++++++++++++------------------ img/banner-preview.png | Bin 0 -> 4766 bytes 2 files changed, 27 insertions(+), 20 deletions(-) create mode 100644 img/banner-preview.png diff --git a/README.md b/README.md index 5c3393a97..37e27bda9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,25 @@ -![WATTIO](http://wattio.com.br/web/image/1204-212f47c3/Logo%20Wattio.png) +![WATTIO](./img/banner-preview.png) + +## Como executar a aplicação + +Para executar a aplicação, siga os seguintes passos: + +1. **Pré-requisitos:** + * [Docker](https://www.docker.com/) + * [Docker Compose](https://docs.docker.com/compose/install/) + +2. **Clone o repositório:** + ```bash + git clone https://github.com/lucasaguiar-la/desafio-backend-wattio + ``` + +3. **Inicie a aplicação:** + ```bash + docker-compose up + ``` +A API estará disponível em `http://localhost:8000/` + +--- #### Descrição @@ -15,23 +36,9 @@ O Objetivo é te desafiar e reconhecer seu esforço para aprender e se adaptar. #### 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 hendrix@wattio.com.br +- [x] Orientação a objetos (utilizar objetos, classes para manipular os filmes) +- [x] [FastAPI](https://fastapi.tiangolo.com/) (API com documentação auto gerada) +- [x] [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 ``` +- [x] 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) -Salve! +--- diff --git a/img/banner-preview.png b/img/banner-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..ef5a7290b9089a836c08690f6151b4df206bb493 GIT binary patch literal 4766 zcmeHL={FP(7amJ~){vwU29=s5*(#dFmaULwG{V@|WE-RzG>Ae*MP=VpXr?ULx2(xF z#3VDMgfUHH8S9Lh*YEuw-f!=Rd+xc_M4H2*gt6l{tJ0{}#%{xu#zcJ9%AkSEN>!~lRFK0)0N_T6>MnXMGXw(U~wAck`FwS&kyxQ1YZZkeMy<_M^Eh=={ggG4xVb=IT~n z>rTXGl^}K-`WVq>lN&9+S+Fa?Mh?)fPf#@r2t0_fdF-WUcmp||4ag~ zhLD{PZs2@n!OH-L6QbN)PX;!tnod}sXWc&1|FqL=wO#CbLo)M%SZT`nSFuj=O<_Rx zt!|`1MfJ~sXkOPAyn+UMQ*Y$RLB4RWS;?pZG~g(ql5cKu3lqQGpa|LU(@H&V3zV2>%QNcV!-QXcu{^z5u+i*YJtSwGJdL( zenQJ~TZz#AEtZ8e3?XfS#leu(QQt8%Du1c3Wg}qf(7`8bPq14~THkl3qWe z;5X|^899PObI6GAu+;kkJ~miMzVs?4Hobn^vEGH=FYA zD%9^$V1TxH zdH)53)|r=0Cfwp8O+r*~;vcf69!6_D`gTz*wvc-?%59|;sU~OzAAmb3BycBUJ){5V zeVd9+K)18R5Zk>wn>(B35&dfy$acQdG$@;=F>>$Ha(;j(tGTTWjt`qvuJ$0^ZT$1R zV1)7)2W980qS3Lo1lC|sjG~l2(gJC_=S%q>0=3ed#Z`k+hKX zPhoMbBu6^ZdK@>A8=u~WtG|lElDAec)KVJ(1C)Tr%+HePm`3aB2~eDAV1q<|YW~fg zje3E3>Y_gt!y&ugF|W>7i{^|=tvkM$5x=_DfY}&Vs2O2haB;dfO@qg{lEa!jmWUd< z_q`FzbR!%j6Dj9=?-U)gOa63eP#jnN#-3l+S&H4o0NdS@1yd~*bd5N1B6)qG;;45w z2VncNh88{#@lTc2YFO>+H#JU7q@d=jq}K2$mi?z>W3SW5{e2H{^^1` zc+SNEow_sT|K=2k^r5BlzB|6A5wAi7CNGA#0DT#Xf#eecJ@L<)qjNc;#s+5~-KM+E zEwKLkV(n|As~AV5k_(Sm!>rr!J?F0*w=~+yH%|?}NYuCSl7(mEi=$CKgg<2P?X;$m zI$W6^cVO#8Xl5rdgeR2tEx3_VWL*IpMOwPd!eaH&B58ZtqH*e?yvO7#8uFlmk(^!F zoBoye{oI3Q{=hc`%=|+k@r3t^i!-CEd2SPHr$R}Dn3s(d?JqA&IeB+u$Ma-QT4;71 z5R+`Lc$AVTh0u$J7bxEIMpG7bUyoFx>?XN1_y}tPnQ6 z@G#V~e|o|xz#yqoys6N7m$Um??`Us5$@<%3#T?wO+|OK-ersN|x3tFufl72kX{GB& z!ie$7aK1i)_?3~lU#~@<~%~^ zd5;DEBJz$ui4mlPa~Zu45tHfe8spxDv1v~d^<&1H^WmZe+ncv(`Qt@BFP%3=)yf3< z--)h$D$Jq*+k)TwR4Kj5eJhc+zcT7468$GW8K>}NWoZgW4L;#ID*8FmaH}+qn%jVt z#au&`@FxNc#cI*!H~X*pr6VFWpO=p~Eca1abwu??M&q~;-S8l?4biBu+e4f)g{kzDL0cH6df*-Z}9qroufI>!;J zs?(e|HjAi<7_fKY&S6g{imXZyp?yfJa>Ep@Gb$qBbZ4q_$@(2xEHu0;>0VEfeVmJ8 z)zi7q8eK=lue$vufiB?!*@PDWZ^4~|3p(*X}EpL{SH&STbVFL-6Mj;pCzh2M-;TJY$GY)5FO|p)*zuqW8l}Zu1nsxeZql*REufbaXY} zaBKvUnVr(QIDw<@DWhxH6Cdcyil~zksMVYBpLI>wFN8W@bB2u#EO#&Bem_4RA48Rg z+OW!EZ65xJ9T~c^&Nj2ieE#|j2_42F9$J3u+R2t06Xw`?F+9Z*9=l+^rYe!V*bSHV z#{HwB`P<_OCcn3zI^s6>X&B1e(=v#>r6la#=ZBRz=_X$1!}71E9khH2f0Iwj(hQwG zF4!bB6CL);0g`QMjQ{e+?S1`_kHPLZE=HkKLG9$w!R?WrN`9{0qEe+)`|exZM0A_B z*EJ`8H`v+%nL^}rEdHLNzHgzx$J^Vbv*Fvs%937Q{vsMv*B+V;}# zm&R%?!A>unBkX2kcO)Bkla<#xBNZ|$XF^V@78Hw^R^4Z~`PmT>Pn1-OYHe&vtRj8e z8|$@8$X3TAw)c`uL~>o|gc!2Zk3qxOZ|M>GnV%QR@eJumoyemkV1u^XS!U^R4d(9* z>k<_cugR+9K|QD-!9|O~{(b}QJ^03X1Q;_>a_vHvlPCO_HzVd?o)-RM!=1-fjxQjT zrS(X2@EA@cBR8dJyDVlVSn-+Gomavs8O-a(?Tz=WERD0hJw8RyTTUC?=2c`|*9W)% zV5^HVOZxvzNoPU8!r0`m_=v;Nsxb}^i&JnPuR7%~yoBCaS_dVR=BW)A@O~9mp0PMn z9mb+bC+9LGc2@T`NTL50sQt8<VNp7Z(*~Vuw3+Wd0dzgX_%}1PUdvLe*9ZBjID? zxW(bO3H5xRV}E;0jJNe_+xvaSKmHk`Ag|o2Tu zI4*@ZGBttW<&+=$7ol~F9Bt*jNvQBBzr>|4T76lrq_K4CYEG-nNk3=q2A`V^yS(wT z_=zES#VQ9rP3`iB@d48lfM;~P&xD$XH_IqUmzm7hDhE6bkXhRF)0>;jEop^<-_AXY zY%|Oi7`6lRX=H1VH1GX_6D~_Yiod)w2@pmdXOCsfp-=;{%}Le!qmY)SO=n(Z)Sf#^ z@|N`P;uXUtJ43+o0R77?{kHQN%a*DT5Qr|?T@}FF>$M?R&1;NXh>-}Kfw$Sd~E<^kg2_dftB!BIk}_CDQ2Y z`)heEQJ!-BoVy$=KvOTs{!VV2$|W}EQ`%;&ZVdJ?GO+Ha63OYx2{pb6fBnk1=PG>1 zATG^pM7C17U6M?zDq^;yT<@LE;?DpcW@AeVp_eLT;#kpVIM;V&nuhXd;f-=-{Su>? zfr_WMq3t@L8eaXRQ>z6VnI1m%4JPyrP{ptM%TfC-lkgObFqxH3Yr}2^o4jNGB1E_>aTC!)|@R z*m)noviGRpnI2YFz#y6oV(l(i2&52&#FGiKI9-+<26K=k=b=@ReG)?`6W11aK9ULsI6z` zfxemxC+9$MP%s1+cKCsi^)3uB3MrG*0Qf?Ac^)WA0FM6O{jVh0fqvl)vhlMmG~ItE O0nCgojqnDpasL5rS!Q(r literal 0 HcmV?d00001 From ea29b8db61948c3247e31bab5df1dc4436e7bac5 Mon Sep 17 00:00:00 2001 From: lucasaguiar-la Date: Tue, 1 Jul 2025 12:31:49 -0300 Subject: [PATCH 18/18] [ADD] README.md --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 37e27bda9..548d7291d 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,18 @@ Para executar a aplicação, siga os seguintes passos: ``` A API estará disponível em `http://localhost:8000/` +4. **Cadastrar um filme** +Ao fazer uma requisição [POST] para `/filmes`, envie o seguinte JSON no corpo da requisição: + + ```json + { + "movie_name": "O Senhor dos Anéis: O Retorno do Rei", + "synopsis": "Gandalf e Aragorn lideram o Mundo dos Homens contra o exército de Sauron para desviar o olhar de Frodo e Sam quando eles se aproximam á Montanha da Perdição com o Um Anel.", + "duration": 201, + "year_release": 2003, + "movie_gender": "Fantasia" + } + ``` --- #### Descrição