Refactor models and endpoints, add logging, and improve error handling.

This commit is contained in:
Esa Kataja
2024-12-01 22:29:00 +02:00
parent cecd9a4458
commit b5e693ab9f
8 changed files with 174 additions and 77 deletions
+55 -18
View File
@@ -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")
+12 -15
View File
@@ -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:
+2 -1
View File
@@ -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:
+24 -28
View File
@@ -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"
)
+50 -8
View File
@@ -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)
+5 -1
View File
@@ -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()
+5 -1
View File
@@ -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(
+21 -5
View File
@@ -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",