from typing import Annotated from fastapi import APIRouter, Depends, status from fastapi.responses import JSONResponse import tv.repository import tv.service from media_manager.auth.db import User from media_manager.auth.schemas import UserRead from media_manager.auth.users import current_active_user, current_superuser from media_manager.database import DbSessionDependency from media_manager.indexer.schemas import PublicIndexerQueryResult, IndexerQueryResultId from media_manager.metadataProvider.schemas import MetaDataProviderShowSearchResult from media_manager.torrent.schemas import Torrent from media_manager.tv import log from media_manager.tv.exceptions import MediaAlreadyExists from media_manager.tv.schemas import ( Show, SeasonRequest, ShowId, RichShowTorrent, PublicShow, PublicSeasonFile, SeasonNumber, CreateSeasonRequest, SeasonRequestId, UpdateSeasonRequest, RichSeasonRequest, ) router = APIRouter() # -------------------------------- # CREATE AND DELETE SHOWS # -------------------------------- @router.post( "/shows", status_code=status.HTTP_201_CREATED, dependencies=[Depends(current_active_user)], responses={ status.HTTP_201_CREATED: { "model": Show, "description": "Successfully created show", }, status.HTTP_409_CONFLICT: {"model": str, "description": "Show already exists"}, }, ) def add_a_show(db: DbSessionDependency, show_id: int, metadata_provider: str = "tmdb"): try: show = tv.service.add_show( db=db, external_id=show_id, metadata_provider=metadata_provider, ) except MediaAlreadyExists as e: return JSONResponse( status_code=status.HTTP_409_CONFLICT, content={"message": str(e)} ) return show @router.delete( "/shows/{show_id}", status_code=status.HTTP_200_OK, dependencies=[Depends(current_active_user)], ) def delete_a_show(db: DbSessionDependency, show_id: ShowId): db.delete(db.get(Show, show_id)) db.commit() # -------------------------------- # GET SHOW INFORMATION # -------------------------------- @router.get( "/shows", dependencies=[Depends(current_active_user)], response_model=list[Show] ) def get_all_shows( db: DbSessionDependency, external_id: int = None, metadata_provider: str = "tmdb" ): if external_id is not None: return tv.service.get_show_by_external_id( db=db, external_id=external_id, metadata_provider=metadata_provider ) else: return tv.service.get_all_shows(db=db) @router.get( "/shows/torrents", dependencies=[Depends(current_active_user)], response_model=list[RichShowTorrent], ) def get_shows_with_torrents(db: DbSessionDependency): """ get all shows that are associated with torrents :return: A list of shows with all their torrents """ result = tv.service.get_all_shows_with_torrents(db=db) return result @router.get( "/shows/{show_id}", dependencies=[Depends(current_active_user)], response_model=PublicShow, ) def get_a_show(db: DbSessionDependency, show_id: ShowId): return tv.service.get_public_show_by_id(db=db, show_id=show_id) @router.get( "/shows/{show_id}/torrents", dependencies=[Depends(current_active_user)], response_model=RichShowTorrent, ) def get_a_shows_torrents(db: DbSessionDependency, show_id: ShowId): return tv.service.get_torrents_for_show( db=db, show=tv.service.get_show_by_id(db=db, show_id=show_id) ) # TODO: replace by route with season_id rather than show_id and season_number @router.get( "/shows/{show_id}/{season_number}/files", status_code=status.HTTP_200_OK, dependencies=[Depends(current_active_user)], ) def get_season_files( db: DbSessionDependency, season_number: SeasonNumber, show_id: ShowId ) -> list[PublicSeasonFile]: return tv.service.get_public_season_files_by_season_number( db=db, season_number=season_number, show_id=show_id ) # -------------------------------- # MANAGE REQUESTS # -------------------------------- @router.post("/seasons/requests", status_code=status.HTTP_204_NO_CONTENT) def request_a_season( db: DbSessionDependency, user: Annotated[User, Depends(current_active_user)], season_request: CreateSeasonRequest, ): """ adds request flag to a season """ request: SeasonRequest = SeasonRequest.model_validate(season_request) request.requested_by = UserRead.model_validate(user) if user.is_superuser: request.authorized = True request.authorized_by = user tv.service.add_season_request(db=db, season_request=request) return @router.get( "/seasons/requests", status_code=status.HTTP_200_OK, dependencies=[Depends(current_active_user)], response_model=list[RichSeasonRequest], ) def get_season_requests(db: DbSessionDependency) -> list[RichSeasonRequest]: return tv.service.get_all_season_requests(db=db) @router.delete( "/seasons/requests/{request_id}", status_code=status.HTTP_204_NO_CONTENT, ) def delete_season_request( db: DbSessionDependency, user: Annotated[User, Depends(current_active_user)], request_id: SeasonRequestId, ): request = tv.service.get_season_request_by_id(db=db, season_request_id=request_id) if user.is_superuser or request.requested_by.id == user.id: tv.service.delete_season_request(db=db, season_request_id=request_id) log.info(f"User {user.id} deleted season request {request_id}.") else: log.warning( f"User {user.id} tried to delete season request {request_id} but is not authorized." ) return JSONResponse( status_code=status.HTTP_403_FORBIDDEN, content={"message": "Not authorized to delete this request."}, ) @router.patch( "/seasons/requests/{season_request_id}", status_code=status.HTTP_204_NO_CONTENT ) def authorize_request( db: DbSessionDependency, user: Annotated[User, Depends(current_superuser)], season_request_id: SeasonRequestId, authorized_status: bool = False, ): """ updates the request flag to true """ season_request: SeasonRequest = tv.repository.get_season_request( db=db, season_request_id=season_request_id ) season_request.authorized_by = UserRead.model_validate(user) season_request.authorized = authorized_status if not authorized_status: season_request.authorized_by = None tv.service.update_season_request(db=db, season_request=season_request) return @router.put("/seasons/requests", status_code=status.HTTP_204_NO_CONTENT) def update_request( db: DbSessionDependency, user: Annotated[User, Depends(current_active_user)], season_request: UpdateSeasonRequest, ): season_request: SeasonRequest = SeasonRequest.model_validate(season_request) request = tv.service.get_season_request_by_id( db=db, season_request_id=season_request.id ) if request.requested_by.id == user.id or user.is_superuser: season_request.requested_by = UserRead.model_validate(user) tv.service.update_season_request(db=db, season_request=season_request) return # -------------------------------- # MANAGE TORRENTS # -------------------------------- # 1 is the default for season_number because it returns multi season torrents @router.get( "/torrents", status_code=status.HTTP_200_OK, dependencies=[Depends(current_superuser)], response_model=list[PublicIndexerQueryResult], ) def get_torrents_for_a_season( db: DbSessionDependency, show_id: ShowId, season_number: int = 1, search_query_override: str = None, ): return tv.service.get_all_available_torrents_for_a_season( db=db, season_number=season_number, show_id=show_id, search_query_override=search_query_override, ) # download a torrent @router.post( "/torrents", status_code=status.HTTP_200_OK, response_model=Torrent, dependencies=[Depends(current_superuser)], ) def download_a_torrent( db: DbSessionDependency, public_indexer_result_id: IndexerQueryResultId, show_id: ShowId, override_file_path_suffix: str = "", ): return tv.service.download_torrent( db=db, public_indexer_result_id=public_indexer_result_id, show_id=show_id, override_show_file_path_suffix=override_file_path_suffix, ) # -------------------------------- # SEARCH SHOWS ON METADATA PROVIDERS # -------------------------------- @router.get( "/search", dependencies=[Depends(current_active_user)], response_model=list[MetaDataProviderShowSearchResult], ) def search_metadata_providers_for_a_show( db: DbSessionDependency, query: str, metadata_provider: str = "tmdb" ): return tv.service.search_for_show( query=query, metadata_provider=metadata_provider, db=db ) @router.get( "/recommended", dependencies=[Depends(current_active_user)], response_model=list[MetaDataProviderShowSearchResult], ) def search_metadata_providers_for_a_show( db: DbSessionDependency, metadata_provider: str = "tmdb" ): return tv.service.get_popular_shows(metadata_provider=metadata_provider, db=db)