Files
MediaManager-maxdorninger/media_manager/tv/router.py
2025-05-29 15:36:35 +02:00

311 lines
9.2 KiB
Python

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)