Compare commits
9
Commits
6e8b62ffe1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2beef62041 | ||
|
|
9c2a3ac42e | ||
|
|
b5e693ab9f | ||
|
|
cecd9a4458 | ||
|
|
ab7372cc9c | ||
|
|
6d7eae5ea4 | ||
|
|
10303accd2 | ||
|
|
7f6dcefb91 | ||
|
|
960856cdd8 |
+1
-1
@@ -9,7 +9,7 @@ wheels/
|
||||
# Virtual environments
|
||||
.venv
|
||||
|
||||
.env
|
||||
.env*
|
||||
.ruff_cache
|
||||
|
||||
*.sqlite
|
||||
|
||||
@@ -5,6 +5,7 @@ WORKDIR /app
|
||||
ENV UV_COMPILE_BYTECODE=1
|
||||
|
||||
ADD pyproject.toml uv.lock .
|
||||
RUN mkdir -p /app/data
|
||||
RUN uv sync --frozen --no-dev
|
||||
|
||||
ADD /src .
|
||||
|
||||
@@ -6,6 +6,18 @@ services:
|
||||
dockerfile: Dockerfile
|
||||
image: pjl-backend:1.0rc1
|
||||
|
||||
env_file:
|
||||
- .env.prod
|
||||
|
||||
Environment:
|
||||
DB_URL: /app/data/pjl.duckdb
|
||||
DEFAULT_AVATAR_URL: https://api.dicebear.com/9.x/adventurer-neutral/svg?seed=Kingston
|
||||
OMDB_API_KEY: ${OMDB_API_KEY}
|
||||
STAGE: prod
|
||||
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
|
||||
# Host the FastAPI application on port 8000
|
||||
ports:
|
||||
- "8000:8000"
|
||||
|
||||
+2
-1
@@ -14,6 +14,7 @@ dependencies = [
|
||||
"rich>=13.9.4",
|
||||
"uvicorn>=0.32.0",
|
||||
"pandas>=2.2.3",
|
||||
"requests>=2.32.3",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
@@ -23,5 +24,5 @@ dev = [
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
extend-select = ["C4", "SIM", "TCH", "ERA", "FAST", "DOC"]
|
||||
lint.extend-select = ["C4", "SIM", "TCH", "ERA", "FAST", "DOC"]
|
||||
target-version = "py312"
|
||||
+55
-18
@@ -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")
|
||||
|
||||
|
||||
@@ -3,12 +3,14 @@ import duckdb
|
||||
import bcrypt
|
||||
|
||||
from lib import settings
|
||||
from lib.logger import logger
|
||||
|
||||
|
||||
@contextmanager
|
||||
def get_db():
|
||||
try:
|
||||
conn = duckdb.connect(database=settings.db_url)
|
||||
logger.debug(f"Connecting to database {settings.db_url.absolute()}")
|
||||
conn = duckdb.connect(database=settings.db_url.absolute())
|
||||
yield conn
|
||||
finally:
|
||||
conn.close()
|
||||
@@ -22,10 +24,11 @@ def db_run(sql, values: tuple = None):
|
||||
|
||||
def init_db():
|
||||
if settings.db_url.exists():
|
||||
pass
|
||||
# TODO: Development feature. Remove this in production
|
||||
from os import unlink
|
||||
# from os import unlink
|
||||
|
||||
unlink(settings.db_url)
|
||||
# unlink(settings.db_url)
|
||||
sql = ""
|
||||
with open("lib/database/database.sql") as f:
|
||||
sql = f.read()
|
||||
@@ -36,8 +39,5 @@ def init_db():
|
||||
|
||||
hashed_password = bcrypt.hashpw("password".encode("utf-8"), bcrypt.gensalt())
|
||||
|
||||
sql = f"INSERT INTO users (username, password, is_admin) VALUES ('admin', '{hashed_password.decode('utf-8')}', true)"
|
||||
db_run(sql)
|
||||
|
||||
sql = f"INSERT INTO users (username, password, is_admin) VALUES ('test', '{hashed_password.decode('utf-8')}', false)"
|
||||
sql = f"INSERT INTO users (username, password, is_admin) VALUES ('admin', '{hashed_password.decode('utf-8')}', true), ('kessinen', '{hashed_password.decode('utf-8')}', false), ('jensku', '{hashed_password.decode('utf-8')}', false), ('emppu', '{hashed_password.decode('utf-8')}', false)"
|
||||
db_run(sql)
|
||||
|
||||
@@ -53,15 +53,21 @@ CREATE TABLE log (
|
||||
);
|
||||
|
||||
-- Add placeholder data
|
||||
|
||||
INSERT INTO movies (name, imdb_id, actors, release_date, plot, poster_url, showtime)
|
||||
VALUES ('A Maple Valley Christmas',
|
||||
'tt21841642',
|
||||
['Peyton List', 'Andrew W. Walker', 'Frances Flanagan'],
|
||||
VALUES ('Christmas Sail',
|
||||
'tt15758364',
|
||||
['Katee Sackhoff', 'Patrick Sabongui', 'Terry O''Quinn'],
|
||||
'2022-11-05',
|
||||
'Erica on karjatilallinen, joka on viettänyt koko elämänsä työskennellen perhetilalla äitinsä ja sisarensa kanssa.
|
||||
Kun Aaron saapuu ja häiritsee hänen suunnitelmiaan, hän alkaa kyseenalaistaa, mitä hän oikeastaan haluaa.',
|
||||
'https://m.media-amazon.com/images/M/MV5BZjliMTRlMGItOGQzZC00NTI2LWJkODctZWZmMDIzOGRmYTQ3XkEyXkFqcGc@._V1_.jpg',
|
||||
'2024-12-01');
|
||||
'Lizin etääntynyt isä pyytää apua ja Liz palaa kotikaupunkiinsa. Hän päättää luoda täydellisen joulun tyttärelleen ja saada ihmissuhteensa kuntoon.',
|
||||
'https://m.media-amazon.com/images/M/MV5BZTA0MjA2MmMtNWZhNy00N2UyLWI5YjItZTA4ZDVkZmNmMWVjXkEyXkFqcGc@._V1_SX300.jpg',
|
||||
'2024-12-02'),
|
||||
('The Holiday Stocking',
|
||||
'tt22308008',
|
||||
['Nadine Ellis', 'Tamala Jones', 'B.J. Britt'],
|
||||
'2022-12-03','Enkeli palaa maan päälle vieraana ja saa mahdollisuuden auttaa sisariaan sovintoon - asia mitä hän ei onnistunut tekemään elinaikanaan.',
|
||||
'https://m.media-amazon.com/images/M/MV5BZTI5OTc5OTYtOWVlMC00YmRhLWFhMmQtMGIwYjNmZTI1Y2Y5XkEyXkFqcGc@._V1_SX300.jpg',
|
||||
'2024-12-03'),;
|
||||
|
||||
INSERT INTO cards (title, description, point_value)
|
||||
VALUES
|
||||
|
||||
+12
-15
@@ -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:
|
||||
|
||||
+18
-13
@@ -1,24 +1,29 @@
|
||||
from datetime import datetime
|
||||
|
||||
from models.user import UserIn, UserOut
|
||||
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:
|
||||
conn.execute(sql)
|
||||
|
||||
return cur_time
|
||||
|
||||
|
||||
def get_user_by_username(username) -> UserIn | None:
|
||||
sql = f"SELECT * FROM users WHERE username = '{username}'"
|
||||
with get_db() as conn:
|
||||
result = conn.execute(sql).fetchone()
|
||||
result = conn.execute(sql).df().to_dict(orient="records")[0]
|
||||
if result is not None:
|
||||
return UserIn(
|
||||
id=str(result[0]),
|
||||
username=result[1],
|
||||
password=result[2],
|
||||
email=result[3],
|
||||
avatar_url=result[4],
|
||||
is_active=result[5],
|
||||
is_admin=result[6],
|
||||
last_login=result[7],
|
||||
created_at=result[8],
|
||||
updated_at=result[9],
|
||||
)
|
||||
last_login = set_last_login(result["id"])
|
||||
result["id"] = str(result["id"])
|
||||
result["last_login"] = last_login
|
||||
return UserIn(**result)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
+2
-4
@@ -4,14 +4,12 @@ from pathlib import Path
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
db_url: Path = Field(Path("pjl.duckdb"), env="DB_URL")
|
||||
db_url: Path = Field(Path("pjl_default.duckdb"), env="DB_URL")
|
||||
default_avatar_url: str = Field(
|
||||
"https://api.dicebear.com/9.x/adventurer-neutral/svg?seed=Kingston",
|
||||
env="DEFAULT_AVATAR_URL",
|
||||
)
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
OMDB_API_KEY: str = Field(env="OMDB_API_KEY")
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
+24
-28
@@ -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
@@ -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
@@ -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
@@ -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(
|
||||
|
||||
+20
-4
@@ -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)
|
||||
"""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",
|
||||
|
||||
@@ -57,6 +57,54 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/2a/c74052e54162ec639266d91539cca7cbf3d1d3b8b36afbfeaee0ea6a1702/bcrypt-4.2.0-cp39-abi3-win_amd64.whl", hash = "sha256:61ed14326ee023917ecd093ee6ef422a72f3aec6f07e21ea5f10622b735538a9", size = 151717 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2024.8.30"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/0b/4b7a70987abf9b8196845806198975b6aab4ce016632f817ad758a5aa056/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", size = 194445 },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/89/354cc56cf4dd2449715bc9a0f54f3aef3dc700d2d62d1fa5bbea53b13426/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", size = 125275 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/44/b730e2a2580110ced837ac083d8ad222343c96bb6b66e9e4e706e4d0b6df/charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", size = 119020 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/e4/9263b8240ed9472a2ae7ddc3e516e71ef46617fe40eaa51221ccd4ad9a27/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", size = 139128 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/e3/9f73e779315a54334240353eaea75854a9a690f3f580e4bd85d977cb2204/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", size = 149277 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/cf/f1f50c2f295312edb8a548d3fa56a5c923b146cd3f24114d5adb7e7be558/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", size = 142174 },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/92/92a76dc2ff3a12e69ba94e7e05168d37d0345fa08c87e1fe24d0c2a42223/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", size = 143838 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/01/2117ff2b1dfc61695daf2babe4a874bca328489afa85952440b59819e9d7/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", size = 146149 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/9b/93a332b8d25b347f6839ca0a61b7f0287b0930216994e8bf67a75d050255/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", size = 140043 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/f6/7ac4a01adcdecbc7a7587767c776d53d369b8b971382b91211489535acf0/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", size = 148229 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/be/5708ad18161dee7dc6a0f7e6cf3a88ea6279c3e8484844c0590e50e803ef/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", size = 151556 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/bb/3d8bc22bacb9eb89785e83e6723f9888265f3a0de3b9ce724d66bd49884e/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", size = 149772 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/fa/d3fc622de05a86f30beea5fc4e9ac46aead4731e73fd9055496732bcc0a4/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", size = 144800 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/65/bdb9bc496d7d190d725e96816e20e2ae3a6fa42a5cac99c3c3d6ff884118/charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", size = 94836 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/67/7b72b69d25b89c0b3cea583ee372c43aa24df15f0e0f8d3982c57804984b/charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", size = 102187 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/89/68a4c86f1a0002810a27f12e9a7b22feb198c59b2f05231349fbce5c06f4/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", size = 194617 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/cd/8947fe425e2ab0aa57aceb7807af13a0e4162cd21eee42ef5b053447edf5/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", size = 125310 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/f0/b5263e8668a4ee9becc2b451ed909e9c27058337fda5b8c49588183c267a/charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", size = 119126 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/6e/e445afe4f7fda27a533f3234b627b3e515a1b9429bc981c9a5e2aa5d97b6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", size = 139342 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/b2/4af9993b532d93270538ad4926c8e37dc29f2111c36f9c629840c57cd9b3/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", size = 149383 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/6f/4e78c3b97686b871db9be6f31d64e9264e889f8c9d7ab33c771f847f79b7/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", size = 142214 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/c9/1c8fe3ce05d30c87eff498592c89015b19fade13df42850aafae09e94f35/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", size = 144104 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/68/efad5dcb306bf37db7db338338e7bb8ebd8cf38ee5bbd5ceaaaa46f257e6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", size = 146255 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/75/1ed813c3ffd200b1f3e71121c95da3f79e6d2a96120163443b3ad1057505/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", size = 140251 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/0d/6f32255c1979653b448d3c709583557a4d24ff97ac4f3a5be156b2e6a210/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", size = 148474 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/a0/c1b5298de4670d997101fef95b97ac440e8c8d8b4efa5a4d1ef44af82f0d/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", size = 151849 },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4f/b3961ba0c664989ba63e30595a3ed0875d6790ff26671e2aae2fdc28a399/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", size = 149781 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/90/6af4cd042066a4adad58ae25648a12c09c879efa4849c705719ba1b23d8c/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482", size = 144970 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/67/e5e7e0cbfefc4ca79025238b43cdf8a2037854195b37d6417f3d0895c4c2/charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", size = 94973 },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/97/fc9bbc54ee13d33dc54a7fcf17b26368b18505500fc01e228c27b5222d80/charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", size = 102308 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.7"
|
||||
@@ -252,6 +300,7 @@ dependencies = [
|
||||
{ name = "pandas" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "requests" },
|
||||
{ name = "rich" },
|
||||
{ name = "uvicorn" },
|
||||
]
|
||||
@@ -270,6 +319,7 @@ requires-dist = [
|
||||
{ name = "pandas", specifier = ">=2.2.3" },
|
||||
{ name = "pydantic", specifier = ">=2.9.2" },
|
||||
{ name = "pydantic-settings", specifier = ">=2.6.1" },
|
||||
{ name = "requests", specifier = ">=2.32.3" },
|
||||
{ name = "rich", specifier = ">=13.9.4" },
|
||||
{ name = "uvicorn", specifier = ">=0.32.0" },
|
||||
]
|
||||
@@ -378,6 +428,21 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "13.9.4"
|
||||
@@ -464,6 +529,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.2.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.32.0"
|
||||
|
||||
Reference in New Issue
Block a user