Refactor models and endpoints, add logging, and improve error handling.
This commit is contained in:
+55
-18
@@ -1,10 +1,14 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
import bcrypt
|
||||||
|
import requests
|
||||||
|
|
||||||
from lib.database.database import db_run
|
from lib.database.database import db_run
|
||||||
from models import Message
|
from models import Message
|
||||||
from models.user import UserCredentials
|
from models.user import UserCredentials
|
||||||
from models.movie import Movie
|
from models.movie import MovieIn, Movie
|
||||||
from models.card import Card
|
from models.card import Card
|
||||||
|
from lib.settings import settings
|
||||||
import bcrypt
|
from lib.logger import logger
|
||||||
|
|
||||||
|
|
||||||
def add_user(user: UserCredentials):
|
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}')"
|
sql = f"INSERT INTO users (username, password) VALUES ('{user.username}', '{hashed_password}')"
|
||||||
try:
|
try:
|
||||||
db_run(sql)
|
db_run(sql)
|
||||||
except:
|
except Exception as e:
|
||||||
return Message(message="User already exists", message_type="error")
|
return Message(message=f"User already exists. Error: {e}", message_type="error")
|
||||||
|
|
||||||
return Message(message="User added successfully", message_type="success")
|
return Message(message="User added successfully", message_type="success")
|
||||||
|
|
||||||
|
|
||||||
def remove_user(user_id: str):
|
def remove_user(user_id: str):
|
||||||
|
logger.debug(f"Removing user {user_id}")
|
||||||
sql = f"DELETE FROM users WHERE id = '{user_id}'"
|
sql = f"DELETE FROM users WHERE id = '{user_id}'"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db_run(sql)
|
db_run(sql)
|
||||||
except:
|
except Exception as e:
|
||||||
return Message(message="User not found", message_type="error")
|
return Message(message=f"User not found. Error: {e}", message_type="error")
|
||||||
|
|
||||||
return Message(message="User removed successfully", message_type="success")
|
return Message(message="User removed successfully", message_type="success")
|
||||||
|
|
||||||
|
|
||||||
def add_movie(movie: Movie):
|
def add_movie(movie: MovieIn):
|
||||||
sql = "INSERT INTO movies (name, imdb_id, actors, release_date, plot, showtime) VALUES (?, ?, ?, ?, ?, ?)"
|
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:
|
try:
|
||||||
db_run(
|
db_run(
|
||||||
sql,
|
sql,
|
||||||
(
|
(
|
||||||
movie.name,
|
new_movie.name,
|
||||||
movie.imdb_id,
|
new_movie.imdb_id,
|
||||||
movie.actors,
|
new_movie.actors,
|
||||||
movie.release_date,
|
new_movie.release_date,
|
||||||
movie.plot,
|
new_movie.plot,
|
||||||
movie.showtime,
|
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")
|
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")
|
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}'"
|
sql = f"UPDATE movies SET is_watched = TRUE WHERE id = '{movie_id}'"
|
||||||
try:
|
try:
|
||||||
db_run(sql)
|
db_run(sql)
|
||||||
except:
|
except Exception as e:
|
||||||
return Message(message="Failed to set movie as watched", message_type="error")
|
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")
|
return Message(message="Movie set as watched successfully", message_type="success")
|
||||||
|
|
||||||
|
|||||||
+12
-15
@@ -1,25 +1,22 @@
|
|||||||
from models.movie import Movie
|
from models.movie import Movie
|
||||||
from lib.database.database import get_db
|
from lib.database.database import get_db
|
||||||
|
from lib.logger import logger
|
||||||
|
|
||||||
|
|
||||||
|
@logger.catch
|
||||||
def get_all_movies() -> list[Movie]:
|
def get_all_movies() -> list[Movie]:
|
||||||
|
logger.info("Getting all movies")
|
||||||
sql = "SELECT * FROM movies"
|
sql = "SELECT * FROM movies"
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
result = conn.execute(sql).fetchall()
|
result = conn.execute(sql).df()
|
||||||
|
result_dict = result.to_dict(orient="records")
|
||||||
movies = []
|
movies = []
|
||||||
for row in result:
|
for row in result_dict:
|
||||||
movie = Movie(
|
row["id"] = str(row["id"])
|
||||||
id=str(row[0]),
|
logger.debug(f"Found movie: {row}")
|
||||||
name=row[1],
|
print("Type of name:", type(row["name"]))
|
||||||
imdb_id=row[2],
|
movie = Movie(**row)
|
||||||
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],
|
|
||||||
)
|
|
||||||
movies.append(movie)
|
movies.append(movie)
|
||||||
|
|
||||||
return movies
|
return movies
|
||||||
@@ -28,7 +25,7 @@ def get_all_movies() -> list[Movie]:
|
|||||||
|
|
||||||
|
|
||||||
def get_next_movie() -> Movie | None:
|
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:
|
with get_db() as conn:
|
||||||
result = conn.execute(sql).fetchdf()
|
result = conn.execute(sql).fetchdf()
|
||||||
if result.empty:
|
if result.empty:
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from models.user import UserIn, UserOut
|
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
|
from lib.logger import logger
|
||||||
|
|
||||||
|
|
||||||
def set_last_login(user_id: str) -> datetime:
|
def set_last_login(user_id: str) -> datetime:
|
||||||
|
logger.debug(f"Setting last login for user {user_id}")
|
||||||
cur_time = datetime.now()
|
cur_time = datetime.now()
|
||||||
sql = f"UPDATE users SET last_login = '{cur_time}' WHERE id = '{user_id}'"
|
sql = f"UPDATE users SET last_login = '{cur_time}' WHERE id = '{user_id}'"
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
|
|||||||
+24
-28
@@ -1,44 +1,40 @@
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
from datetime import datetime
|
from datetime import datetime, date
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
class Movie(BaseModel):
|
class MovieBase(BaseModel):
|
||||||
id: Optional[str] = Field(
|
name: Optional[str] | None = Field(
|
||||||
default=None, examples=["123e4567-e89b-12d3-a456-426655440000"]
|
None, description="Name of the movie", examples=["A Maple Valley Christmas"]
|
||||||
)
|
|
||||||
name: str = Field(
|
|
||||||
description="Name of the movie", examples=["A Maple Valley Christmas"]
|
|
||||||
)
|
)
|
||||||
imdb_id: str = Field(description="IMDB ID of the movie", examples=["tt1234567"])
|
imdb_id: str = Field(description="IMDB ID of the movie", examples=["tt1234567"])
|
||||||
actors: list[str] = Field(
|
showtime: date = Field(description="Showtime of the movie", examples=["2024-12-01"])
|
||||||
description="Actors of the movie",
|
|
||||||
examples=[["Peyton List", "Andrew W. Walker"]],
|
|
||||||
)
|
|
||||||
plot: str = Field(
|
plot: str = Field(
|
||||||
description="Plot of the movie", examples=["Erica is a rancher..."]
|
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(
|
release_date: datetime | None = Field(
|
||||||
description="Poster URL of the movie", examples=["https://..."]
|
None, description="Release date of the movie", examples=["2022-11-05"]
|
||||||
)
|
)
|
||||||
showtime: datetime = Field(
|
poster_url: str | None = Field(
|
||||||
description="Showtime of the movie", examples=["2024-12-01"]
|
None, description="Poster URL of the movie", examples=["https://..."]
|
||||||
)
|
)
|
||||||
is_watched: bool = Field(
|
|
||||||
default=False,
|
|
||||||
description="Whether the movie is watched or not",
|
class Movie(MovieIn):
|
||||||
examples=[True],
|
id: Optional[str] = Field(
|
||||||
|
default=None, examples=["123e4567-e89b-12d3-a456-426655440000"]
|
||||||
)
|
)
|
||||||
created_at: datetime = Field(
|
created_at: datetime = Field(
|
||||||
default_factory=datetime.utcnow,
|
default_factory=datetime.utcnow, description="Creation date of the movie"
|
||||||
description="Creation date of the movie",
|
|
||||||
examples=[datetime.utcnow()],
|
|
||||||
)
|
)
|
||||||
updated_at: datetime = Field(
|
modified_at: datetime = Field(
|
||||||
default_factory=datetime.utcnow,
|
default_factory=datetime.utcnow, description="Modification date of the movie"
|
||||||
description="Modification date of the movie",
|
|
||||||
examples=[datetime.utcnow()],
|
|
||||||
)
|
)
|
||||||
|
|||||||
+50
-8
@@ -2,7 +2,7 @@ from fastapi import APIRouter
|
|||||||
|
|
||||||
from models import Message
|
from models import Message
|
||||||
from models.user import UserCredentials
|
from models.user import UserCredentials
|
||||||
from models.movie import Movie
|
from models.movie import MovieBase
|
||||||
from models.card import Card
|
from models.card import Card
|
||||||
from lib.database.admin import (
|
from lib.database.admin import (
|
||||||
add_user,
|
add_user,
|
||||||
@@ -22,35 +22,77 @@ router = APIRouter(
|
|||||||
|
|
||||||
@router.post("/user", response_model=Message)
|
@router.post("/user", response_model=Message)
|
||||||
async def add_user_endpoint(user: UserCredentials):
|
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)
|
return add_user(user)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/user", response_model=Message)
|
@router.delete("/user", response_model=Message)
|
||||||
async def remove_user_endpoint(user_id: str):
|
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)
|
return remove_user(user_id)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/movie", response_model=Message)
|
@router.post("/movie", response_model=Message)
|
||||||
async def add_movie_endpoint(movie: Movie):
|
async def add_movie_endpoint(movie: MovieBase):
|
||||||
"""Add a movie to the database"""
|
"""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)
|
return add_movie(movie)
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/movie", response_model=Message)
|
@router.patch("/movie", response_model=Message)
|
||||||
async def set_movie_watched_endpoint(movie_id: str):
|
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)
|
return set_movie_watched(movie_id)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/card", response_model=Message)
|
@router.post("/card", response_model=Message)
|
||||||
async def add_card_endpoint(card: Card):
|
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)
|
return add_card(card)
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/card", response_model=Message)
|
@router.delete("/card", response_model=Message)
|
||||||
async def remove_card_endpoint(card_id: str):
|
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)
|
return remove_card(card_id)
|
||||||
|
|||||||
+5
-1
@@ -12,5 +12,9 @@ router = APIRouter(
|
|||||||
|
|
||||||
@router.get("/", responses={200: {"model": list[Card]}})
|
@router.get("/", responses={200: {"model": list[Card]}})
|
||||||
async def get_cards() -> list[Card]:
|
async def get_cards() -> list[Card]:
|
||||||
"""Get all cards"""
|
"""Get all cards
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[Card]: List of cards
|
||||||
|
"""
|
||||||
return get_all_cards()
|
return get_all_cards()
|
||||||
|
|||||||
+5
-1
@@ -18,7 +18,11 @@ async def get_movies():
|
|||||||
|
|
||||||
@router.get("/next", response_model=Movie)
|
@router.get("/next", response_model=Movie)
|
||||||
async def get_next_movie_endpoint():
|
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()
|
next_movie = get_next_movie()
|
||||||
if next_movie is None:
|
if next_movie is None:
|
||||||
return Response(
|
return Response(
|
||||||
|
|||||||
+21
-5
@@ -17,7 +17,11 @@ router = APIRouter(
|
|||||||
responses={200: {"model": list[UserOut]}, 404: {"model": Message}},
|
responses={200: {"model": list[UserOut]}, 404: {"model": Message}},
|
||||||
)
|
)
|
||||||
async def get_users() -> list[UserOut]:
|
async def get_users() -> list[UserOut]:
|
||||||
"""Get all users"""
|
"""Get all users
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[UserOut]: List of users
|
||||||
|
"""
|
||||||
return get_all_users()
|
return get_all_users()
|
||||||
|
|
||||||
|
|
||||||
@@ -28,11 +32,20 @@ async def get_users() -> list[UserOut]:
|
|||||||
400: {"model": Message},
|
400: {"model": Message},
|
||||||
404: {"model": Message},
|
404: {"model": Message},
|
||||||
},
|
},
|
||||||
|
summary="Login a user",
|
||||||
|
description="Login a user by providing their username and password",
|
||||||
)
|
)
|
||||||
async def login(user: UserCredentials):
|
async def login(user: UserCredentials) -> UserOut | Response:
|
||||||
"""Login a user"""
|
"""Login a user by providing their username and password
|
||||||
user_from_db: UserIn = get_user_by_username(user.username)
|
|
||||||
|
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:
|
if user_from_db is None:
|
||||||
|
# User not found
|
||||||
return Response(
|
return Response(
|
||||||
status_code=404,
|
status_code=404,
|
||||||
media_type="application/json",
|
media_type="application/json",
|
||||||
@@ -40,11 +53,14 @@ async def login(user: UserCredentials):
|
|||||||
message="User not found", message_type="error"
|
message="User not found", message_type="error"
|
||||||
).model_dump_json(),
|
).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")):
|
if bcrypt.checkpw(password, user_from_db.password.encode("utf-8")):
|
||||||
|
# Login successful
|
||||||
return UserOut(**user_from_db.model_dump())
|
return UserOut(**user_from_db.model_dump())
|
||||||
|
|
||||||
|
# Invalid password
|
||||||
return Response(
|
return Response(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
media_type="application/json",
|
media_type="application/json",
|
||||||
|
|||||||
Reference in New Issue
Block a user