diff --git a/backend/src/tv/models.py b/backend/src/tv/models.py index 051ed29..dc8fc6c 100644 --- a/backend/src/tv/models.py +++ b/backend/src/tv/models.py @@ -3,6 +3,7 @@ from uuid import UUID from sqlalchemy import ForeignKey, PrimaryKeyConstraint, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship +from auth.db import User from backend.src.database import Base from torrent.models import Quality @@ -36,6 +37,7 @@ class Season(Base): episodes: Mapped[list["Episode"]] = relationship(back_populates="season", cascade="all, delete") season_files = relationship("SeasonFile", back_populates="season", cascade="all, delete") + season_requests = relationship("SeasonRequest", back_populates="season", cascade="all, delete") class Episode(Base): @@ -74,6 +76,10 @@ class SeasonRequest(Base): season_id: Mapped[UUID] = mapped_column(ForeignKey(column="season.id", ondelete="CASCADE"), ) wanted_quality: Mapped[Quality] min_quality: Mapped[Quality] - requested_by: Mapped[UUID | None] = mapped_column(ForeignKey(column="user.id", ondelete="SET NULL"), ) + requested_by_id: Mapped[UUID | None] = mapped_column(ForeignKey(column="user.id", ondelete="SET NULL"), ) authorized: Mapped[bool] = mapped_column(default=False) - authorized_by: Mapped[UUID | None] = mapped_column(ForeignKey(column="user.id", ondelete="SET NULL"), ) + authorized_by_id: Mapped[UUID | None] = mapped_column(ForeignKey(column="user.id", ondelete="SET NULL"), ) + + requested_by: Mapped["User|None"] = relationship(foreign_keys=[requested_by_id], uselist=False) + authorized_by: Mapped["User|None"] = relationship(foreign_keys=[authorized_by_id], uselist=False) + season = relationship("Season", back_populates="season_requests", uselist=False) diff --git a/backend/src/tv/repository.py b/backend/src/tv/repository.py index 21fed5f..952d0c1 100644 --- a/backend/src/tv/repository.py +++ b/backend/src/tv/repository.py @@ -1,6 +1,4 @@ -import pprint - -from sqlalchemy import select, delete, update +from sqlalchemy import select, delete from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Session, joinedload @@ -121,12 +119,13 @@ def add_season_to_requested_list(season_request: SeasonRequestSchema, db: Sessio db.commit() -def remove_season_from_requested_list(season_request: SeasonRequestSchema, db: Session) -> None: +def delete_season_request(season_request_id: SeasonRequestId, db: Session) -> None: """ Removes a Season from the SeasonRequest table, which removes it from the 'requested' list. """ - db.delete(SeasonRequest(**season_request.model_dump())) + stmt = delete(SeasonRequest).where(SeasonRequest.id == season_request_id) + db.execute(stmt) db.commit() @@ -138,10 +137,16 @@ def get_season_by_number(db: Session, season_number: int, show_id: ShowId) -> Se def get_season_requests(db: Session) -> list[RichSeasonRequestSchema]: - stmt = select(SeasonRequest).join(Season, Season.id == SeasonRequest.season_id).join(Show, - Season.show_id == Show.id) - result = db.execute(stmt).scalars().all() - return [RichSeasonRequestSchema.model_validate(season) for season in result] + stmt = select(SeasonRequest).options(joinedload(SeasonRequest.requested_by), + joinedload(SeasonRequest.authorized_by), + joinedload(SeasonRequest.season).joinedload(Season.show)) + result = db.execute(stmt).scalars().unique().all() + return [RichSeasonRequestSchema(min_quality=x.min_quality, + wanted_quality=x.wanted_quality, show=x.season.show, season=x.season, + requested_by=x.requested_by, authorized_by=x.authorized_by, authorized=x.authorized, + id=x.id) + for x in result] + def add_season_file(db: Session, season_file: SeasonFileSchema) -> SeasonFileSchema: db.add(SeasonFile(**season_file.model_dump())) @@ -209,5 +214,3 @@ def update_season_request(db: Session, season_request: SeasonRequestSchema) -> N db.delete(db.get(SeasonRequest, season_request.id)) db.add(SeasonRequest(**season_request.model_dump())) db.commit() - - diff --git a/backend/src/tv/router.py b/backend/src/tv/router.py index 7a1dd35..1e65abe 100644 --- a/backend/src/tv/router.py +++ b/backend/src/tv/router.py @@ -6,6 +6,7 @@ from fastapi.responses import JSONResponse import tv.repository import tv.service from auth.db import User +from auth.schemas import UserRead from auth.users import current_active_user, current_superuser from backend.src.database import DbSessionDependency from indexer.schemas import PublicIndexerQueryResult, IndexerQueryResultId @@ -13,7 +14,7 @@ from metadataProvider.schemas import MetaDataProviderShowSearchResult from torrent.schemas import Torrent from tv.exceptions import MediaAlreadyExists from tv.schemas import Show, SeasonRequest, ShowId, RichShowTorrent, PublicShow, PublicSeasonFile, SeasonNumber, \ - CreateSeasonRequest, SeasonRequestId, UpdateSeasonRequest, RichSeasonRequest, SeasonId + CreateSeasonRequest, SeasonRequestId, UpdateSeasonRequest, RichSeasonRequest router = APIRouter() @@ -82,50 +83,51 @@ def get_season_files(db: DbSessionDependency, season_number: SeasonNumber, show_ # MANAGE REQUESTS # -------------------------------- -@router.post("/seasons/requests", status_code=status.HTTP_200_OK, response_model=RichSeasonRequest) +@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 = user.id + request.requested_by = UserRead.model_validate(user) tv.service.request_season(db=db, season_request=request) - return request + return -@router.get("/seasons/requests", status_code=status.HTTP_200_OK, dependencies=[Depends(current_active_user)]) -def get_requested_seasons(db: DbSessionDependency) -> list[SeasonRequest]: +@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.patch("/seasons/requests/{season_request_id}", status_code=status.HTTP_200_OK, response_model=SeasonRequest) +@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 = user.id + season_request.authorized_by = UserRead.model_validate(user) season_request.authorized = authorized_status tv.service.update_season_request(db=db, season_request=season_request) - return season_request + return -@router.put("/seasons/requests/{season_request_id}", status_code=status.HTTP_200_OK, response_model=SeasonRequest) +@router.put("/seasons/requests/{season_request_id}", status_code=status.HTTP_204_NO_CONTENT) def update_request(db: DbSessionDependency, user: Annotated[User, Depends(current_superuser)], season_request: UpdateSeasonRequest): season_request: SeasonRequest = SeasonRequest.model_validate(season_request) - season_request.requested_by = user.id + season_request.requested_by = UserRead.model_validate(user) tv.service.update_season_request(db=db, season_request=season_request) - return season_request + return @router.delete("/seasons/requests/{season_request_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(current_active_user)]) -def delete_season_request(db: DbSessionDependency, request: SeasonRequest): - tv.service.unrequest_season(db=db, season_request=request) +def delete_season_request(db: DbSessionDependency, request_id: SeasonRequestId): + tv.service.delete_season_request(db=db, season_request_id=request_id) # -------------------------------- diff --git a/backend/src/tv/schemas.py b/backend/src/tv/schemas.py index 5a53669..868a95d 100644 --- a/backend/src/tv/schemas.py +++ b/backend/src/tv/schemas.py @@ -5,6 +5,7 @@ from uuid import UUID from pydantic import BaseModel, Field, ConfigDict from tvdb_v4_official import Request +from auth.schemas import UserRead from torrent.models import Quality from torrent.schemas import TorrentId, TorrentStatus @@ -55,12 +56,12 @@ class Show(BaseModel): class SeasonRequestBase(BaseModel): - season_id: SeasonId min_quality: Quality wanted_quality: Quality class CreateSeasonRequest(SeasonRequestBase): + season_id: SeasonId pass @@ -72,17 +73,15 @@ class SeasonRequest(SeasonRequestBase): model_config = ConfigDict(from_attributes=True) id: SeasonRequestId = Field(default_factory=uuid.uuid4) + season: Season - requested_by: UUID | None = None + requested_by: UserRead | None = None authorized: bool = False - authorized_by: UUID | None = None + authorized_by: UserRead | None = None class RichSeasonRequest(SeasonRequest): - show_id: ShowId - show_name: str - show_year: int | None - season_number: SeasonNumber + show: Show class SeasonFile(BaseModel): model_config = ConfigDict(from_attributes=True) diff --git a/backend/src/tv/service.py b/backend/src/tv/service.py index 532cf89..97d704c 100644 --- a/backend/src/tv/service.py +++ b/backend/src/tv/service.py @@ -15,7 +15,7 @@ from tv import log from tv.exceptions import MediaAlreadyExists from tv.repository import add_season_file, get_season_files_by_season_id from tv.schemas import Show, ShowId, SeasonRequest, SeasonFile, SeasonId, Season, RichShowTorrent, RichSeasonTorrent, \ - PublicSeason, PublicShow, PublicSeasonFile, SeasonNumber, SeasonRequestId + PublicSeason, PublicShow, PublicSeasonFile, SeasonNumber, SeasonRequestId, RichSeasonRequest def add_show(db: Session, external_id: int, metadata_provider: str) -> Show | None: @@ -34,8 +34,9 @@ def request_season(db: Session, season_request: SeasonRequest) -> None: def update_season_request(db: Session, season_request: SeasonRequest) -> None: tv.repository.update_season_request(db=db, season_request=season_request) -def unrequest_season(db: Session, season_request: SeasonRequest) -> None: - tv.repository.remove_season_from_requested_list(db=db, season_request=season_request) + +def delete_season_request(db: Session, season_request_id: SeasonRequestId) -> None: + tv.repository.delete_season_request(db=db, season_request_id=season_request_id) def get_public_season_files_by_season_id(db: Session, season_id: SeasonId) -> list[PublicSeasonFile]: @@ -148,7 +149,7 @@ def get_season(db: Session, season_id: SeasonId) -> Season: return tv.repository.get_season(season_id=season_id, db=db) -def get_all_season_requests(db: Session) -> list[SeasonRequest]: +def get_all_season_requests(db: Session) -> list[RichSeasonRequest]: return tv.repository.get_season_requests(db=db) diff --git a/web/src/lib/components/login-form.svelte b/web/src/lib/components/login-form.svelte index 2011474..6909813 100644 --- a/web/src/lib/components/login-form.svelte +++ b/web/src/lib/components/login-form.svelte @@ -80,7 +80,7 @@ if (response.ok) { console.log('Registration successful!'); console.log('Received User Data: ', response); - goto('/dashboard'); + tabValue = "login"; // Switch to login tab after successful registration errorMessage = 'Registration successful! Redirecting...'; } else { let errorText = await response.text(); diff --git a/web/src/lib/components/nav-user.svelte b/web/src/lib/components/nav-user.svelte index cd1a9cb..f90db43 100644 --- a/web/src/lib/components/nav-user.svelte +++ b/web/src/lib/components/nav-user.svelte @@ -17,23 +17,9 @@ import {base} from '$app/paths'; import {env} from "$env/dynamic/public"; import {goto} from '$app/navigation'; - + import {handleLogout} from '$lib/utils.ts'; const user: () => User = getContext('user'); const sidebar = useSidebar(); - const apiUrl = env.PUBLIC_API_URL; - - async function handleLogout() { - const response = await fetch(apiUrl + '/auth/cookie/logout', { - method: 'POST', - credentials: 'include' - }); - if (response.ok) { - console.log('Logout successful!'); - await goto(base + '/login'); - } else { - console.error('Logout failed:', response.status); - } - } diff --git a/web/src/lib/components/season-requests-table.svelte b/web/src/lib/components/season-requests-table.svelte new file mode 100644 index 0000000..d7070fc --- /dev/null +++ b/web/src/lib/components/season-requests-table.svelte @@ -0,0 +1,62 @@ + + +
- If you have any questions, please contact an administrator.
+ If you have any questions, please contact an administrator. +