Skip to content

Commit

Permalink
Add GetMyDeatiledWatchlistQuery
Browse files Browse the repository at this point in the history
  • Loading branch information
madnoberson committed Mar 14, 2024
1 parent df31f2f commit f499844
Show file tree
Hide file tree
Showing 15 changed files with 400 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/amdb/application/common/readers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
"RatingForExportViewModelsReader",
"NonDetailedMovieViewModelsReader",
"MyDetailedRatingsViewModelReader",
"MyDetailedWatchlistViewModelReader",
)

from .detailed_movie import DetailedMovieViewModelReader
from .detailed_review import DetailedReviewViewModelsReader
from .rating_for_export import RatingForExportViewModelsReader
from .non_detailed_movie import NonDetailedMovieViewModelsReader
from .my_detailed_ratings import MyDetailedRatingsViewModelReader
from .my_detailed_watchlist import MyDetailedWatchlistViewModelReader
14 changes: 14 additions & 0 deletions src/amdb/application/common/readers/my_detailed_watchlist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from amdb.domain.entities.user import UserId
from amdb.application.common.view_models.my_detailed_watchlist import (
MyDetailedWatchlistViewModel,
)


class MyDetailedWatchlistViewModelReader:
def get(
self,
current_user_id: UserId,
limit: int,
offset: int,
) -> MyDetailedWatchlistViewModel:
raise NotImplementedError
30 changes: 30 additions & 0 deletions src/amdb/application/common/view_models/my_detailed_watchlist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from datetime import date, datetime

from typing_extensions import TypedDict

from amdb.domain.entities.movie import MovieId
from amdb.domain.entities.movie_for_later import MovieForLaterId


class MovieViewModel(TypedDict):
id: MovieId
title: str
release_date: date
rating: float
rating_count: int


class MovieForLaterViewModel(TypedDict):
id: MovieForLaterId
note: str
created_at: datetime


class DetailedMovieForLaterViewModel(TypedDict):
movie: MovieViewModel
movie_for_later: MovieForLaterViewModel


class MyDetailedWatchlistViewModel(TypedDict):
detailed_movies_for_later: list[DetailedMovieForLaterViewModel]
movie_for_later_count: int
7 changes: 7 additions & 0 deletions src/amdb/application/queries/my_detailed_watchlist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from dataclasses import dataclass


@dataclass(frozen=True, slots=True)
class GetMyDeatiledWatchlistQuery:
limit: int
offset: int
2 changes: 2 additions & 0 deletions src/amdb/application/query_handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"ExportAndSendMyRatingsHandler",
"GetMyDetailedRatingsHandler",
"GetNonDetailedMoviesHandler",
"GetMyDetailedWatchlistHandler",
)

from .login import LoginHandler
Expand All @@ -17,3 +18,4 @@
from .export_and_send_my_ratings import ExportAndSendMyRatingsHandler
from .my_detailed_ratings import GetMyDetailedRatingsHandler
from .non_detailed_movies import GetNonDetailedMoviesHandler
from .my_detailed_watchlist import GetMyDetailedWatchlistHandler
35 changes: 35 additions & 0 deletions src/amdb/application/query_handlers/my_detailed_watchlist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from amdb.application.common.readers.my_detailed_watchlist import (
MyDetailedWatchlistViewModelReader,
)
from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.common.view_models.my_detailed_watchlist import (
MyDetailedWatchlistViewModel,
)
from amdb.application.queries.my_detailed_watchlist import (
GetMyDeatiledWatchlistQuery,
)


class GetMyDetailedWatchlistHandler:
def __init__(
self,
*,
my_detailed_watchlist_reader: MyDetailedWatchlistViewModelReader,
identity_provider: IdentityProvider,
) -> None:
self._my_detailed_watchlist_reader = my_detailed_watchlist_reader
self._identity_provider = identity_provider

def execute(
self,
query: GetMyDeatiledWatchlistQuery,
) -> MyDetailedWatchlistViewModel:
current_user_id = self._identity_provider.user_id()

view_model = self._my_detailed_watchlist_reader.get(
current_user_id=current_user_id,
limit=query.limit,
offset=query.offset,
)

return view_model
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"RatingForExportViewModelMapper",
"NonDetailedMovieViewModelsMapper",
"MyDetailedRatingsViewModelMapper",
"MyDetailedWatchlistViewModelMapper",
"PermissionsMapper",
"PasswordHashMapper",
)
Expand All @@ -23,5 +24,8 @@
from .view_models.rating_for_export import RatingForExportViewModelMapper
from .view_models.non_detailed_movie import NonDetailedMovieViewModelsMapper
from .view_models.my_detailed_ratings import MyDetailedRatingsViewModelMapper
from .view_models.my_detailed_watchlist import (
MyDetailedWatchlistViewModelMapper,
)
from .permissions import PermissionsMapper
from .password_hash import PasswordHashMapper
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,5 @@ def _rating_count(self, current_user_id: UserId) -> int:
statement,
parameters,
).scalar_one()

return rating_count
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from sqlalchemy import Connection, text

from amdb.domain.entities.user import UserId
from amdb.domain.entities.movie import MovieId
from amdb.domain.entities.movie_for_later import MovieForLaterId
from amdb.application.common.view_models.my_detailed_watchlist import (
MovieViewModel,
MovieForLaterViewModel,
DetailedMovieForLaterViewModel,
MyDetailedWatchlistViewModel,
)


class MyDetailedWatchlistViewModelMapper:
def __init__(self, connecion: Connection) -> None:
self._connection = connecion

def get(
self,
current_user_id: UserId,
limit: int,
offset: int,
) -> MyDetailedWatchlistViewModel:
detailed_movies_for_later = self._detailed_movies_for_later(
current_user_id=current_user_id,
limit=limit,
offset=offset,
)
movies_for_later_count = self._movies_for_later_count(
current_user_id=current_user_id,
)
view_model = MyDetailedWatchlistViewModel(
detailed_movies_for_later=detailed_movies_for_later,
movie_for_later_count=movies_for_later_count,
)
return view_model

def _detailed_movies_for_later(
self,
current_user_id: UserId,
limit: int,
offset: int,
) -> list[DetailedMovieForLaterViewModel]:
statement = text(
"""
SELECT
m.id movie_id,
m.title movie_title,
m.release_date movie_release_date,
m.rating movie_rating,
m.rating_count movie_rating_count,
umfl.id movie_for_later_id,
umfl.note movie_for_later_note,
umfl.created_at movie_for_later_created_at
FROM
movies_for_later umfl
LEFT JOIN movies m
ON m.id = umfl.movie_id
WHERE
umfl.user_id = :current_user_id
LIMIT :limit OFFSET :offset
""",
)
parameters = {
"current_user_id": current_user_id,
"limit": limit,
"offset": offset,
}
rows = self._connection.execute(statement, parameters).fetchall()

detailed_movies_for_later = []
for row in rows:
row_as_dict = row._mapping # noqa: SLF001
detailed_movie_for_later = DetailedMovieForLaterViewModel(
movie=MovieViewModel(
id=MovieId(row_as_dict["movie_id"]),
title=row_as_dict["movie_title"],
release_date=row_as_dict["movie_release_date"],
rating=row_as_dict["movie_rating"],
rating_count=row_as_dict["movie_rating_count"],
),
movie_for_later=MovieForLaterViewModel(
id=MovieForLaterId(row_as_dict["movie_for_later_id"]),
note=row_as_dict["movie_for_later_note"],
created_at=row_as_dict["movie_for_later_created_at"],
),
)
detailed_movies_for_later.append(detailed_movie_for_later)

return detailed_movies_for_later

def _movies_for_later_count(
self,
current_user_id: UserId,
) -> int:
statement = text(
"""
SELECT COUNT(umfl.id) FROM movies_for_later umfl
WHERE umfl.user_id = :current_user_id
""",
)
parameters = {
"current_user_id": current_user_id,
}
movies_for_later_count = self._connection.execute(
statement,
parameters,
).scalar_one()

return movies_for_later_count
6 changes: 6 additions & 0 deletions src/amdb/main/providers/data_mappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
RatingForExportViewModelsReader,
MyDetailedRatingsViewModelReader,
NonDetailedMovieViewModelsReader,
MyDetailedWatchlistViewModelReader,
)
from amdb.application.common.unit_of_work import UnitOfWork
from amdb.infrastructure.password_manager.password_hash_gateway import (
Expand All @@ -34,6 +35,7 @@
RatingForExportViewModelMapper,
MyDetailedRatingsViewModelMapper,
NonDetailedMovieViewModelsMapper,
MyDetailedWatchlistViewModelMapper,
)
from amdb.infrastructure.persistence.redis.cache.permissions_mapper import (
PermissionsMapperCacheProvider,
Expand Down Expand Up @@ -101,3 +103,7 @@ class ViewModelMappersProvider(Provider):
NonDetailedMovieViewModelsMapper,
provides=NonDetailedMovieViewModelsReader,
)
my_detailed_watchlist = provide(
MyDetailedWatchlistViewModelMapper,
provides=MyDetailedWatchlistViewModelReader,
)
17 changes: 17 additions & 0 deletions src/amdb/main/providers/query_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
RatingForExportViewModelsReader,
NonDetailedMovieViewModelsReader,
MyDetailedRatingsViewModelReader,
MyDetailedWatchlistViewModelReader,
)
from amdb.application.common.password_manager import PasswordManager
from amdb.application.common.sending.email import SendEmail
Expand All @@ -34,6 +35,7 @@
ExportAndSendMyRatingsHandler,
GetMyDetailedRatingsHandler,
GetNonDetailedMoviesHandler,
GetMyDetailedWatchlistHandler,
)
from amdb.presentation.create_handler import CreateHandler

Expand Down Expand Up @@ -168,3 +170,18 @@ def create_handler(
)

return create_handler

@provide
def get_my_detailed_watchlist(
self,
my_detailed_watchlist_reader: MyDetailedWatchlistViewModelReader,
) -> CreateHandler[GetMyDetailedWatchlistHandler]:
def create_handler(
identity_provider: IdentityProvider,
) -> GetMyDetailedWatchlistHandler:
return GetMyDetailedWatchlistHandler(
my_detailed_watchlist_reader=my_detailed_watchlist_reader,
identity_provider=identity_provider,
)

return create_handler
56 changes: 56 additions & 0 deletions src/amdb/presentation/web_api/watchlists/get_my_detailed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from typing import Annotated, Optional

from fastapi import Cookie
from dishka.integrations.fastapi import FromDishka, inject

from amdb.application.common.view_models.my_detailed_watchlist import (
MyDetailedWatchlistViewModel,
)
from amdb.application.queries.my_detailed_watchlist import (
GetMyDeatiledWatchlistQuery,
)
from amdb.application.query_handlers.my_detailed_watchlist import (
GetMyDetailedWatchlistHandler,
)
from amdb.application.common.gateways.permissions import PermissionsGateway
from amdb.infrastructure.auth.session.session import SessionId
from amdb.infrastructure.auth.session.session_gateway import SessionGateway
from amdb.infrastructure.auth.session.identity_provider import (
SessionIdentityProvider,
)
from amdb.presentation.create_handler import CreateHandler
from amdb.presentation.web_api.constants import SESSION_ID_COOKIE


HandlerMaker = CreateHandler[GetMyDetailedWatchlistHandler]


@inject
async def get_my_detailed_watchlist(
*,
create_handler: Annotated[HandlerMaker, FromDishka()],
session_gateway: Annotated[SessionGateway, FromDishka()],
permissions_gateway: Annotated[PermissionsGateway, FromDishka()],
session_id: Annotated[
Optional[str],
Cookie(alias=SESSION_ID_COOKIE),
] = None,
limit: int = 100,
offset: int = 0,
) -> MyDetailedWatchlistViewModel:
"""
Returns current user watclist with movies information.
"""
identity_provider = SessionIdentityProvider(
session_id=SessionId(session_id) if session_id else None,
session_gateway=session_gateway,
permissions_gateway=permissions_gateway,
)

handler = create_handler(identity_provider)
query = GetMyDeatiledWatchlistQuery(
limit=limit,
offset=offset,
)

return handler.execute(query)
6 changes: 6 additions & 0 deletions src/amdb/presentation/web_api/watchlists/router.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
from fastapi import APIRouter

from .get_my_detailed import get_my_detailed_watchlist
from .add_movie import add_movie_to_watchlist
from .delete_movie import delete_movie_from_watchlist


watchlists_router = APIRouter(tags=["watchlists"])
watchlists_router.add_api_route(
path="/me/detailed-movies-for-later",
endpoint=get_my_detailed_watchlist,
methods=["GET"],
)
watchlists_router.add_api_route(
path="/my/movies-for-later",
endpoint=add_movie_to_watchlist,
Expand Down
Loading

0 comments on commit f499844

Please sign in to comment.