diff --git a/src/lib/database/admin.py b/src/lib/database/admin.py index 77e2ef5..31c764c 100644 --- a/src/lib/database/admin.py +++ b/src/lib/database/admin.py @@ -1,10 +1,14 @@ +from datetime import datetime +import bcrypt +import requests + from lib.database.database import db_run from models import Message from models.user import UserCredentials -from models.movie import Movie +from models.movie import MovieIn, Movie from models.card import Card - -import bcrypt +from lib.settings import settings +from lib.logger import logger def add_user(user: UserCredentials): @@ -14,40 +18,71 @@ def add_user(user: UserCredentials): sql = f"INSERT INTO users (username, password) VALUES ('{user.username}', '{hashed_password}')" try: db_run(sql) - except: - return Message(message="User already exists", message_type="error") + except Exception as e: + return Message(message=f"User already exists. Error: {e}", message_type="error") return Message(message="User added successfully", message_type="success") def remove_user(user_id: str): + logger.debug(f"Removing user {user_id}") sql = f"DELETE FROM users WHERE id = '{user_id}'" try: db_run(sql) - except: - return Message(message="User not found", message_type="error") + except Exception as e: + return Message(message=f"User not found. Error: {e}", message_type="error") return Message(message="User removed successfully", message_type="success") -def add_movie(movie: Movie): - sql = "INSERT INTO movies (name, imdb_id, actors, release_date, plot, showtime) VALUES (?, ?, ?, ?, ?, ?)" +def add_movie(movie: MovieIn): + logger.info(f"Adding movie {movie.imdb_id}") + logger.debug(f"Requesting movie data from OMDB: {movie.imdb_id}") + request_url = "https://omdbapi.com/" + request_params = { + "apikey": settings.OMDB_API_KEY, + "i": movie.imdb_id, + } + + response = requests.get(request_url, params=request_params) + if response.status_code != 200: + logger.error(f"Failed to add movie {movie.imdb_id}") + return Message(message="Failed to add movie", message_type="error") + movie_data = response.json() + logger.debug(f"Movie data received from OMDB: {movie_data}") + if "Released" in movie_data: + movie_data["Released"] = datetime.strptime(movie_data["Released"], "%d %b %Y") + + new_movie = Movie( + name=movie_data["Title"], + imdb_id=movie_data["imdbID"], + actors=[actor.strip() for actor in movie_data["Actors"].split(",")], + release_date=movie_data["Released"], + plot=movie.plot, + poster_url=movie_data["Poster"], + showtime=movie.showtime, + ) + logger.debug(f"Movie data: {new_movie.model_dump()}") + sql = "INSERT INTO movies (name, imdb_id, actors, release_date, plot, showtime, poster_url) VALUES (?, ?, ?, ?, ?, ?, ?)" try: db_run( sql, ( - movie.name, - movie.imdb_id, - movie.actors, - movie.release_date, - movie.plot, - movie.showtime, + new_movie.name, + new_movie.imdb_id, + new_movie.actors, + new_movie.release_date, + new_movie.plot, + new_movie.showtime, + new_movie.poster_url, ), ) - except: + except Exception as e: + logger.error(f"Failed to add movie {movie.imdb_id}. Error: {e}") return Message(message="Failed to add movie", message_type="error") + logger.info(f"Movie {movie.imdb_id} added successfully") return Message(message="Movie added successfully", message_type="success") @@ -55,8 +90,10 @@ def set_movie_watched(movie_id: str): sql = f"UPDATE movies SET is_watched = TRUE WHERE id = '{movie_id}'" try: db_run(sql) - except: - return Message(message="Failed to set movie as watched", message_type="error") + except Exception as e: + return Message( + message=f"Failed to set movie as watched. Error: {e}", message_type="error" + ) return Message(message="Movie set as watched successfully", message_type="success") diff --git a/src/lib/database/movie.py b/src/lib/database/movie.py index 3c4c38d..0890e8b 100644 --- a/src/lib/database/movie.py +++ b/src/lib/database/movie.py @@ -1,25 +1,22 @@ from models.movie import Movie from lib.database.database import get_db +from lib.logger import logger +@logger.catch def get_all_movies() -> list[Movie]: + logger.info("Getting all movies") sql = "SELECT * FROM movies" with get_db() as conn: - result = conn.execute(sql).fetchall() + result = conn.execute(sql).df() + result_dict = result.to_dict(orient="records") movies = [] - for row in result: - movie = Movie( - id=str(row[0]), - name=row[1], - imdb_id=row[2], - actors=row[3], - release_date=row[4], - plot=row[5], - showtime=row[6], - is_watched=row[7], - created_at=row[8], - updated_at=row[9], - ) + for row in result_dict: + row["id"] = str(row["id"]) + logger.debug(f"Found movie: {row}") + print("Type of name:", type(row["name"])) + movie = Movie(**row) + movies.append(movie) return movies @@ -28,7 +25,7 @@ def get_all_movies() -> list[Movie]: def get_next_movie() -> Movie | None: - sql = "SELECT * FROM movies WHERE is_watched = FALSE AND showtime > CURRENT_TIMESTAMP ORDER BY showtime ASC" + sql = "SELECT * FROM movies WHERE is_watched = FALSE AND showtime = CURRENT_DATE ORDER BY showtime ASC" with get_db() as conn: result = conn.execute(sql).fetchdf() if result.empty: diff --git a/src/lib/database/user.py b/src/lib/database/user.py index f383748..66f0abd 100644 --- a/src/lib/database/user.py +++ b/src/lib/database/user.py @@ -1,11 +1,12 @@ from datetime import datetime from models.user import UserIn, UserOut -from lib.database.database import get_db, db_run +from lib.database.database import get_db from lib.logger import logger def set_last_login(user_id: str) -> datetime: + logger.debug(f"Setting last login for user {user_id}") cur_time = datetime.now() sql = f"UPDATE users SET last_login = '{cur_time}' WHERE id = '{user_id}'" with get_db() as conn: diff --git a/src/models/movie.py b/src/models/movie.py index bfbe133..dbcfbbc 100644 --- a/src/models/movie.py +++ b/src/models/movie.py @@ -1,44 +1,40 @@ from pydantic import BaseModel, Field -from datetime import datetime +from datetime import datetime, date from typing import Optional -class Movie(BaseModel): - id: Optional[str] = Field( - default=None, examples=["123e4567-e89b-12d3-a456-426655440000"] - ) - name: str = Field( - description="Name of the movie", examples=["A Maple Valley Christmas"] +class MovieBase(BaseModel): + name: Optional[str] | None = Field( + None, description="Name of the movie", examples=["A Maple Valley Christmas"] ) imdb_id: str = Field(description="IMDB ID of the movie", examples=["tt1234567"]) - actors: list[str] = Field( - description="Actors of the movie", - examples=[["Peyton List", "Andrew W. Walker"]], - ) + showtime: date = Field(description="Showtime of the movie", examples=["2024-12-01"]) plot: str = Field( description="Plot of the movie", examples=["Erica is a rancher..."] ) - release_date: datetime = Field( - description="Release date of the movie", examples=["2022-11-05"] + + +class MovieIn(MovieBase): + actors: Optional[list[str]] | None = Field( + None, + description="Actors of the movie", + examples=[["Peyton List", "Andrew W. Walker"]], ) - poster_url: str = Field( - description="Poster URL of the movie", examples=["https://..."] + release_date: datetime | None = Field( + None, description="Release date of the movie", examples=["2022-11-05"] ) - showtime: datetime = Field( - description="Showtime of the movie", examples=["2024-12-01"] + poster_url: str | None = Field( + None, description="Poster URL of the movie", examples=["https://..."] ) - is_watched: bool = Field( - default=False, - description="Whether the movie is watched or not", - examples=[True], + + +class Movie(MovieIn): + id: Optional[str] = Field( + default=None, examples=["123e4567-e89b-12d3-a456-426655440000"] ) created_at: datetime = Field( - default_factory=datetime.utcnow, - description="Creation date of the movie", - examples=[datetime.utcnow()], + default_factory=datetime.utcnow, description="Creation date of the movie" ) - updated_at: datetime = Field( - default_factory=datetime.utcnow, - description="Modification date of the movie", - examples=[datetime.utcnow()], + modified_at: datetime = Field( + default_factory=datetime.utcnow, description="Modification date of the movie" ) diff --git a/src/routes/admin.py b/src/routes/admin.py index fc493c1..96999f2 100644 --- a/src/routes/admin.py +++ b/src/routes/admin.py @@ -2,7 +2,7 @@ from fastapi import APIRouter from models import Message from models.user import UserCredentials -from models.movie import Movie +from models.movie import MovieBase from models.card import Card from lib.database.admin import ( add_user, @@ -22,35 +22,77 @@ router = APIRouter( @router.post("/user", response_model=Message) async def add_user_endpoint(user: UserCredentials): - """Add a user to the database""" + """Add a user to the database + + Args: + user (UserCredentials): The user to add + + Returns: + Message: A message indicating the result of the operation + """ return add_user(user) @router.delete("/user", response_model=Message) async def remove_user_endpoint(user_id: str): - """Remove a user from the database""" + """Remove a user from the database + + Args: + user_id (str): The id of the user to remove + + Returns: + Message: A message indicating the result of the operation + """ return remove_user(user_id) @router.post("/movie", response_model=Message) -async def add_movie_endpoint(movie: Movie): - """Add a movie to the database""" +async def add_movie_endpoint(movie: MovieBase): + """Add a movie to the database + + Args: + movie (MovieBase): The movie to add + + Returns: + Message: A message indicating the result of the operation + """ return add_movie(movie) @router.patch("/movie", response_model=Message) async def set_movie_watched_endpoint(movie_id: str): - """Set movie as watched""" + """Set movie as watched + + Args: + movie_id (str): The id of the movie to set as watched + + Returns: + Message: A message indicating the result of the operation + """ return set_movie_watched(movie_id) @router.post("/card", response_model=Message) async def add_card_endpoint(card: Card): - """Add a card to the database""" + """Add a card to the database + + Args: + card (Card): The card to add + + Returns: + Message: A message indicating the result of the operation + """ return add_card(card) @router.delete("/card", response_model=Message) async def remove_card_endpoint(card_id: str): - """Remove a card from the database""" + """Remove a card from the database + + Args: + card_id (str): The id of the card to remove + + Returns: + Message: A message indicating the result of the operation + """ return remove_card(card_id) diff --git a/src/routes/card.py b/src/routes/card.py index 4cad2d8..ae3b95a 100644 --- a/src/routes/card.py +++ b/src/routes/card.py @@ -12,5 +12,9 @@ router = APIRouter( @router.get("/", responses={200: {"model": list[Card]}}) async def get_cards() -> list[Card]: - """Get all cards""" + """Get all cards + + Returns: + list[Card]: List of cards + """ return get_all_cards() diff --git a/src/routes/movie.py b/src/routes/movie.py index 995aa71..085b28b 100644 --- a/src/routes/movie.py +++ b/src/routes/movie.py @@ -18,7 +18,11 @@ async def get_movies(): @router.get("/next", response_model=Movie) async def get_next_movie_endpoint(): - """Get the next movie to watch""" + """Get the next movie to watch + + Returns: + Movie: The next movie to watch + """ next_movie = get_next_movie() if next_movie is None: return Response( diff --git a/src/routes/user.py b/src/routes/user.py index 21b456f..6c5b617 100644 --- a/src/routes/user.py +++ b/src/routes/user.py @@ -17,7 +17,11 @@ router = APIRouter( responses={200: {"model": list[UserOut]}, 404: {"model": Message}}, ) async def get_users() -> list[UserOut]: - """Get all users""" + """Get all users + + Returns: + list[UserOut]: List of users + """ return get_all_users() @@ -28,11 +32,20 @@ async def get_users() -> list[UserOut]: 400: {"model": Message}, 404: {"model": Message}, }, + summary="Login a user", + description="Login a user by providing their username and password", ) -async def login(user: UserCredentials): - """Login a user""" - user_from_db: UserIn = get_user_by_username(user.username) +async def login(user: UserCredentials) -> UserOut | Response: + """Login a user by providing their username and password + + Returns: + UserOut: User information + Response: Error message + """ + # Get the user from the database + user_from_db: UserIn | None = get_user_by_username(user.username) if user_from_db is None: + # User not found return Response( status_code=404, media_type="application/json", @@ -40,11 +53,14 @@ async def login(user: UserCredentials): message="User not found", message_type="error" ).model_dump_json(), ) - password = user.password.encode("utf-8") + # Check the password + password = user.password.encode("utf-8") if bcrypt.checkpw(password, user_from_db.password.encode("utf-8")): + # Login successful return UserOut(**user_from_db.model_dump()) + # Invalid password return Response( status_code=400, media_type="application/json",