Add movie/series ratings and watchlist
Builds on the accounts foundation: PUT/DELETE a rating (0.5-10.0) or a
watchlist entry on any movie or series, all scoped to the bearer token's
own user, plus GET /account/ratings|watchlist/{movies,series} to list
them back. Both are polymorphic entity_type/entity_id tables, same shape
as watch_provider_offerings and trending_entries, so movie and series
share one repository/router each rather than duplicating per type.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a38c2074a6
commit
0ab31a314c
@@ -9,7 +9,14 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.core.ids import aurora_id, parse_aurora_id
|
||||
from app.models.enums import ArtworkType, MovieStatus
|
||||
from app.models.movie import Movie
|
||||
from app.repositories import movie_repository, polymorphic, trending_repository
|
||||
from app.repositories import (
|
||||
movie_repository,
|
||||
polymorphic,
|
||||
rating_repository,
|
||||
trending_repository,
|
||||
watchlist_repository,
|
||||
)
|
||||
from app.schemas.account import RatingOut
|
||||
from app.schemas.common import CollectionBriefOut, CompanyBriefOut, GenreOut, KeywordOut
|
||||
from app.schemas.movie import (
|
||||
MovieCreditsOut,
|
||||
@@ -20,6 +27,8 @@ from app.schemas.movie import (
|
||||
MovieSummaryOut,
|
||||
MovieVideosOut,
|
||||
MovieWatchProvidersOut,
|
||||
RatedMovieListOut,
|
||||
RatedMovieOut,
|
||||
)
|
||||
from app.services.common import (
|
||||
build_watch_providers_out,
|
||||
@@ -209,6 +218,79 @@ async def build_movie_summaries(
|
||||
return out
|
||||
|
||||
|
||||
async def rate_movie(
|
||||
session: AsyncSession, movie_aurora_id: str, *, user_id: uuid.UUID, value: float
|
||||
) -> RatingOut:
|
||||
movie_id = await _resolve_movie_id(movie_aurora_id)
|
||||
if await movie_repository.get_movie_by_id(session, movie_id) is None:
|
||||
raise MovieNotFoundError(movie_aurora_id)
|
||||
|
||||
rating = await rating_repository.upsert_rating(
|
||||
session, user_id=user_id, entity_type="movie", entity_id=movie_id, value=value
|
||||
)
|
||||
return RatingOut(value=rating.value, created_at=rating.created_at, updated_at=rating.updated_at)
|
||||
|
||||
|
||||
async def unrate_movie(session: AsyncSession, movie_aurora_id: str, *, user_id: uuid.UUID) -> None:
|
||||
movie_id = await _resolve_movie_id(movie_aurora_id)
|
||||
if await movie_repository.get_movie_by_id(session, movie_id) is None:
|
||||
raise MovieNotFoundError(movie_aurora_id)
|
||||
|
||||
await rating_repository.delete_rating(session, user_id=user_id, entity_type="movie", entity_id=movie_id)
|
||||
|
||||
|
||||
async def list_rated_movies(
|
||||
session: AsyncSession, *, user_id: uuid.UUID, page: int, page_size: int
|
||||
) -> RatedMovieListOut:
|
||||
ratings, total = await rating_repository.list_rated(
|
||||
session, user_id=user_id, entity_type="movie", limit=page_size, offset=(page - 1) * page_size
|
||||
)
|
||||
results: list[RatedMovieOut] = []
|
||||
for rating in ratings:
|
||||
summary = await build_movie_summary(session, rating.entity_id)
|
||||
if summary is not None:
|
||||
results.append(RatedMovieOut(**summary.model_dump(), rating=rating.value))
|
||||
total_pages = (total + page_size - 1) // page_size if total else 0
|
||||
|
||||
return RatedMovieListOut(
|
||||
page=page, page_size=page_size, total_results=total, total_pages=total_pages, results=results
|
||||
)
|
||||
|
||||
|
||||
async def add_movie_to_watchlist(
|
||||
session: AsyncSession, movie_aurora_id: str, *, user_id: uuid.UUID
|
||||
) -> None:
|
||||
movie_id = await _resolve_movie_id(movie_aurora_id)
|
||||
if await movie_repository.get_movie_by_id(session, movie_id) is None:
|
||||
raise MovieNotFoundError(movie_aurora_id)
|
||||
|
||||
await watchlist_repository.add_entry(session, user_id=user_id, entity_type="movie", entity_id=movie_id)
|
||||
|
||||
|
||||
async def remove_movie_from_watchlist(
|
||||
session: AsyncSession, movie_aurora_id: str, *, user_id: uuid.UUID
|
||||
) -> None:
|
||||
movie_id = await _resolve_movie_id(movie_aurora_id)
|
||||
if await movie_repository.get_movie_by_id(session, movie_id) is None:
|
||||
raise MovieNotFoundError(movie_aurora_id)
|
||||
|
||||
await watchlist_repository.remove_entry(session, user_id=user_id, entity_type="movie", entity_id=movie_id)
|
||||
|
||||
|
||||
async def list_watchlist_movies(
|
||||
session: AsyncSession, *, user_id: uuid.UUID, page: int, page_size: int
|
||||
) -> MovieListOut:
|
||||
entries, total = await watchlist_repository.list_entries(
|
||||
session, user_id=user_id, entity_type="movie", limit=page_size, offset=(page - 1) * page_size
|
||||
)
|
||||
results = await build_movie_summaries(session, [entry.entity_id for entry in entries])
|
||||
total_pages = (total + page_size - 1) // page_size if total else 0
|
||||
|
||||
return MovieListOut(
|
||||
page=page, page_size=page_size, total_results=total, total_pages=total_pages, results=results
|
||||
)
|
||||
|
||||
|
||||
async def list_now_playing(
|
||||
session: AsyncSession, *, region: str, page: int, page_size: int
|
||||
) -> MovieListOut:
|
||||
|
||||
Reference in New Issue
Block a user