Skip to content

Commit

Permalink
Add DeleteFromWatchlistCommand
Browse files Browse the repository at this point in the history
  • Loading branch information
madnoberson committed Mar 12, 2024
1 parent 69e1bf0 commit df31f2f
Show file tree
Hide file tree
Showing 11 changed files with 325 additions and 7 deletions.
2 changes: 2 additions & 0 deletions src/amdb/application/command_handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"UnrateMovieHandler",
"ReviewMovieHandler",
"AddToWatchlistHandler",
"DeleteFromWatchlistHandler",
)

from .register_user import RegisterUserHandler
Expand All @@ -17,3 +18,4 @@
from .unrate_movie import UnrateMovieHandler
from .review_movie import ReviewMovieHandler
from .add_to_watchlist import AddToWatchlistHandler
from .delete_from_watchlist import DeleteFromWatchlistHandler
2 changes: 2 additions & 0 deletions src/amdb/application/command_handlers/add_to_watchlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,6 @@ def execute(self, command: AddToWatchlistCommand) -> MovieForLaterId:
)
self._movie_for_later_gateway.save(new_movie_for_later)

self._unit_of_work.commit()

return new_movie_for_later.id
42 changes: 42 additions & 0 deletions src/amdb/application/command_handlers/delete_from_watchlist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from amdb.application.common.gateways.movie_for_later import (
MovieForLaterGateway,
)
from amdb.application.common.unit_of_work import UnitOfWork
from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.common.constants.exceptions import (
USER_IS_NOT_OWNER,
MOVIE_NOT_IN_WATCHLIST,
)
from amdb.application.common.exception import ApplicationError
from amdb.application.commands.delete_from_watchlist import (
DeleteFromWatchlistCommand,
)


class DeleteFromWatchlistHandler:
def __init__(
self,
*,
movie_for_later_gateway: MovieForLaterGateway,
unit_of_work: UnitOfWork,
identity_provider: IdentityProvider,
) -> None:
self._movie_for_later_gateway = movie_for_later_gateway
self._unit_of_work = unit_of_work
self._identity_provider = identity_provider

def execute(self, command: DeleteFromWatchlistCommand) -> None:
current_user_id = self._identity_provider.user_id()

movie_for_later = self._movie_for_later_gateway.with_id(
command.movie_for_later_id,
)
if not movie_for_later:
raise ApplicationError(MOVIE_NOT_IN_WATCHLIST)

if movie_for_later.user_id != current_user_id:
raise ApplicationError(USER_IS_NOT_OWNER)

self._movie_for_later_gateway.delete(movie_for_later)

self._unit_of_work.commit()
8 changes: 8 additions & 0 deletions src/amdb/application/commands/delete_from_watchlist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from dataclasses import dataclass

from amdb.domain.entities.movie_for_later import MovieForLaterId


@dataclass(frozen=True, slots=True)
class DeleteFromWatchlistCommand:
movie_for_later_id: MovieForLaterId
1 change: 1 addition & 0 deletions src/amdb/application/common/constants/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
REVIEW_DOES_NOT_EXIST = "Review doesn't exist"

MOVIE_ALREADY_IN_WATCHLIST = "Movie already in watchlist"
MOVIE_NOT_IN_WATCHLIST = "Movie not in watchlist"
11 changes: 10 additions & 1 deletion src/amdb/application/common/gateways/movie_for_later.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
from typing import Protocol, Optional

from amdb.domain.entities.movie_for_later import MovieForLater
from amdb.domain.entities.movie_for_later import MovieForLater, MovieForLaterId
from amdb.domain.entities.movie import MovieId
from amdb.domain.entities.user import UserId


class MovieForLaterGateway(Protocol):
def with_id(
self,
movie_for_later_id: MovieForLaterId,
) -> Optional[MovieForLater]:
raise NotImplementedError

def with_movie_id_and_user_id(
self,
user_id: UserId,
Expand All @@ -15,3 +21,6 @@ def with_movie_id_and_user_id(

def save(self, movie_for_later: MovieForLater) -> None:
raise NotImplementedError

def delete(self, movie_for_later: MovieForLater) -> None:
raise NotImplementedError
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Annotated, Optional

from sqlalchemy import Connection, Row, select, insert, and_
from sqlalchemy import Connection, Row, select, insert, delete, and_

from amdb.domain.entities.user import UserId
from amdb.domain.entities.movie import MovieId
Expand All @@ -14,6 +14,18 @@ class MovieForLaterMapper:
def __init__(self, connection: Connection) -> None:
self._connection = connection

def with_id(
self,
movie_for_later_id: MovieForLaterId,
) -> Optional[MovieForLater]:
statement = select(MovieForLaterModel).where(
MovieForLaterModel.id == movie_for_later_id,
)
row = self._connection.execute(statement).one_or_none()
if row:
return self._to_entity(row) # type: ignore
return None

def with_movie_id_and_user_id(
self,
user_id: UserId,
Expand All @@ -27,7 +39,7 @@ def with_movie_id_and_user_id(
)
row = self._connection.execute(statement).one_or_none()
if row:
return self._to_entity(row)
return self._to_entity(row) # type: ignore
return None

def save(self, movie_for_later: MovieForLater) -> None:
Expand All @@ -40,6 +52,12 @@ def save(self, movie_for_later: MovieForLater) -> None:
)
self._connection.execute(statement)

def delete(self, movie_for_later: MovieForLater) -> None:
statement = delete(MovieForLaterModel).where(
MovieForLaterModel.id == movie_for_later.id,
)
self._connection.execute(statement)

def _to_entity(
self,
row: Annotated[MovieForLaterModel, Row],
Expand Down
24 changes: 21 additions & 3 deletions src/amdb/main/providers/command_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
UnrateMovieHandler,
ReviewMovieHandler,
AddToWatchlistHandler,
DeleteFromWatchlistHandler,
)
from amdb.presentation.create_handler import CreateHandler

Expand Down Expand Up @@ -108,7 +109,7 @@ def create_handler(
return create_handler

@provide
def rate_movie_handler(
def rate_movie(
self,
access_concern: AccessConcern,
rate_movie: RateMovie,
Expand All @@ -135,7 +136,7 @@ def create_handler(
return create_handler

@provide
def unrate_movie_handler(
def unrate_movie(
self,
access_concern: AccessConcern,
unrate_movie: UnrateMovie,
Expand All @@ -160,7 +161,7 @@ def create_handler(
return create_handler

@provide
def review_movie_handler(
def review_movie(
self,
access_concern: AccessConcern,
review_movie: ReviewMovie,
Expand Down Expand Up @@ -208,3 +209,20 @@ def create_handler(
)

return create_handler

@provide
def delete_from_watchlist(
self,
movie_for_later_gateway: MovieForLaterGateway,
unit_of_work: UnitOfWork,
) -> CreateHandler[DeleteFromWatchlistHandler]:
def create_handler(
identity_provider: IdentityProvider,
) -> DeleteFromWatchlistHandler:
return DeleteFromWatchlistHandler(
movie_for_later_gateway=movie_for_later_gateway,
unit_of_work=unit_of_work,
identity_provider=identity_provider,
)

return create_handler
56 changes: 56 additions & 0 deletions src/amdb/presentation/web_api/watchlists/delete_movie.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.domain.entities.movie_for_later import MovieForLaterId
from amdb.application.commands.delete_from_watchlist import (
DeleteFromWatchlistCommand,
)
from amdb.application.command_handlers.delete_from_watchlist import (
DeleteFromWatchlistHandler,
)
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[DeleteFromWatchlistHandler]


@inject
async def delete_movie_from_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,
movie_for_later_id: MovieForLaterId,
) -> None:
"""
Deletes movie from watchlist. \n\n
### Returns 400:
* When movie not in watchlist
* When user is not a watchlist owner
"""
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)
command = DeleteFromWatchlistCommand(
movie_for_later_id=movie_for_later_id,
)

handler.execute(command)
8 changes: 7 additions & 1 deletion src/amdb/presentation/web_api/watchlists/router.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
from fastapi import APIRouter

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="/my/watchlist/movies",
path="/my/movies-for-later",
endpoint=add_movie_to_watchlist,
methods=["POST"],
)
watchlists_router.add_api_route(
path="/my/movies-for-later/{movie_for_later_id}",
endpoint=delete_movie_from_watchlist,
methods=["DELETE"],
)
Loading

0 comments on commit df31f2f

Please sign in to comment.