diff --git a/MediaManager/src/auth/__init__.py b/MediaManager/src/auth/__init__.py index d684c51..c9267c6 100644 --- a/MediaManager/src/auth/__init__.py +++ b/MediaManager/src/auth/__init__.py @@ -1,5 +1,6 @@ import logging from datetime import datetime, timedelta, timezone +from typing import Annotated import jwt from fastapi import Depends, HTTPException, status, APIRouter @@ -29,12 +30,13 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/v1/token") router = APIRouter() -async def get_current_user(token: str = Depends(oauth2_scheme), config = Depends(AuthConfig)) -> UserInternal: +async def get_current_user(token: str = Depends(oauth2_scheme)) -> UserInternal: credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) + config = AuthConfig() log.debug("token: " + token) try: payload = jwt.decode(token, config.jwt_signing_key, algorithms=[config.jwt_signing_algorithm]) @@ -55,8 +57,9 @@ async def get_current_user(token: str = Depends(oauth2_scheme), config = Depends return user -def create_access_token(data: dict, expires_delta: timedelta | None = None, config = Depends(AuthConfig)): +def create_access_token(data: dict, expires_delta: timedelta | None = None): to_encode = data.copy() + config = AuthConfig() if expires_delta: expire = datetime.now(timezone.utc) + expires_delta else: diff --git a/MediaManager/src/config/__init__.py b/MediaManager/src/config/__init__.py index 31507d0..bb9c09b 100644 --- a/MediaManager/src/config/__init__.py +++ b/MediaManager/src/config/__init__.py @@ -16,6 +16,10 @@ class DbConfig(BaseModel): def password(self): return self._password +class TvConfig(BaseModel): + api_key: str = os.getenv("TMDB_API_KEY") + + class IndexerConfig(BaseModel): default_indexer: Literal["tmdb"] = os.getenv("INDEXER") or "tmdb" @@ -44,6 +48,7 @@ def load_config(): log.info(f"loaded config: DbConfig: {DbConfig().__str__()}") log.info(f"loaded config: IndexerConfig: {IndexerConfig().__str__()}") log.info(f"loaded config: AuthConfig: {AuthConfig().__str__()}") + log.info(f"loaded config: TvConfig: {TvConfig().__str__()}") if __name__ == "__main__": diff --git a/MediaManager/src/database/tv.py b/MediaManager/src/database/tv.py index 5387c4a..41a28b4 100644 --- a/MediaManager/src/database/tv.py +++ b/MediaManager/src/database/tv.py @@ -1,63 +1,5 @@ -from typing import Literal, List -from uuid import UUID, uuid4 - -from pydantic import BaseModel - from database import PgDatabase, log - -# NOTE: use tmdbsimple for api calls - -class Episode(BaseModel): - number: int - title: str - -class Season(BaseModel): - number: int - episodes: List[Episode] - - def get_episode_count(self)-> int: - return self.episodes.__len__() - -class Show(BaseModel): - id: UUID = uuid4() - external_id: int - indexer: Literal["tmdb"] - name: str - seasons: List[Season] - - def get_season_count(self)-> int: - return self.seasons.__len__() - - def get_episode_count(self) -> int: - episode_count = 0 - for season in self.seasons: - episode_count += season.get_episode_count() - return episode_count - - def save_show(self) -> None: - with PgDatabase() as db: - db.connection.execute(""" - INSERT INTO tv_show ( - id, - external_id, - indexer, - name, - episode_count, - season_count - )VALUES(%s,%s,%s,%s,%s,%s); - """, - (self.id, - self.external_id, - self.indexer, - self.name, - self.get_episode_count(), - self.get_season_count(), - ) - ) - log.info("added show: " + self.__str__()) - - # TODO: add NOT NULL and default values to DB def init_table(): @@ -66,10 +8,12 @@ def init_table(): CREATE TABLE IF NOT EXISTS tv_show ( id UUID PRIMARY KEY, external_id TEXT, - indexer TEXT, + metadata_provider TEXT, name TEXT, episode_count INTEGER, - season_count INTEGER + season_count INTEGER, + UNIQUE (external_id, metadata_provider) + );""") log.info("tv_show Table initialized successfully") db.connection.execute(""" @@ -78,17 +22,16 @@ def init_table(): season_number INTEGER, episode_count INTEGER, CONSTRAINT PK_season PRIMARY KEY (show_id,season_number) - );""") log.info("tv_seasonTable initialized successfully") db.connection.execute(""" CREATE TABLE IF NOT EXISTS tv_episode ( - season INTEGER, + season_number INTEGER, show_id uuid, episode_number INTEGER, title TEXT, - CONSTRAINT PK_episode PRIMARY KEY (season,show_id,episode_number), - FOREIGN KEY (season, show_id) REFERENCES tv_season(season_number,show_id) + CONSTRAINT PK_episode PRIMARY KEY (season_number,show_id,episode_number), + FOREIGN KEY (season_number, show_id) REFERENCES tv_season(season_number,show_id) );""") log.info("tv_episode Table initialized successfully") diff --git a/MediaManager/src/main.py b/MediaManager/src/main.py index 0b77903..b0d1086 100644 --- a/MediaManager/src/main.py +++ b/MediaManager/src/main.py @@ -6,6 +6,7 @@ from fastapi import FastAPI import config import database +import tv.router from auth import password from routers import users @@ -18,6 +19,7 @@ database.init_db() app = FastAPI(root_path="/api/v1") app.include_router(users.router, tags=["users"]) app.include_router(password.router, tags=["authentication"]) +app.include_router(tv.router.router, tags=["tv"]) diff --git a/MediaManager/src/routers/tv.py b/MediaManager/src/routers/tv.py deleted file mode 100644 index e69de29..0000000 diff --git a/MediaManager/src/tv/__init__.py b/MediaManager/src/tv/__init__.py new file mode 100644 index 0000000..d81a7a5 --- /dev/null +++ b/MediaManager/src/tv/__init__.py @@ -0,0 +1,106 @@ +import logging +import pprint +from typing import Literal, List, Any +from uuid import UUID, uuid4 + +import requests +from pydantic import BaseModel + +from config import TvConfig +from database import PgDatabase +import tmdbsimple as tmdb + + +# NOTE: use tmdbsimple for api calls + +class Episode(BaseModel): + number: int + title: str + + +class Season(BaseModel): + number: int + episodes: List[Episode] + + def get_episode_count(self) -> int: + return self.episodes.__len__() + + +class Show(BaseModel): + id: UUID = uuid4() + external_id: int + metadata_provider: str + name: str + seasons: List[Season] = [] + + def get_season_count(self) -> int: + return self.seasons.__len__() + + def get_episode_count(self) -> int: + episode_count = 0 + for season in self.seasons: + episode_count += season.get_episode_count() + return episode_count + + def save_show(self) -> None: + with PgDatabase() as db: + db.connection.execute(""" + INSERT INTO tv_show ( + id, + external_id, + metadata_provider, + name, + episode_count, + season_count + )VALUES(%s,%s,%s,%s,%s,%s); + """, + (self.id, + self.external_id, + self.metadata_provider, + self.name, + self.get_episode_count(), + self.get_season_count(), + ) + ) + log.info("added show: " + self.__str__()) + + def get_data_from_tmdb(self) -> None: + data = tmdb.TV(self.external_id).info() + log.debug("data from tmdb: " + pprint.pformat(data)) + self.name = data["original_name"] + self.metadata_provider = "tmdb" + + def add_season(self, season_number: int) -> None: + data = tmdb.TV_Seasons(self.external_id, season_number).info() + log.debug("data from tmdb: " + pprint.pformat(data)) + + episodes: List[Episode] = [] + for episode in data["episodes"]: + episodes.append(Episode(title=episode["name"],number=episode["episode_number"])) + + season = Season(number=season_number, episodes=episodes) + + self.seasons.append(season) + + def add_seasons(self, season_numbers: List[int]) -> None: + for season_number in season_numbers: + self.add_season(season_number) + +def get_all_shows() -> List[Show]: + with PgDatabase() as db: + result = db.connection.execute(""" + SELECT * FROM tv_show + """).fetchall() + return result + +def get_show(id: UUID) -> Show: + with PgDatabase() as db: + result = db.connection.execute(""" + SELECT * FROM tv_show WHERE id = %s + """, (id,)).fetchone() + return Show(**result) + +config = TvConfig() +log = logging.getLogger(__name__) + +tmdb.API_KEY = config.api_key diff --git a/MediaManager/src/tv/router.py b/MediaManager/src/tv/router.py new file mode 100644 index 0000000..9fe98e0 --- /dev/null +++ b/MediaManager/src/tv/router.py @@ -0,0 +1,43 @@ +from typing import Annotated +from uuid import UUID + +import psycopg.errors +from fastapi import APIRouter, Depends + +import auth +from database.users import User, UserInternal +from tv import Show, get_all_shows, tmdb, log, get_show + +router = APIRouter( + prefix="/tv", +) + + +@router.post("/show", status_code=201,dependencies=[Depends(auth.get_current_user)]) +def post_add_show_route(show_id: int, metadata_provider: str = "tmdb"): + show: Show = Show(external_id=show_id, metadata_provider=metadata_provider, name="temp_name_set_in_post_show_route") + show.get_data_from_tmdb() + + try: + show.save_show() + except psycopg.errors.UniqueViolation: + log.info("Show already exists " + show.__str__()) + return show + return show + +@router.post("/{show_id}/{season}", status_code=201,dependencies=[Depends(auth.get_current_user)]) +def post_add_season_route(show_id: UUID, season: int): + show = get_show(show_id) + show.add_season(season) + show.save_show() + return get_show(show_id) + +@router.get("/show") +def get_shows_route(): + return get_all_shows() + +@router.get("/search") +def search_show_route(query: str): + search = tmdb.Search() + return search.tv(query=query) +