From 0cd0914c999478f28970da364befc58eb84ffdc0 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Fri, 16 Feb 2024 13:07:59 +0400
Subject: [PATCH 01/39] Refactor pyproject.toml, add .ruff.toml, format with
ruff
---
.ruff.toml | 26 ++++++
pyproject.toml | 56 +++----------
.../command_handlers/create_movie.py | 12 ++-
.../command_handlers/delete_movie.py | 8 +-
.../command_handlers/rate_movie.py | 8 +-
.../command_handlers/register_user.py | 8 +-
.../command_handlers/review_movie.py | 8 +-
.../command_handlers/unrate_movie.py | 8 +-
.../application/query_handlers/get_movie.py | 8 +-
.../query_handlers/get_movie_ratings.py | 17 +++-
.../query_handlers/get_movie_reviews.py | 17 +++-
.../application/query_handlers/get_movies.py | 12 ++-
.../query_handlers/get_my_ratings.py | 17 +++-
.../query_handlers/get_my_reviews.py | 17 +++-
.../application/query_handlers/get_rating.py | 8 +-
.../application/query_handlers/get_review.py | 8 +-
src/amdb/application/query_handlers/login.py | 4 +-
src/amdb/domain/constants/exceptions.py | 4 +-
src/amdb/domain/services/access_concern.py | 8 +-
src/amdb/domain/services/rate_movie.py | 4 +-
src/amdb/domain/services/unrate_movie.py | 6 +-
.../auth/session/identity_provider.py | 8 +-
.../password_manager/password_manager.py | 4 +-
.../persistence/alembic/config.py | 4 +-
.../migrations/versions/65f8840f4494_.py | 12 ++-
.../migrations/versions/85a348467b90_.py | 4 +-
.../migrations/versions/9e92de201574_.py | 4 +-
.../persistence/sqlalchemy/gateway_factory.py | 4 +-
.../persistence/sqlalchemy/gateways/movie.py | 8 +-
.../persistence/sqlalchemy/gateways/rating.py | 18 ++++-
.../persistence/sqlalchemy/gateways/review.py | 18 ++++-
.../persistence/sqlalchemy/gateways/user.py | 4 +-
.../sqlalchemy/gateways/user_password_hash.py | 8 +-
.../persistence/sqlalchemy/mappers/movie.py | 4 +-
.../persistence/sqlalchemy/mappers/rating.py | 4 +-
.../persistence/sqlalchemy/mappers/review.py | 10 ++-
.../persistence/sqlalchemy/mappers/user.py | 4 +-
src/amdb/main/cli/di.py | 12 ++-
src/amdb/main/ioc.py | 80 ++++++++++++++-----
src/amdb/main/web_api/app.py | 4 +-
src/amdb/main/web_api/config.py | 4 +-
src/amdb/main/web_api/di.py | 20 +++--
src/amdb/presentation/cli/movie.py | 4 +-
src/amdb/presentation/handler_factory.py | 12 ++-
.../web_api/dependencies/identity_provider.py | 20 +++--
.../web_api/exception_handlers.py | 8 +-
.../web_api/routers/auth/login.py | 12 ++-
.../web_api/routers/auth/register.py | 12 ++-
.../web_api/routers/movies/get_movie.py | 12 ++-
.../web_api/routers/movies/get_movies.py | 12 ++-
.../routers/ratings/get_movie_ratings.py | 21 +++--
.../web_api/routers/ratings/get_my_ratings.py | 21 +++--
.../web_api/routers/ratings/get_rating.py | 12 ++-
.../web_api/routers/ratings/rate_movie.py | 12 ++-
.../web_api/routers/ratings/unrate_movie.py | 12 ++-
.../routers/reviews/get_movie_reviews.py | 21 +++--
.../web_api/routers/reviews/get_my_reviews.py | 21 +++--
.../web_api/routers/reviews/get_review.py | 12 ++-
.../web_api/routers/reviews/review_movie.py | 12 ++-
.../command_handlers/test_create_movie.py | 12 ++-
.../command_handlers/test_delete_movie.py | 8 +-
.../command_handlers/test_rate_movie.py | 8 +-
.../command_handlers/test_register_user.py | 8 +-
.../command_handlers/test_review_movie.py | 8 +-
.../command_handlers/test_unrate_movie.py | 8 +-
tests/unit/application/conftest.py | 36 ++++++---
.../query_handlers/test_get_movie.py | 8 +-
.../query_handlers/test_get_movie_ratings.py | 21 +++--
.../query_handlers/test_get_movie_reviews.py | 21 +++--
.../query_handlers/test_get_movies.py | 12 ++-
.../query_handlers/test_get_my_ratings.py | 21 +++--
.../query_handlers/test_get_my_reviews.py | 21 +++--
.../query_handlers/test_get_rating.py | 8 +-
.../query_handlers/test_get_review.py | 8 +-
.../application/query_handlers/test_login.py | 4 +-
.../infrastructure/alembic/test_stairway.py | 12 ++-
76 files changed, 700 insertions(+), 262 deletions(-)
create mode 100644 .ruff.toml
diff --git a/.ruff.toml b/.ruff.toml
new file mode 100644
index 0000000..e3dc0bb
--- /dev/null
+++ b/.ruff.toml
@@ -0,0 +1,26 @@
+line-length = 79
+src = ["src"]
+include = ["src/**.py", "tests/**.py"]
+
+extend-select = [
+ "N", # https://docs.astral.sh/ruff/settings/#pep8-naming
+ "EM", # https://docs.astral.sh/ruff/settings/#flake8-errmsg
+ "ISC", # https://docs.astral.sh/ruff/settings/#flake8-implicit-str-concat
+ "G", # https://docs.astral.sh/ruff/rules/#flake8-logging-format-g
+ "Q", # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt
+]
+select = [
+ "F401", # unused-import
+ "F406", # undefined-local-with-nested-import-star-usage
+ "COM812", # missing-trailing-comma
+ "DTZ003", # call-datetime-utcnow
+ "EM102", # f-string-in-exception
+ "INP001", # implicit-namespace-package
+ "PIE794", # duplicate-class-field-definition
+ "PIE796", # non-unique-enums
+ "T201", # print
+ "SLF001", # private-member-access
+]
+
+[format]
+quote-style = "double"
diff --git a/pyproject.toml b/pyproject.toml
index fe9e7be..1c0694a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -2,6 +2,13 @@
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
+[tool.setuptools.packages.find]
+where = ["src"]
+include = ["amdb*"]
+
+[tool.setuptools.package-data]
+amdb = ["infrastructure/persistence/alembic/alembic.ini"]
+
[project]
name = "amdb"
description = "Awesome Movie Database Backend"
@@ -32,10 +39,10 @@ web_api = [
cli = [
"typer[all]==0.9.*",
]
-style = [
- "mypy==1.8.0",
- "ruff==0.1.9",
- "types-redis",
+dev = [
+ "mypy==1.8.*",
+ "ruff==0.1.*",
+ "pre-commit==3.5.*",
]
test = [
"pytest",
@@ -43,48 +50,7 @@ test = [
coverage = [
"pytest-cov",
]
-dev = [
- "amdb[web_api,cli,style,test,coverage]",
- "pre-commit==3.5.0",
-]
[project.scripts]
amdb-cli = "amdb.main.cli.__main__:main"
amdb-web_api = "amdb.main.web_api.__main__:main"
-
-[tool.setuptools.packages.find]
-where = ["src"]
-include = ["amdb*"]
-
-[tool.setuptools.package-data]
-amdb = ["infrastructure/persistence/alembic/alembic.ini"]
-
-[tool.pytest.ini_options]
-pythonpath = "src/"
-
-[tool.ruff.lint]
-extend-select = [
- "N", # https://docs.astral.sh/ruff/settings/#pep8-naming
- "EM", # https://docs.astral.sh/ruff/settings/#flake8-errmsg
- "ISC", # https://docs.astral.sh/ruff/settings/#flake8-implicit-str-concat
- "G", # https://docs.astral.sh/ruff/rules/#flake8-logging-format-g
- "Q", # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt
-]
-select = [
- "F401", # unused-import
- "F406", # undefined-local-with-nested-import-star-usage
- "COM812", # missing-trailing-comma
- "DTZ003", # call-datetime-utcnow
- "EM102", # f-string-in-exception
- "INP001", # implicit-namespace-package
- "PIE794", # duplicate-class-field-definition
- "PIE796", # non-unique-enums
- "T201", # print
- "SLF001", # private-member-access
-]
-
-[tool.ruff]
-line-length = 99
-
-[tool.ruff.format]
-quote-style = "double"
diff --git a/src/amdb/application/command_handlers/create_movie.py b/src/amdb/application/command_handlers/create_movie.py
index 0b81bb5..3c481e0 100644
--- a/src/amdb/application/command_handlers/create_movie.py
+++ b/src/amdb/application/command_handlers/create_movie.py
@@ -3,11 +3,17 @@
from amdb.domain.entities.movie import MovieId
from amdb.domain.services.access_concern import AccessConcern
from amdb.domain.services.create_movie import CreateMovie
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
-from amdb.application.common.constants.exceptions import CREATE_MOVIE_ACCESS_DENIED
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
+from amdb.application.common.constants.exceptions import (
+ CREATE_MOVIE_ACCESS_DENIED,
+)
from amdb.application.common.exception import ApplicationError
from amdb.application.commands.create_movie import CreateMovieCommand
diff --git a/src/amdb/application/command_handlers/delete_movie.py b/src/amdb/application/command_handlers/delete_movie.py
index 7cca51f..c4b5963 100644
--- a/src/amdb/application/command_handlers/delete_movie.py
+++ b/src/amdb/application/command_handlers/delete_movie.py
@@ -1,10 +1,14 @@
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.rating_gateway import RatingGateway
from amdb.application.common.interfaces.review_gateway import ReviewGateway
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.common.constants.exceptions import (
DELETE_MOVIE_ACCESS_DENIED,
MOVIE_DOES_NOT_EXIST,
diff --git a/src/amdb/application/command_handlers/rate_movie.py b/src/amdb/application/command_handlers/rate_movie.py
index bb022e7..ee50707 100644
--- a/src/amdb/application/command_handlers/rate_movie.py
+++ b/src/amdb/application/command_handlers/rate_movie.py
@@ -7,12 +7,16 @@
from amdb.domain.entities.rating import RatingId
from amdb.domain.services.access_concern import AccessConcern
from amdb.domain.services.rate_movie import RateMovie
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.user_gateway import UserGateway
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.rating_gateway import RatingGateway
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.common.constants.exceptions import (
RATE_MOVIE_ACCESS_DENIED,
MOVIE_DOES_NOT_EXIST,
diff --git a/src/amdb/application/command_handlers/register_user.py b/src/amdb/application/command_handlers/register_user.py
index 6498ff0..c3f3431 100644
--- a/src/amdb/application/command_handlers/register_user.py
+++ b/src/amdb/application/command_handlers/register_user.py
@@ -3,10 +3,14 @@
from amdb.domain.entities.user import UserId
from amdb.domain.services.create_user import CreateUser
from amdb.application.common.interfaces.user_gateway import UserGateway
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
from amdb.application.common.interfaces.password_manager import PasswordManager
-from amdb.application.common.constants.exceptions import USER_NAME_ALREADY_EXISTS
+from amdb.application.common.constants.exceptions import (
+ USER_NAME_ALREADY_EXISTS,
+)
from amdb.application.common.exception import ApplicationError
from amdb.application.commands.register_user import RegisterUserCommand
diff --git a/src/amdb/application/command_handlers/review_movie.py b/src/amdb/application/command_handlers/review_movie.py
index 3a6b80d..036305e 100644
--- a/src/amdb/application/command_handlers/review_movie.py
+++ b/src/amdb/application/command_handlers/review_movie.py
@@ -7,12 +7,16 @@
from amdb.domain.entities.review import ReviewId
from amdb.domain.services.access_concern import AccessConcern
from amdb.domain.services.review_movie import ReviewMovie
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.user_gateway import UserGateway
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.review_gateway import ReviewGateway
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.common.constants.exceptions import (
REVIEW_MOVIE_ACCESS_DENIED,
MOVIE_DOES_NOT_EXIST,
diff --git a/src/amdb/application/command_handlers/unrate_movie.py b/src/amdb/application/command_handlers/unrate_movie.py
index f9fb90a..67e19e2 100644
--- a/src/amdb/application/command_handlers/unrate_movie.py
+++ b/src/amdb/application/command_handlers/unrate_movie.py
@@ -3,11 +3,15 @@
from amdb.domain.entities.movie import Movie
from amdb.domain.services.access_concern import AccessConcern
from amdb.domain.services.unrate_movie import UnrateMovie
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.rating_gateway import RatingGateway
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.common.constants.exceptions import (
UNRATE_MOVIE_ACCESS_DENIED,
USER_IS_NOT_OWNER,
diff --git a/src/amdb/application/query_handlers/get_movie.py b/src/amdb/application/query_handlers/get_movie.py
index 52fe8e6..0632db5 100644
--- a/src/amdb/application/query_handlers/get_movie.py
+++ b/src/amdb/application/query_handlers/get_movie.py
@@ -1,7 +1,11 @@
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.queries.get_movie import GetMovieQuery, GetMovieResult
from amdb.application.common.constants.exceptions import (
GET_MOVIE_ACCESS_DENIED,
diff --git a/src/amdb/application/query_handlers/get_movie_ratings.py b/src/amdb/application/query_handlers/get_movie_ratings.py
index 880e706..6566f04 100644
--- a/src/amdb/application/query_handlers/get_movie_ratings.py
+++ b/src/amdb/application/query_handlers/get_movie_ratings.py
@@ -1,9 +1,16 @@
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.rating_gateway import RatingGateway
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
-from amdb.application.queries.get_movie_ratings import GetMovieRatingsQuery, GetMovieRatingsResult
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
+from amdb.application.queries.get_movie_ratings import (
+ GetMovieRatingsQuery,
+ GetMovieRatingsResult,
+)
from amdb.application.common.constants.exceptions import (
GET_MOVIE_RATINGS_ACCESS_DENIED,
MOVIE_DOES_NOT_EXIST,
@@ -29,7 +36,9 @@ def __init__(
def execute(self, query: GetMovieRatingsQuery) -> GetMovieRatingsResult:
current_permissions = self._identity_provider.get_permissions()
- required_permissions = self._permissions_gateway.for_get_movie_ratings()
+ required_permissions = (
+ self._permissions_gateway.for_get_movie_ratings()
+ )
access = self._access_concern.authorize(
current_permissions=current_permissions,
required_permissions=required_permissions,
diff --git a/src/amdb/application/query_handlers/get_movie_reviews.py b/src/amdb/application/query_handlers/get_movie_reviews.py
index b4d0474..7d8534f 100644
--- a/src/amdb/application/query_handlers/get_movie_reviews.py
+++ b/src/amdb/application/query_handlers/get_movie_reviews.py
@@ -1,14 +1,21 @@
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.review_gateway import ReviewGateway
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.common.constants.exceptions import (
GET_MOVIE_REVIEWS_ACCESS_DENIED,
MOVIE_DOES_NOT_EXIST,
)
from amdb.application.common.exception import ApplicationError
-from amdb.application.queries.get_movie_reviews import GetMovieReviewsQuery, GetMovieReviewsResult
+from amdb.application.queries.get_movie_reviews import (
+ GetMovieReviewsQuery,
+ GetMovieReviewsResult,
+)
class GetMovieReviewsHandler:
@@ -29,7 +36,9 @@ def __init__(
def execute(self, query: GetMovieReviewsQuery) -> GetMovieReviewsResult:
current_permissions = self._identity_provider.get_permissions()
- required_permissions = self._permissions_gateway.for_get_movie_reviews()
+ required_permissions = (
+ self._permissions_gateway.for_get_movie_reviews()
+ )
access = self._access_concern.authorize(
current_permissions=current_permissions,
required_permissions=required_permissions,
diff --git a/src/amdb/application/query_handlers/get_movies.py b/src/amdb/application/query_handlers/get_movies.py
index 2f685f9..d06909d 100644
--- a/src/amdb/application/query_handlers/get_movies.py
+++ b/src/amdb/application/query_handlers/get_movies.py
@@ -1,9 +1,15 @@
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.queries.get_movies import GetMoviesQuery, GetMoviesResult
-from amdb.application.common.constants.exceptions import GET_MOVIES_ACCESS_DENIED
+from amdb.application.common.constants.exceptions import (
+ GET_MOVIES_ACCESS_DENIED,
+)
from amdb.application.common.exception import ApplicationError
diff --git a/src/amdb/application/query_handlers/get_my_ratings.py b/src/amdb/application/query_handlers/get_my_ratings.py
index e09ad50..5fcef7a 100644
--- a/src/amdb/application/query_handlers/get_my_ratings.py
+++ b/src/amdb/application/query_handlers/get_my_ratings.py
@@ -1,9 +1,18 @@
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.rating_gateway import RatingGateway
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
-from amdb.application.queries.get_my_ratings import GetMyRatingsQuery, GetMyRatingsResult
-from amdb.application.common.constants.exceptions import GET_MY_RATINGS_ACCESS_DENIED
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
+from amdb.application.queries.get_my_ratings import (
+ GetMyRatingsQuery,
+ GetMyRatingsResult,
+)
+from amdb.application.common.constants.exceptions import (
+ GET_MY_RATINGS_ACCESS_DENIED,
+)
from amdb.application.common.exception import ApplicationError
diff --git a/src/amdb/application/query_handlers/get_my_reviews.py b/src/amdb/application/query_handlers/get_my_reviews.py
index 7db59dc..9004cbd 100644
--- a/src/amdb/application/query_handlers/get_my_reviews.py
+++ b/src/amdb/application/query_handlers/get_my_reviews.py
@@ -1,10 +1,19 @@
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.review_gateway import ReviewGateway
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
-from amdb.application.common.constants.exceptions import GET_MY_REVIEWS_ACCESS_DENIED
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
+from amdb.application.common.constants.exceptions import (
+ GET_MY_REVIEWS_ACCESS_DENIED,
+)
from amdb.application.common.exception import ApplicationError
-from amdb.application.queries.get_my_reviews import GetMyReviewsQuery, GetMyReviewsResult
+from amdb.application.queries.get_my_reviews import (
+ GetMyReviewsQuery,
+ GetMyReviewsResult,
+)
class GetMyReviewsHandler:
diff --git a/src/amdb/application/query_handlers/get_rating.py b/src/amdb/application/query_handlers/get_rating.py
index 9615d0b..2c902ea 100644
--- a/src/amdb/application/query_handlers/get_rating.py
+++ b/src/amdb/application/query_handlers/get_rating.py
@@ -1,7 +1,11 @@
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.rating_gateway import RatingGateway
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.queries.get_rating import GetRatingQuery, GetRatingResult
from amdb.application.common.constants.exceptions import (
GET_RATING_ACCESS_DENIED,
diff --git a/src/amdb/application/query_handlers/get_review.py b/src/amdb/application/query_handlers/get_review.py
index bd8bc32..43cf7de 100644
--- a/src/amdb/application/query_handlers/get_review.py
+++ b/src/amdb/application/query_handlers/get_review.py
@@ -1,7 +1,11 @@
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.review_gateway import ReviewGateway
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.common.constants.exceptions import (
GET_REVIEW_ACCESS_DENIED,
REVIEW_DOES_NOT_EXIST,
diff --git a/src/amdb/application/query_handlers/login.py b/src/amdb/application/query_handlers/login.py
index 62b9766..47a01e1 100644
--- a/src/amdb/application/query_handlers/login.py
+++ b/src/amdb/application/query_handlers/login.py
@@ -3,7 +3,9 @@
from amdb.domain.entities.user import UserId
from amdb.domain.services.access_concern import AccessConcern
from amdb.application.common.interfaces.user_gateway import UserGateway
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.password_manager import PasswordManager
from amdb.application.common.constants.exceptions import (
LOGIN_ACCESS_DENIED,
diff --git a/src/amdb/domain/constants/exceptions.py b/src/amdb/domain/constants/exceptions.py
index a85439d..dff4fd1 100644
--- a/src/amdb/domain/constants/exceptions.py
+++ b/src/amdb/domain/constants/exceptions.py
@@ -1 +1,3 @@
-INVALID_RATING_VALUE = "Rating value must be from 0 to 10 and be a multiple of 0.5"
+INVALID_RATING_VALUE = (
+ "Rating value must be from 0 to 10 and be a multiple of 0.5"
+)
diff --git a/src/amdb/domain/services/access_concern.py b/src/amdb/domain/services/access_concern.py
index d9c515c..29ad163 100644
--- a/src/amdb/domain/services/access_concern.py
+++ b/src/amdb/domain/services/access_concern.py
@@ -1,3 +1,7 @@
class AccessConcern:
- def authorize(self, current_permissions: int, required_permissions: int) -> bool:
- return current_permissions & required_permissions == required_permissions
+ def authorize(
+ self, current_permissions: int, required_permissions: int
+ ) -> bool:
+ return (
+ current_permissions & required_permissions == required_permissions
+ )
diff --git a/src/amdb/domain/services/rate_movie.py b/src/amdb/domain/services/rate_movie.py
index 7dd9d8d..0e8a5c3 100644
--- a/src/amdb/domain/services/rate_movie.py
+++ b/src/amdb/domain/services/rate_movie.py
@@ -20,7 +20,9 @@ def __call__(
if rating <= 0 or rating > 10 or rating % 0.5 != 0:
raise DomainError(INVALID_RATING_VALUE)
- movie.rating = (movie.rating * movie.rating_count + rating) / (movie.rating_count + 1)
+ movie.rating = (movie.rating * movie.rating_count + rating) / (
+ movie.rating_count + 1
+ )
movie.rating_count += 1
return Rating(
diff --git a/src/amdb/domain/services/unrate_movie.py b/src/amdb/domain/services/unrate_movie.py
index 4ecb0af..44edcd1 100644
--- a/src/amdb/domain/services/unrate_movie.py
+++ b/src/amdb/domain/services/unrate_movie.py
@@ -13,7 +13,7 @@ def __call__(
movie.rating = 0
movie.rating_count = 0
else:
- movie.rating = (movie.rating * movie.rating_count - rating.value) / (
- movie.rating_count - 1
- )
+ movie.rating = (
+ movie.rating * movie.rating_count - rating.value
+ ) / (movie.rating_count - 1)
movie.rating_count -= 1
diff --git a/src/amdb/infrastructure/auth/session/identity_provider.py b/src/amdb/infrastructure/auth/session/identity_provider.py
index 554192a..5c3eea8 100644
--- a/src/amdb/infrastructure/auth/session/identity_provider.py
+++ b/src/amdb/infrastructure/auth/session/identity_provider.py
@@ -1,8 +1,12 @@
from typing import Optional, cast
from amdb.domain.entities.user import UserId
-from amdb.infrastructure.persistence.redis.gateways.session import RedisSessionGateway
-from amdb.infrastructure.persistence.redis.gateways.permissions import RedisPermissionsGateway
+from amdb.infrastructure.persistence.redis.gateways.session import (
+ RedisSessionGateway,
+)
+from amdb.infrastructure.persistence.redis.gateways.permissions import (
+ RedisPermissionsGateway,
+)
from amdb.infrastructure.exception import InfrastructureError
from .constants.exceptions import NO_SESSION_ID, SESSION_DOES_NOT_EXIST
from .model import SessionId
diff --git a/src/amdb/infrastructure/password_manager/password_manager.py b/src/amdb/infrastructure/password_manager/password_manager.py
index 46416bd..254471f 100644
--- a/src/amdb/infrastructure/password_manager/password_manager.py
+++ b/src/amdb/infrastructure/password_manager/password_manager.py
@@ -25,4 +25,6 @@ def set(self, user_id: UserId, password: str) -> None:
def verify(self, user_id: UserId, password: str) -> bool:
user_password_hash = self._user_password_hash_gateway.get(user_id)
user_password_hash = cast(UserPasswordHash, user_password_hash)
- return self._hasher.verify(password.encode(), user_password_hash.password_hash)
+ return self._hasher.verify(
+ password.encode(), user_password_hash.password_hash
+ )
diff --git a/src/amdb/infrastructure/persistence/alembic/config.py b/src/amdb/infrastructure/persistence/alembic/config.py
index 0a75e2f..5d7f07d 100644
--- a/src/amdb/infrastructure/persistence/alembic/config.py
+++ b/src/amdb/infrastructure/persistence/alembic/config.py
@@ -4,5 +4,7 @@
ALEMBIC_CONFIG = str(
- importlib.resources.files(amdb.infrastructure.persistence.alembic).joinpath("alembic.ini"),
+ importlib.resources.files(
+ amdb.infrastructure.persistence.alembic
+ ).joinpath("alembic.ini"),
)
diff --git a/src/amdb/infrastructure/persistence/alembic/migrations/versions/65f8840f4494_.py b/src/amdb/infrastructure/persistence/alembic/migrations/versions/65f8840f4494_.py
index 44504f6..fa79316 100644
--- a/src/amdb/infrastructure/persistence/alembic/migrations/versions/65f8840f4494_.py
+++ b/src/amdb/infrastructure/persistence/alembic/migrations/versions/65f8840f4494_.py
@@ -101,11 +101,17 @@ def upgrade() -> None:
""",
)
with op.batch_alter_table("ratings") as batch_op:
- batch_op.add_column(sa.Column("id", sa.Uuid(), nullable=False, default="uuid7()"))
+ batch_op.add_column(
+ sa.Column("id", sa.Uuid(), nullable=False, default="uuid7()")
+ )
batch_op.drop_constraint("pk_ratings", type_="primary")
batch_op.create_primary_key("pk_ratings", ["id"])
- batch_op.create_unique_constraint("uq_ratings", ("user_id", "movie_id"))
- op.create_unique_constraint("uq_reviews", "reviews", ("user_id", "movie_id"))
+ batch_op.create_unique_constraint(
+ "uq_ratings", ("user_id", "movie_id")
+ )
+ op.create_unique_constraint(
+ "uq_reviews", "reviews", ("user_id", "movie_id")
+ )
def downgrade() -> None:
diff --git a/src/amdb/infrastructure/persistence/alembic/migrations/versions/85a348467b90_.py b/src/amdb/infrastructure/persistence/alembic/migrations/versions/85a348467b90_.py
index a5b2eb5..853078e 100644
--- a/src/amdb/infrastructure/persistence/alembic/migrations/versions/85a348467b90_.py
+++ b/src/amdb/infrastructure/persistence/alembic/migrations/versions/85a348467b90_.py
@@ -35,7 +35,9 @@ def upgrade() -> None:
sa.Column("type", sa.SmallInteger(), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
- sa.ForeignKeyConstraint(["movie_id"], ["movies.id"], ondelete="CASCADE"),
+ sa.ForeignKeyConstraint(
+ ["movie_id"], ["movies.id"], ondelete="CASCADE"
+ ),
sa.PrimaryKeyConstraint("id"),
)
diff --git a/src/amdb/infrastructure/persistence/alembic/migrations/versions/9e92de201574_.py b/src/amdb/infrastructure/persistence/alembic/migrations/versions/9e92de201574_.py
index f53e96d..22b25a5 100644
--- a/src/amdb/infrastructure/persistence/alembic/migrations/versions/9e92de201574_.py
+++ b/src/amdb/infrastructure/persistence/alembic/migrations/versions/9e92de201574_.py
@@ -49,7 +49,9 @@ def upgrade() -> None:
sa.Column("user_id", sa.Uuid(), nullable=False),
sa.Column("value", sa.Float(), nullable=False),
sa.Column("created_at", sa.TIMESTAMP(timezone=True), nullable=True),
- sa.ForeignKeyConstraint(["movie_id"], ["movies.id"], ondelete="CASCADE"),
+ sa.ForeignKeyConstraint(
+ ["movie_id"], ["movies.id"], ondelete="CASCADE"
+ ),
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("movie_id", "user_id"),
)
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/gateway_factory.py b/src/amdb/infrastructure/persistence/sqlalchemy/gateway_factory.py
index e10eba9..b30327c 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/gateway_factory.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/gateway_factory.py
@@ -38,7 +38,9 @@ def rating(self) -> SQLAlchemyRatingGateway:
return SQLAlchemyRatingGateway(self._session, RatingMapper())
def user_password_hash(self) -> SQLAlchemyUserPasswordHashGateway:
- return SQLAlchemyUserPasswordHashGateway(self._session, UserPasswordHashMapper())
+ return SQLAlchemyUserPasswordHashGateway(
+ self._session, UserPasswordHashMapper()
+ )
def review(self) -> SQLAlchemyReviewGateway:
return SQLAlchemyReviewGateway(self._session, ReviewMapper())
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/movie.py b/src/amdb/infrastructure/persistence/sqlalchemy/gateways/movie.py
index 7a34bb4..b7e38a6 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/movie.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/gateways/movie.py
@@ -4,8 +4,12 @@
from sqlalchemy.orm.session import Session
from amdb.domain.entities.movie import MovieId, Movie as MovieEntity
-from amdb.infrastructure.persistence.sqlalchemy.models.movie import Movie as MovieModel
-from amdb.infrastructure.persistence.sqlalchemy.mappers.movie import MovieMapper
+from amdb.infrastructure.persistence.sqlalchemy.models.movie import (
+ Movie as MovieModel,
+)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.movie import (
+ MovieMapper,
+)
class SQLAlchemyMovieGateway:
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/rating.py b/src/amdb/infrastructure/persistence/sqlalchemy/gateways/rating.py
index ec3c2c0..c20f165 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/rating.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/gateways/rating.py
@@ -6,8 +6,12 @@
from amdb.domain.entities.user import UserId
from amdb.domain.entities.movie import MovieId
from amdb.domain.entities.rating import RatingId, Rating as RatingEntity
-from amdb.infrastructure.persistence.sqlalchemy.models.rating import Rating as RatingModel
-from amdb.infrastructure.persistence.sqlalchemy.mappers.rating import RatingMapper
+from amdb.infrastructure.persistence.sqlalchemy.models.rating import (
+ Rating as RatingModel,
+)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.rating import (
+ RatingMapper,
+)
class SQLAlchemyRatingGateway:
@@ -48,7 +52,10 @@ def list_with_movie_id(
offset: int,
) -> list[RatingEntity]:
statement = (
- select(RatingModel).where(RatingModel.movie_id == movie_id).limit(limit).offset(offset)
+ select(RatingModel)
+ .where(RatingModel.movie_id == movie_id)
+ .limit(limit)
+ .offset(offset)
)
rating_models = self._session.scalars(statement)
@@ -66,7 +73,10 @@ def list_with_user_id(
offset: int,
) -> list[RatingEntity]:
statement = (
- select(RatingModel).where(RatingModel.user_id == user_id).limit(limit).offset(offset)
+ select(RatingModel)
+ .where(RatingModel.user_id == user_id)
+ .limit(limit)
+ .offset(offset)
)
rating_models = self._session.scalars(statement)
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/review.py b/src/amdb/infrastructure/persistence/sqlalchemy/gateways/review.py
index 1f5582f..5c76ea7 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/review.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/gateways/review.py
@@ -6,8 +6,12 @@
from amdb.domain.entities.user import UserId
from amdb.domain.entities.movie import MovieId
from amdb.domain.entities.review import ReviewId, Review as ReviewEntity
-from amdb.infrastructure.persistence.sqlalchemy.models.review import Review as ReviewModel
-from amdb.infrastructure.persistence.sqlalchemy.mappers.review import ReviewMapper
+from amdb.infrastructure.persistence.sqlalchemy.models.review import (
+ Review as ReviewModel,
+)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.review import (
+ ReviewMapper,
+)
class SQLAlchemyReviewGateway:
@@ -48,7 +52,10 @@ def list_with_movie_id(
offset: int,
) -> list[ReviewEntity]:
statement = (
- select(ReviewModel).where(ReviewModel.movie_id == movie_id).limit(limit).offset(offset)
+ select(ReviewModel)
+ .where(ReviewModel.movie_id == movie_id)
+ .limit(limit)
+ .offset(offset)
)
review_models = self._session.scalars(statement)
@@ -66,7 +73,10 @@ def list_with_user_id(
offset: int,
) -> list[ReviewEntity]:
statement = (
- select(ReviewModel).where(ReviewModel.user_id == user_id).limit(limit).offset(offset)
+ select(ReviewModel)
+ .where(ReviewModel.user_id == user_id)
+ .limit(limit)
+ .offset(offset)
)
review_models = self._session.scalars(statement)
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/user.py b/src/amdb/infrastructure/persistence/sqlalchemy/gateways/user.py
index ff3251f..2d22d1b 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/user.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/gateways/user.py
@@ -4,7 +4,9 @@
from sqlalchemy.orm.session import Session
from amdb.domain.entities.user import UserId, User as UserEntity
-from amdb.infrastructure.persistence.sqlalchemy.models.user import User as UserModel
+from amdb.infrastructure.persistence.sqlalchemy.models.user import (
+ User as UserModel,
+)
from amdb.infrastructure.persistence.sqlalchemy.mappers.user import UserMapper
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/user_password_hash.py b/src/amdb/infrastructure/persistence/sqlalchemy/gateways/user_password_hash.py
index 0345880..31b3980 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/user_password_hash.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/gateways/user_password_hash.py
@@ -22,9 +22,13 @@ def __init__(
self._mapper = mapper
def get(self, user_id: UserId) -> Optional[UserPasswordHash]:
- user_password_hash_model = self._session.get(UserPasswordHashModel, user_id)
+ user_password_hash_model = self._session.get(
+ UserPasswordHashModel, user_id
+ )
if user_password_hash_model:
- return self._mapper.to_password_manager_model(user_password_hash_model)
+ return self._mapper.to_password_manager_model(
+ user_password_hash_model
+ )
return None
def save(self, user_password_hash: UserPasswordHash) -> None:
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/movie.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/movie.py
index 478d27a..520ab2f 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/movie.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/movie.py
@@ -1,5 +1,7 @@
from amdb.domain.entities.movie import MovieId, Movie as MovieEntity
-from amdb.infrastructure.persistence.sqlalchemy.models.movie import Movie as MovieModel
+from amdb.infrastructure.persistence.sqlalchemy.models.movie import (
+ Movie as MovieModel,
+)
class MovieMapper:
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/rating.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/rating.py
index b93e5ea..ed34246 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/rating.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/rating.py
@@ -1,7 +1,9 @@
from amdb.domain.entities.user import UserId
from amdb.domain.entities.movie import MovieId
from amdb.domain.entities.rating import RatingId, Rating as RatingEntity
-from amdb.infrastructure.persistence.sqlalchemy.models.rating import Rating as RatingModel
+from amdb.infrastructure.persistence.sqlalchemy.models.rating import (
+ Rating as RatingModel,
+)
class RatingMapper:
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/review.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/review.py
index 968ef6c..8a28c94 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/review.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/review.py
@@ -1,7 +1,13 @@
from amdb.domain.entities.user import UserId
from amdb.domain.entities.movie import MovieId
-from amdb.domain.entities.review import ReviewId, ReviewType, Review as ReviewEntity
-from amdb.infrastructure.persistence.sqlalchemy.models.review import Review as ReviewModel
+from amdb.domain.entities.review import (
+ ReviewId,
+ ReviewType,
+ Review as ReviewEntity,
+)
+from amdb.infrastructure.persistence.sqlalchemy.models.review import (
+ Review as ReviewModel,
+)
class ReviewMapper:
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/user.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/user.py
index 54dec2a..86f28fc 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/user.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/user.py
@@ -1,5 +1,7 @@
from amdb.domain.entities.user import UserId, User as UserEntity
-from amdb.infrastructure.persistence.sqlalchemy.models.user import User as UserModel
+from amdb.infrastructure.persistence.sqlalchemy.models.user import (
+ User as UserModel,
+)
class UserMapper:
diff --git a/src/amdb/main/cli/di.py b/src/amdb/main/cli/di.py
index 87e71d1..8f1f9d8 100644
--- a/src/amdb/main/cli/di.py
+++ b/src/amdb/main/cli/di.py
@@ -6,14 +6,18 @@
from redis.client import Redis
from amdb.domain.entities.user import UserId
-from amdb.infrastructure.persistence.redis.gateways.permissions import RedisPermissionsGateway
+from amdb.infrastructure.persistence.redis.gateways.permissions import (
+ RedisPermissionsGateway,
+)
from amdb.infrastructure.auth.raw.identity_provider import RawIdentityProvider
from amdb.infrastructure.security.hasher import Hasher
from amdb.main.config import GenericConfig
from amdb.main.ioc import IoC
-IDENTITY_PROVIDER_USER_ID = UserId(UUID("00000000-0000-0000-0000-000000000000"))
+IDENTITY_PROVIDER_USER_ID = UserId(
+ UUID("00000000-0000-0000-0000-000000000000")
+)
IDENTITY_PROVIDER_PERMISSIONS = 12
@@ -22,7 +26,9 @@ class DependenciesDict(TypedDict):
identity_provider: RawIdentityProvider
-def create_dependencies_dict(generic_config: GenericConfig) -> DependenciesDict:
+def create_dependencies_dict(
+ generic_config: GenericConfig,
+) -> DependenciesDict:
redis = Redis(
host=generic_config.redis.host,
port=generic_config.redis.port,
diff --git a/src/amdb/main/ioc.py b/src/amdb/main/ioc.py
index 51cb261..32a5969 100644
--- a/src/amdb/main/ioc.py
+++ b/src/amdb/main/ioc.py
@@ -9,7 +9,9 @@
from amdb.domain.services.rate_movie import RateMovie
from amdb.domain.services.unrate_movie import UnrateMovie
from amdb.domain.services.review_movie import ReviewMovie
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.command_handlers.register_user import RegisterUserHandler
from amdb.application.command_handlers.create_movie import CreateMovieHandler
from amdb.application.command_handlers.delete_movie import DeleteMovieHandler
@@ -19,18 +21,26 @@
from amdb.application.query_handlers.login import LoginHandler
from amdb.application.query_handlers.get_movies import GetMoviesHandler
from amdb.application.query_handlers.get_movie import GetMovieHandler
-from amdb.application.query_handlers.get_movie_ratings import GetMovieRatingsHandler
+from amdb.application.query_handlers.get_movie_ratings import (
+ GetMovieRatingsHandler,
+)
from amdb.application.query_handlers.get_my_ratings import GetMyRatingsHandler
from amdb.application.query_handlers.get_rating import GetRatingHandler
-from amdb.application.query_handlers.get_movie_reviews import GetMovieReviewsHandler
+from amdb.application.query_handlers.get_movie_reviews import (
+ GetMovieReviewsHandler,
+)
from amdb.application.query_handlers.get_my_reviews import GetMyReviewsHandler
from amdb.application.query_handlers.get_review import GetReviewHandler
from amdb.infrastructure.persistence.sqlalchemy.gateway_factory import (
build_sqlalchemy_gateway_factory,
)
-from amdb.infrastructure.persistence.redis.gateways.permissions import RedisPermissionsGateway
+from amdb.infrastructure.persistence.redis.gateways.permissions import (
+ RedisPermissionsGateway,
+)
from amdb.infrastructure.security.hasher import Hasher
-from amdb.infrastructure.password_manager.password_manager import HashingPasswordManager
+from amdb.infrastructure.password_manager.password_manager import (
+ HashingPasswordManager,
+)
from amdb.presentation.handler_factory import HandlerFactory
@@ -47,7 +57,9 @@ def __init__(
@contextmanager
def register_user(self) -> Iterator[RegisterUserHandler]:
- with build_sqlalchemy_gateway_factory(self._sessionmaker) as gateway_factory:
+ with build_sqlalchemy_gateway_factory(
+ self._sessionmaker
+ ) as gateway_factory:
hashing_password_manager = HashingPasswordManager(
hasher=self._hasher,
user_password_hash_gateway=gateway_factory.user_password_hash(),
@@ -62,7 +74,9 @@ def register_user(self) -> Iterator[RegisterUserHandler]:
@contextmanager
def login(self) -> Iterator[LoginHandler]:
- with build_sqlalchemy_gateway_factory(self._sessionmaker) as gateway_factory:
+ with build_sqlalchemy_gateway_factory(
+ self._sessionmaker
+ ) as gateway_factory:
hashing_password_manager = HashingPasswordManager(
hasher=self._hasher,
user_password_hash_gateway=gateway_factory.user_password_hash(),
@@ -79,7 +93,9 @@ def get_movies(
self,
identity_provider: IdentityProvider,
) -> Iterator[GetMoviesHandler]:
- with build_sqlalchemy_gateway_factory(self._sessionmaker) as gateway_factory:
+ with build_sqlalchemy_gateway_factory(
+ self._sessionmaker
+ ) as gateway_factory:
yield GetMoviesHandler(
access_concern=AccessConcern(),
permissions_gateway=self._permissions_gateway,
@@ -92,7 +108,9 @@ def get_movie(
self,
identity_provider: IdentityProvider,
) -> Iterator[GetMovieHandler]:
- with build_sqlalchemy_gateway_factory(self._sessionmaker) as gateway_factory:
+ with build_sqlalchemy_gateway_factory(
+ self._sessionmaker
+ ) as gateway_factory:
yield GetMovieHandler(
access_concern=AccessConcern(),
permissions_gateway=self._permissions_gateway,
@@ -105,7 +123,9 @@ def create_movie(
self,
identity_provider: IdentityProvider,
) -> Iterator[CreateMovieHandler]:
- with build_sqlalchemy_gateway_factory(self._sessionmaker) as gateway_factory:
+ with build_sqlalchemy_gateway_factory(
+ self._sessionmaker
+ ) as gateway_factory:
yield CreateMovieHandler(
access_concern=AccessConcern(),
create_movie=CreateMovie(),
@@ -120,7 +140,9 @@ def delete_movie(
self,
identity_provider: IdentityProvider,
) -> Iterator[DeleteMovieHandler]:
- with build_sqlalchemy_gateway_factory(self._sessionmaker) as gateway_factory:
+ with build_sqlalchemy_gateway_factory(
+ self._sessionmaker
+ ) as gateway_factory:
yield DeleteMovieHandler(
access_concern=AccessConcern(),
permissions_gateway=self._permissions_gateway,
@@ -136,7 +158,9 @@ def get_movie_ratings(
self,
identity_provider: IdentityProvider,
) -> Iterator[GetMovieRatingsHandler]:
- with build_sqlalchemy_gateway_factory(self._sessionmaker) as gateway_factory:
+ with build_sqlalchemy_gateway_factory(
+ self._sessionmaker
+ ) as gateway_factory:
yield GetMovieRatingsHandler(
access_concern=AccessConcern(),
permissions_gateway=self._permissions_gateway,
@@ -150,7 +174,9 @@ def get_my_ratings(
self,
identity_provider: IdentityProvider,
) -> Iterator[GetMyRatingsHandler]:
- with build_sqlalchemy_gateway_factory(self._sessionmaker) as gateway_factory:
+ with build_sqlalchemy_gateway_factory(
+ self._sessionmaker
+ ) as gateway_factory:
yield GetMyRatingsHandler(
access_concern=AccessConcern(),
permissions_gateway=self._permissions_gateway,
@@ -163,7 +189,9 @@ def get_rating(
self,
identity_provider: IdentityProvider,
) -> Iterator[GetRatingHandler]:
- with build_sqlalchemy_gateway_factory(self._sessionmaker) as gateway_factory:
+ with build_sqlalchemy_gateway_factory(
+ self._sessionmaker
+ ) as gateway_factory:
yield GetRatingHandler(
access_concern=AccessConcern(),
permissions_gateway=self._permissions_gateway,
@@ -176,7 +204,9 @@ def rate_movie(
self,
identity_provider: IdentityProvider,
) -> Iterator[RateMovieHandler]:
- with build_sqlalchemy_gateway_factory(self._sessionmaker) as gateway_factory:
+ with build_sqlalchemy_gateway_factory(
+ self._sessionmaker
+ ) as gateway_factory:
yield RateMovieHandler(
access_concern=AccessConcern(),
rate_movie=RateMovie(),
@@ -193,7 +223,9 @@ def unrate_movie(
self,
identity_provider: IdentityProvider,
) -> Iterator[UnrateMovieHandler]:
- with build_sqlalchemy_gateway_factory(self._sessionmaker) as gateway_factory:
+ with build_sqlalchemy_gateway_factory(
+ self._sessionmaker
+ ) as gateway_factory:
yield UnrateMovieHandler(
access_concern=AccessConcern(),
unrate_movie=UnrateMovie(),
@@ -209,7 +241,9 @@ def get_movie_reviews(
self,
identity_provider: IdentityProvider,
) -> Iterator[GetMovieReviewsHandler]:
- with build_sqlalchemy_gateway_factory(self._sessionmaker) as gateway_factory:
+ with build_sqlalchemy_gateway_factory(
+ self._sessionmaker
+ ) as gateway_factory:
yield GetMovieReviewsHandler(
access_concern=AccessConcern(),
permissions_gateway=self._permissions_gateway,
@@ -223,7 +257,9 @@ def get_my_reviews(
self,
identity_provider: IdentityProvider,
) -> Iterator[GetMyReviewsHandler]:
- with build_sqlalchemy_gateway_factory(self._sessionmaker) as gateway_factory:
+ with build_sqlalchemy_gateway_factory(
+ self._sessionmaker
+ ) as gateway_factory:
yield GetMyReviewsHandler(
access_concern=AccessConcern(),
permissions_gateway=self._permissions_gateway,
@@ -236,7 +272,9 @@ def get_review(
self,
identity_provider: IdentityProvider,
) -> Iterator[GetReviewHandler]:
- with build_sqlalchemy_gateway_factory(self._sessionmaker) as gateway_factory:
+ with build_sqlalchemy_gateway_factory(
+ self._sessionmaker
+ ) as gateway_factory:
yield GetReviewHandler(
access_concern=AccessConcern(),
permissions_gateway=self._permissions_gateway,
@@ -249,7 +287,9 @@ def review_movie(
self,
identity_provider: IdentityProvider,
) -> Iterator[ReviewMovieHandler]:
- with build_sqlalchemy_gateway_factory(self._sessionmaker) as gateway_factory:
+ with build_sqlalchemy_gateway_factory(
+ self._sessionmaker
+ ) as gateway_factory:
yield ReviewMovieHandler(
access_concern=AccessConcern(),
review_movie=ReviewMovie(),
diff --git a/src/amdb/main/web_api/app.py b/src/amdb/main/web_api/app.py
index 1f2a9da..d19e906 100644
--- a/src/amdb/main/web_api/app.py
+++ b/src/amdb/main/web_api/app.py
@@ -1,7 +1,9 @@
from fastapi import FastAPI
from amdb.infrastructure.auth.session.config import SessionConfig
-from amdb.presentation.web_api.exception_handlers import setup_exception_handlers
+from amdb.presentation.web_api.exception_handlers import (
+ setup_exception_handlers,
+)
from amdb.presentation.web_api.routers.setup import setup_routers
from amdb.main.config import GenericConfig
from .config import FastAPIConfig
diff --git a/src/amdb/main/web_api/config.py b/src/amdb/main/web_api/config.py
index 858dabb..a490322 100644
--- a/src/amdb/main/web_api/config.py
+++ b/src/amdb/main/web_api/config.py
@@ -22,7 +22,9 @@ def build_web_api_config() -> "WebAPIConfig":
port=int(_get_env(UVICORN_PORT_ENV)),
)
session_config = SessionConfig(
- session_lifetime=timedelta(minutes=int(_get_env(SESSION_LIFETIME_ENV))),
+ session_lifetime=timedelta(
+ minutes=int(_get_env(SESSION_LIFETIME_ENV))
+ ),
)
return WebAPIConfig(
fastapi=fastapi_config,
diff --git a/src/amdb/main/web_api/di.py b/src/amdb/main/web_api/di.py
index 2d70b4a..948002c 100644
--- a/src/amdb/main/web_api/di.py
+++ b/src/amdb/main/web_api/di.py
@@ -5,8 +5,12 @@
from amdb.infrastructure.security.hasher import Hasher
from amdb.infrastructure.auth.session.config import SessionConfig
-from amdb.infrastructure.persistence.redis.gateways.session import RedisSessionGateway
-from amdb.infrastructure.persistence.redis.gateways.permissions import RedisPermissionsGateway
+from amdb.infrastructure.persistence.redis.gateways.session import (
+ RedisSessionGateway,
+)
+from amdb.infrastructure.persistence.redis.gateways.permissions import (
+ RedisPermissionsGateway,
+)
from amdb.infrastructure.auth.session.session_processor import SessionProcessor
from amdb.presentation.handler_factory import HandlerFactory
from amdb.presentation.web_api.dependencies.depends_stub import Stub
@@ -30,10 +34,14 @@ def setup_dependecies(
redis=redis,
session_lifetime=session_config.session_lifetime,
)
- app.dependency_overrides[Stub(RedisSessionGateway)] = lambda: redis_session_gateway # type: ignore
+ app.dependency_overrides[Stub(RedisSessionGateway)] = (
+ lambda: redis_session_gateway
+ ) # type: ignore
redis_permissions_gateway = RedisPermissionsGateway(redis)
- app.dependency_overrides[Stub(RedisPermissionsGateway)] = lambda: redis_permissions_gateway # type: ignore
+ app.dependency_overrides[Stub(RedisPermissionsGateway)] = (
+ lambda: redis_permissions_gateway
+ ) # type: ignore
engine = create_engine(generic_config.postgres.dsn)
ioc = IoC(
@@ -44,4 +52,6 @@ def setup_dependecies(
app.dependency_overrides[HandlerFactory] = lambda: ioc # type: ignore
session_processor = SessionProcessor()
- app.dependency_overrides[Stub(SessionProcessor)] = lambda: session_processor # type: ignore
+ app.dependency_overrides[Stub(SessionProcessor)] = (
+ lambda: session_processor
+ ) # type: ignore
diff --git a/src/amdb/presentation/cli/movie.py b/src/amdb/presentation/cli/movie.py
index 095de7a..7cbad71 100644
--- a/src/amdb/presentation/cli/movie.py
+++ b/src/amdb/presentation/cli/movie.py
@@ -8,7 +8,9 @@
import rich.table
from amdb.domain.entities.movie import MovieId
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.commands.create_movie import CreateMovieCommand
from amdb.application.commands.delete_movie import DeleteMovieCommand
from amdb.application.queries.get_movies import GetMoviesQuery
diff --git a/src/amdb/presentation/handler_factory.py b/src/amdb/presentation/handler_factory.py
index d9b2f66..930141b 100644
--- a/src/amdb/presentation/handler_factory.py
+++ b/src/amdb/presentation/handler_factory.py
@@ -1,7 +1,9 @@
from abc import ABC, abstractmethod
from typing import ContextManager
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.command_handlers.register_user import RegisterUserHandler
from amdb.application.command_handlers.create_movie import CreateMovieHandler
from amdb.application.command_handlers.delete_movie import DeleteMovieHandler
@@ -11,10 +13,14 @@
from amdb.application.query_handlers.login import LoginHandler
from amdb.application.query_handlers.get_movies import GetMoviesHandler
from amdb.application.query_handlers.get_movie import GetMovieHandler
-from amdb.application.query_handlers.get_movie_ratings import GetMovieRatingsHandler
+from amdb.application.query_handlers.get_movie_ratings import (
+ GetMovieRatingsHandler,
+)
from amdb.application.query_handlers.get_my_ratings import GetMyRatingsHandler
from amdb.application.query_handlers.get_rating import GetRatingHandler
-from amdb.application.query_handlers.get_movie_reviews import GetMovieReviewsHandler
+from amdb.application.query_handlers.get_movie_reviews import (
+ GetMovieReviewsHandler,
+)
from amdb.application.query_handlers.get_my_reviews import GetMyReviewsHandler
from amdb.application.query_handlers.get_review import GetReviewHandler
diff --git a/src/amdb/presentation/web_api/dependencies/identity_provider.py b/src/amdb/presentation/web_api/dependencies/identity_provider.py
index d58e912..77f8169 100644
--- a/src/amdb/presentation/web_api/dependencies/identity_provider.py
+++ b/src/amdb/presentation/web_api/dependencies/identity_provider.py
@@ -2,21 +2,31 @@
from fastapi import Cookie, Depends
-from amdb.infrastructure.persistence.redis.gateways.session import RedisSessionGateway
-from amdb.infrastructure.persistence.redis.gateways.permissions import RedisPermissionsGateway
-from amdb.infrastructure.auth.session.identity_provider import SessionIdentityProvider
+from amdb.infrastructure.persistence.redis.gateways.session import (
+ RedisSessionGateway,
+)
+from amdb.infrastructure.persistence.redis.gateways.permissions import (
+ RedisPermissionsGateway,
+)
+from amdb.infrastructure.auth.session.identity_provider import (
+ SessionIdentityProvider,
+)
from amdb.infrastructure.auth.session.model import SessionId
from amdb.presentation.web_api.constants import SESSION_ID_COOKIE
from .depends_stub import Stub
def get_identity_provider(
- session_gateway: Annotated[RedisSessionGateway, Depends(Stub(RedisSessionGateway))],
+ session_gateway: Annotated[
+ RedisSessionGateway, Depends(Stub(RedisSessionGateway))
+ ],
permissions_gateway: Annotated[
RedisPermissionsGateway,
Depends(Stub(RedisPermissionsGateway)),
],
- session_id: Annotated[Optional[str], Cookie(alias=SESSION_ID_COOKIE)] = None,
+ session_id: Annotated[
+ Optional[str], Cookie(alias=SESSION_ID_COOKIE)
+ ] = None,
) -> SessionIdentityProvider:
return SessionIdentityProvider(
session_id=SessionId(session_id) if session_id else None,
diff --git a/src/amdb/presentation/web_api/exception_handlers.py b/src/amdb/presentation/web_api/exception_handlers.py
index e537331..4408072 100644
--- a/src/amdb/presentation/web_api/exception_handlers.py
+++ b/src/amdb/presentation/web_api/exception_handlers.py
@@ -9,7 +9,9 @@
def setup_exception_handlers(app: FastAPI) -> None:
app.add_exception_handler(DomainError, _domain_error_handler)
app.add_exception_handler(ApplicationError, _application_error_handler)
- app.add_exception_handler(InfrastructureError, _infrastructure_error_handler)
+ app.add_exception_handler(
+ InfrastructureError, _infrastructure_error_handler
+ )
def _domain_error_handler(_, error: ApplicationError) -> JSONResponse:
@@ -20,5 +22,7 @@ def _application_error_handler(_, error: ApplicationError) -> JSONResponse:
return JSONResponse(content={"message": error.message}, status_code=400)
-def _infrastructure_error_handler(_, error: InfrastructureError) -> JSONResponse:
+def _infrastructure_error_handler(
+ _, error: InfrastructureError
+) -> JSONResponse:
return JSONResponse(content=None, status_code=500)
diff --git a/src/amdb/presentation/web_api/routers/auth/login.py b/src/amdb/presentation/web_api/routers/auth/login.py
index 5d48b80..8423b92 100644
--- a/src/amdb/presentation/web_api/routers/auth/login.py
+++ b/src/amdb/presentation/web_api/routers/auth/login.py
@@ -5,7 +5,9 @@
from amdb.domain.entities.user import UserId
from amdb.application.queries.login import LoginQuery
from amdb.infrastructure.auth.session.session_processor import SessionProcessor
-from amdb.infrastructure.persistence.redis.gateways.session import RedisSessionGateway
+from amdb.infrastructure.persistence.redis.gateways.session import (
+ RedisSessionGateway,
+)
from amdb.presentation.handler_factory import HandlerFactory
from amdb.presentation.web_api.dependencies.depends_stub import Stub
from amdb.presentation.web_api.constants import SESSION_ID_COOKIE
@@ -13,8 +15,12 @@
async def login(
ioc: Annotated[HandlerFactory, Depends()],
- session_processor: Annotated[SessionProcessor, Depends(Stub(SessionProcessor))],
- session_gateway: Annotated[RedisSessionGateway, Depends(Stub(RedisSessionGateway))],
+ session_processor: Annotated[
+ SessionProcessor, Depends(Stub(SessionProcessor))
+ ],
+ session_gateway: Annotated[
+ RedisSessionGateway, Depends(Stub(RedisSessionGateway))
+ ],
login_query: LoginQuery,
response: Response,
) -> UserId:
diff --git a/src/amdb/presentation/web_api/routers/auth/register.py b/src/amdb/presentation/web_api/routers/auth/register.py
index c98bec9..e25e7c9 100644
--- a/src/amdb/presentation/web_api/routers/auth/register.py
+++ b/src/amdb/presentation/web_api/routers/auth/register.py
@@ -5,7 +5,9 @@
from amdb.domain.entities.user import UserId
from amdb.application.commands.register_user import RegisterUserCommand
from amdb.infrastructure.auth.session.session_processor import SessionProcessor
-from amdb.infrastructure.persistence.redis.gateways.session import RedisSessionGateway
+from amdb.infrastructure.persistence.redis.gateways.session import (
+ RedisSessionGateway,
+)
from amdb.presentation.handler_factory import HandlerFactory
from amdb.presentation.web_api.dependencies.depends_stub import Stub
from amdb.presentation.web_api.constants import SESSION_ID_COOKIE
@@ -13,8 +15,12 @@
async def register(
ioc: Annotated[HandlerFactory, Depends()],
- session_processor: Annotated[SessionProcessor, Depends(Stub(SessionProcessor))],
- session_gateway: Annotated[RedisSessionGateway, Depends(Stub(RedisSessionGateway))],
+ session_processor: Annotated[
+ SessionProcessor, Depends(Stub(SessionProcessor))
+ ],
+ session_gateway: Annotated[
+ RedisSessionGateway, Depends(Stub(RedisSessionGateway))
+ ],
register_user_command: RegisterUserCommand,
response: Response,
) -> UserId:
diff --git a/src/amdb/presentation/web_api/routers/movies/get_movie.py b/src/amdb/presentation/web_api/routers/movies/get_movie.py
index afbec2a..8768121 100644
--- a/src/amdb/presentation/web_api/routers/movies/get_movie.py
+++ b/src/amdb/presentation/web_api/routers/movies/get_movie.py
@@ -3,15 +3,21 @@
from fastapi import Depends
from amdb.domain.entities.movie import MovieId
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.queries.get_movie import GetMovieResult, GetMovieQuery
from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import get_identity_provider
+from amdb.presentation.web_api.dependencies.identity_provider import (
+ get_identity_provider,
+)
def get_movie(
ioc: Annotated[HandlerFactory, Depends()],
- identity_provider: Annotated[IdentityProvider, Depends(get_identity_provider)],
+ identity_provider: Annotated[
+ IdentityProvider, Depends(get_identity_provider)
+ ],
movie_id: MovieId,
) -> GetMovieResult:
"""
diff --git a/src/amdb/presentation/web_api/routers/movies/get_movies.py b/src/amdb/presentation/web_api/routers/movies/get_movies.py
index 4cb9624..3794fdc 100644
--- a/src/amdb/presentation/web_api/routers/movies/get_movies.py
+++ b/src/amdb/presentation/web_api/routers/movies/get_movies.py
@@ -2,15 +2,21 @@
from fastapi import Depends
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import get_identity_provider
+from amdb.presentation.web_api.dependencies.identity_provider import (
+ get_identity_provider,
+)
from amdb.application.queries.get_movies import GetMoviesQuery, GetMoviesResult
async def get_movies(
ioc: Annotated[HandlerFactory, Depends()],
- identity_provider: Annotated[IdentityProvider, Depends(get_identity_provider)],
+ identity_provider: Annotated[
+ IdentityProvider, Depends(get_identity_provider)
+ ],
limit: int = 100,
offset: int = 0,
) -> GetMoviesResult:
diff --git a/src/amdb/presentation/web_api/routers/ratings/get_movie_ratings.py b/src/amdb/presentation/web_api/routers/ratings/get_movie_ratings.py
index 64b777b..660eec9 100644
--- a/src/amdb/presentation/web_api/routers/ratings/get_movie_ratings.py
+++ b/src/amdb/presentation/web_api/routers/ratings/get_movie_ratings.py
@@ -3,15 +3,24 @@
from fastapi import Depends
from amdb.domain.entities.movie import MovieId
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
-from amdb.application.queries.get_movie_ratings import GetMovieRatingsQuery, GetMovieRatingsResult
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
+from amdb.application.queries.get_movie_ratings import (
+ GetMovieRatingsQuery,
+ GetMovieRatingsResult,
+)
from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import get_identity_provider
+from amdb.presentation.web_api.dependencies.identity_provider import (
+ get_identity_provider,
+)
async def get_movie_ratings(
ioc: Annotated[HandlerFactory, Depends()],
- identity_provider: Annotated[IdentityProvider, Depends(get_identity_provider)],
+ identity_provider: Annotated[
+ IdentityProvider, Depends(get_identity_provider)
+ ],
movie_id: MovieId,
limit: int = 100,
offset: int = 0,
@@ -27,6 +36,8 @@ async def get_movie_ratings(
limit=limit,
offset=offset,
)
- get_movie_ratings_result = get_movie_ratings_handler.execute(get_movie_ratings_query)
+ get_movie_ratings_result = get_movie_ratings_handler.execute(
+ get_movie_ratings_query
+ )
return get_movie_ratings_result
diff --git a/src/amdb/presentation/web_api/routers/ratings/get_my_ratings.py b/src/amdb/presentation/web_api/routers/ratings/get_my_ratings.py
index 5ad4fa5..f0aa378 100644
--- a/src/amdb/presentation/web_api/routers/ratings/get_my_ratings.py
+++ b/src/amdb/presentation/web_api/routers/ratings/get_my_ratings.py
@@ -2,15 +2,24 @@
from fastapi import Depends
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
-from amdb.application.queries.get_my_ratings import GetMyRatingsQuery, GetMyRatingsResult
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
+from amdb.application.queries.get_my_ratings import (
+ GetMyRatingsQuery,
+ GetMyRatingsResult,
+)
from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import get_identity_provider
+from amdb.presentation.web_api.dependencies.identity_provider import (
+ get_identity_provider,
+)
async def get_my_ratings(
ioc: Annotated[HandlerFactory, Depends()],
- identity_provider: Annotated[IdentityProvider, Depends(get_identity_provider)],
+ identity_provider: Annotated[
+ IdentityProvider, Depends(get_identity_provider)
+ ],
limit: int = 100,
offset: int = 0,
) -> GetMyRatingsResult:
@@ -23,6 +32,8 @@ async def get_my_ratings(
limit=limit,
offset=offset,
)
- get_my_ratings_result = get_my_ratings_handler.execute(get_my_ratings_query)
+ get_my_ratings_result = get_my_ratings_handler.execute(
+ get_my_ratings_query
+ )
return get_my_ratings_result
diff --git a/src/amdb/presentation/web_api/routers/ratings/get_rating.py b/src/amdb/presentation/web_api/routers/ratings/get_rating.py
index 7e3581f..2cb994e 100644
--- a/src/amdb/presentation/web_api/routers/ratings/get_rating.py
+++ b/src/amdb/presentation/web_api/routers/ratings/get_rating.py
@@ -3,15 +3,21 @@
from fastapi import Depends
from amdb.domain.entities.rating import RatingId
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.queries.get_rating import GetRatingQuery, GetRatingResult
from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import get_identity_provider
+from amdb.presentation.web_api.dependencies.identity_provider import (
+ get_identity_provider,
+)
async def get_rating(
ioc: Annotated[HandlerFactory, Depends()],
- identity_provider: Annotated[IdentityProvider, Depends(get_identity_provider)],
+ identity_provider: Annotated[
+ IdentityProvider, Depends(get_identity_provider)
+ ],
rating_id: RatingId,
) -> GetRatingResult:
"""
diff --git a/src/amdb/presentation/web_api/routers/ratings/rate_movie.py b/src/amdb/presentation/web_api/routers/ratings/rate_movie.py
index 4fc9a80..6ba1727 100644
--- a/src/amdb/presentation/web_api/routers/ratings/rate_movie.py
+++ b/src/amdb/presentation/web_api/routers/ratings/rate_movie.py
@@ -3,15 +3,21 @@
from fastapi import Depends
from amdb.domain.entities.rating import RatingId
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.commands.rate_movie import RateMovieCommand
from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import get_identity_provider
+from amdb.presentation.web_api.dependencies.identity_provider import (
+ get_identity_provider,
+)
async def rate_movie(
ioc: Annotated[HandlerFactory, Depends(HandlerFactory)],
- identity_provider: Annotated[IdentityProvider, Depends(get_identity_provider)],
+ identity_provider: Annotated[
+ IdentityProvider, Depends(get_identity_provider)
+ ],
rate_movie_command: RateMovieCommand,
) -> RatingId:
"""
diff --git a/src/amdb/presentation/web_api/routers/ratings/unrate_movie.py b/src/amdb/presentation/web_api/routers/ratings/unrate_movie.py
index 6b03068..08c30c6 100644
--- a/src/amdb/presentation/web_api/routers/ratings/unrate_movie.py
+++ b/src/amdb/presentation/web_api/routers/ratings/unrate_movie.py
@@ -3,15 +3,21 @@
from fastapi import Depends
from amdb.domain.entities.rating import RatingId
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.commands.unrate_movie import UnrateMovieCommand
from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import get_identity_provider
+from amdb.presentation.web_api.dependencies.identity_provider import (
+ get_identity_provider,
+)
async def unrate_movie(
ioc: Annotated[HandlerFactory, Depends(HandlerFactory)],
- identity_provider: Annotated[IdentityProvider, Depends(get_identity_provider)],
+ identity_provider: Annotated[
+ IdentityProvider, Depends(get_identity_provider)
+ ],
rating_id: RatingId,
) -> None:
"""
diff --git a/src/amdb/presentation/web_api/routers/reviews/get_movie_reviews.py b/src/amdb/presentation/web_api/routers/reviews/get_movie_reviews.py
index 1f8d3c7..e4c6bdc 100644
--- a/src/amdb/presentation/web_api/routers/reviews/get_movie_reviews.py
+++ b/src/amdb/presentation/web_api/routers/reviews/get_movie_reviews.py
@@ -3,15 +3,24 @@
from fastapi import Depends
from amdb.domain.entities.movie import MovieId
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
-from amdb.application.queries.get_movie_reviews import GetMovieReviewsQuery, GetMovieReviewsResult
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
+from amdb.application.queries.get_movie_reviews import (
+ GetMovieReviewsQuery,
+ GetMovieReviewsResult,
+)
from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import get_identity_provider
+from amdb.presentation.web_api.dependencies.identity_provider import (
+ get_identity_provider,
+)
async def get_movie_reviews(
ioc: Annotated[HandlerFactory, Depends()],
- identity_provider: Annotated[IdentityProvider, Depends(get_identity_provider)],
+ identity_provider: Annotated[
+ IdentityProvider, Depends(get_identity_provider)
+ ],
movie_id: MovieId,
limit: int = 100,
offset: int = 0,
@@ -27,6 +36,8 @@ async def get_movie_reviews(
limit=limit,
offset=offset,
)
- get_movie_reviews_result = get_movie_reviews_handler.execute(get_movie_reviews_query)
+ get_movie_reviews_result = get_movie_reviews_handler.execute(
+ get_movie_reviews_query
+ )
return get_movie_reviews_result
diff --git a/src/amdb/presentation/web_api/routers/reviews/get_my_reviews.py b/src/amdb/presentation/web_api/routers/reviews/get_my_reviews.py
index e0e4fc5..60942e5 100644
--- a/src/amdb/presentation/web_api/routers/reviews/get_my_reviews.py
+++ b/src/amdb/presentation/web_api/routers/reviews/get_my_reviews.py
@@ -2,15 +2,24 @@
from fastapi import Depends
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
-from amdb.application.queries.get_my_reviews import GetMyReviewsQuery, GetMyReviewsResult
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
+from amdb.application.queries.get_my_reviews import (
+ GetMyReviewsQuery,
+ GetMyReviewsResult,
+)
from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import get_identity_provider
+from amdb.presentation.web_api.dependencies.identity_provider import (
+ get_identity_provider,
+)
async def get_my_reviews(
ioc: Annotated[HandlerFactory, Depends()],
- identity_provider: Annotated[IdentityProvider, Depends(get_identity_provider)],
+ identity_provider: Annotated[
+ IdentityProvider, Depends(get_identity_provider)
+ ],
limit: int = 100,
offset: int = 0,
) -> GetMyReviewsResult:
@@ -19,6 +28,8 @@ async def get_my_reviews(
limit=limit,
offset=offset,
)
- get_my_reviews_result = get_my_reviews_handler.execute(get_my_reviews_query)
+ get_my_reviews_result = get_my_reviews_handler.execute(
+ get_my_reviews_query
+ )
return get_my_reviews_result
diff --git a/src/amdb/presentation/web_api/routers/reviews/get_review.py b/src/amdb/presentation/web_api/routers/reviews/get_review.py
index 59d364e..5c1d7a2 100644
--- a/src/amdb/presentation/web_api/routers/reviews/get_review.py
+++ b/src/amdb/presentation/web_api/routers/reviews/get_review.py
@@ -3,15 +3,21 @@
from fastapi import Depends
from amdb.domain.entities.review import ReviewId
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.queries.get_review import GetReviewQuery, GetReviewResult
from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import get_identity_provider
+from amdb.presentation.web_api.dependencies.identity_provider import (
+ get_identity_provider,
+)
async def get_review(
ioc: Annotated[HandlerFactory, Depends()],
- identity_provider: Annotated[IdentityProvider, Depends(get_identity_provider)],
+ identity_provider: Annotated[
+ IdentityProvider, Depends(get_identity_provider)
+ ],
review_id: ReviewId,
) -> GetReviewResult:
"""
diff --git a/src/amdb/presentation/web_api/routers/reviews/review_movie.py b/src/amdb/presentation/web_api/routers/reviews/review_movie.py
index e726f8c..2e47a0a 100644
--- a/src/amdb/presentation/web_api/routers/reviews/review_movie.py
+++ b/src/amdb/presentation/web_api/routers/reviews/review_movie.py
@@ -5,10 +5,14 @@
from amdb.domain.entities.movie import MovieId
from amdb.domain.entities.review import ReviewId, ReviewType
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.commands.review_movie import ReviewMovieCommand
from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import get_identity_provider
+from amdb.presentation.web_api.dependencies.identity_provider import (
+ get_identity_provider,
+)
class ReviewMovie(BaseModel):
@@ -19,7 +23,9 @@ class ReviewMovie(BaseModel):
async def review_movie(
ioc: Annotated[HandlerFactory, Depends()],
- identity_provider: Annotated[IdentityProvider, Depends(get_identity_provider)],
+ identity_provider: Annotated[
+ IdentityProvider, Depends(get_identity_provider)
+ ],
movie_id: MovieId,
data: ReviewMovie,
) -> ReviewId:
diff --git a/tests/unit/application/command_handlers/test_create_movie.py b/tests/unit/application/command_handlers/test_create_movie.py
index 19de162..5d017b9 100644
--- a/tests/unit/application/command_handlers/test_create_movie.py
+++ b/tests/unit/application/command_handlers/test_create_movie.py
@@ -5,13 +5,19 @@
from amdb.domain.services.access_concern import AccessConcern
from amdb.domain.services.create_movie import CreateMovie
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.commands.create_movie import CreateMovieCommand
from amdb.application.command_handlers.create_movie import CreateMovieHandler
-from amdb.application.common.constants.exceptions import CREATE_MOVIE_ACCESS_DENIED
+from amdb.application.common.constants.exceptions import (
+ CREATE_MOVIE_ACCESS_DENIED,
+)
from amdb.application.common.exception import ApplicationError
diff --git a/tests/unit/application/command_handlers/test_delete_movie.py b/tests/unit/application/command_handlers/test_delete_movie.py
index 5523bec..6821f40 100644
--- a/tests/unit/application/command_handlers/test_delete_movie.py
+++ b/tests/unit/application/command_handlers/test_delete_movie.py
@@ -6,12 +6,16 @@
from amdb.domain.entities.movie import MovieId, Movie
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.rating_gateway import RatingGateway
from amdb.application.common.interfaces.review_gateway import ReviewGateway
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.commands.delete_movie import DeleteMovieCommand
from amdb.application.command_handlers.delete_movie import DeleteMovieHandler
from amdb.application.common.constants.exceptions import (
diff --git a/tests/unit/application/command_handlers/test_rate_movie.py b/tests/unit/application/command_handlers/test_rate_movie.py
index 86240a9..376a737 100644
--- a/tests/unit/application/command_handlers/test_rate_movie.py
+++ b/tests/unit/application/command_handlers/test_rate_movie.py
@@ -11,12 +11,16 @@
from amdb.domain.services.rate_movie import RateMovie
from amdb.domain.constants.exceptions import INVALID_RATING_VALUE
from amdb.domain.exception import DomainError
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.user_gateway import UserGateway
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.rating_gateway import RatingGateway
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.commands.rate_movie import RateMovieCommand
from amdb.application.command_handlers.rate_movie import RateMovieHandler
from amdb.application.common.constants.exceptions import (
diff --git a/tests/unit/application/command_handlers/test_register_user.py b/tests/unit/application/command_handlers/test_register_user.py
index e67ff38..e7aa115 100644
--- a/tests/unit/application/command_handlers/test_register_user.py
+++ b/tests/unit/application/command_handlers/test_register_user.py
@@ -4,12 +4,16 @@
from amdb.domain.entities.user import UserId, User
from amdb.domain.services.create_user import CreateUser
from amdb.application.common.interfaces.user_gateway import UserGateway
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
from amdb.application.common.interfaces.password_manager import PasswordManager
from amdb.application.commands.register_user import RegisterUserCommand
from amdb.application.command_handlers.register_user import RegisterUserHandler
-from amdb.application.common.constants.exceptions import USER_NAME_ALREADY_EXISTS
+from amdb.application.common.constants.exceptions import (
+ USER_NAME_ALREADY_EXISTS,
+)
from amdb.application.common.exception import ApplicationError
diff --git a/tests/unit/application/command_handlers/test_review_movie.py b/tests/unit/application/command_handlers/test_review_movie.py
index e2b7d33..20d494d 100644
--- a/tests/unit/application/command_handlers/test_review_movie.py
+++ b/tests/unit/application/command_handlers/test_review_movie.py
@@ -9,12 +9,16 @@
from amdb.domain.entities.review import ReviewId, ReviewType, Review
from amdb.domain.services.access_concern import AccessConcern
from amdb.domain.services.review_movie import ReviewMovie
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.user_gateway import UserGateway
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.review_gateway import ReviewGateway
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.commands.review_movie import ReviewMovieCommand
from amdb.application.command_handlers.review_movie import ReviewMovieHandler
from amdb.application.common.constants.exceptions import (
diff --git a/tests/unit/application/command_handlers/test_unrate_movie.py b/tests/unit/application/command_handlers/test_unrate_movie.py
index d2683bc..45b5a9f 100644
--- a/tests/unit/application/command_handlers/test_unrate_movie.py
+++ b/tests/unit/application/command_handlers/test_unrate_movie.py
@@ -9,12 +9,16 @@
from amdb.domain.entities.rating import RatingId, Rating
from amdb.domain.services.access_concern import AccessConcern
from amdb.domain.services.unrate_movie import UnrateMovie
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.user_gateway import UserGateway
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.rating_gateway import RatingGateway
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.commands.unrate_movie import UnrateMovieCommand
from amdb.application.command_handlers.unrate_movie import UnrateMovieHandler
from amdb.application.common.constants.exceptions import (
diff --git a/tests/unit/application/conftest.py b/tests/unit/application/conftest.py
index a67644a..76a8d77 100644
--- a/tests/unit/application/conftest.py
+++ b/tests/unit/application/conftest.py
@@ -7,23 +7,41 @@
from redis.client import Redis
from amdb.infrastructure.persistence.sqlalchemy.models.base import Model
-from amdb.infrastructure.persistence.sqlalchemy.gateways.user import SQLAlchemyUserGateway
-from amdb.infrastructure.persistence.sqlalchemy.gateways.movie import SQLAlchemyMovieGateway
-from amdb.infrastructure.persistence.sqlalchemy.gateways.rating import SQLAlchemyRatingGateway
+from amdb.infrastructure.persistence.sqlalchemy.gateways.user import (
+ SQLAlchemyUserGateway,
+)
+from amdb.infrastructure.persistence.sqlalchemy.gateways.movie import (
+ SQLAlchemyMovieGateway,
+)
+from amdb.infrastructure.persistence.sqlalchemy.gateways.rating import (
+ SQLAlchemyRatingGateway,
+)
from amdb.infrastructure.persistence.sqlalchemy.gateways.user_password_hash import (
SQLAlchemyUserPasswordHashGateway,
)
-from amdb.infrastructure.persistence.sqlalchemy.gateways.review import SQLAlchemyReviewGateway
-from amdb.infrastructure.persistence.redis.gateways.permissions import RedisPermissionsGateway
+from amdb.infrastructure.persistence.sqlalchemy.gateways.review import (
+ SQLAlchemyReviewGateway,
+)
+from amdb.infrastructure.persistence.redis.gateways.permissions import (
+ RedisPermissionsGateway,
+)
from amdb.infrastructure.persistence.sqlalchemy.mappers.user import UserMapper
-from amdb.infrastructure.persistence.sqlalchemy.mappers.movie import MovieMapper
-from amdb.infrastructure.persistence.sqlalchemy.mappers.rating import RatingMapper
+from amdb.infrastructure.persistence.sqlalchemy.mappers.movie import (
+ MovieMapper,
+)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.rating import (
+ RatingMapper,
+)
from amdb.infrastructure.persistence.sqlalchemy.mappers.user_password_hash import (
UserPasswordHashMapper,
)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.review import ReviewMapper
+from amdb.infrastructure.persistence.sqlalchemy.mappers.review import (
+ ReviewMapper,
+)
from amdb.infrastructure.security.hasher import Hasher
-from amdb.infrastructure.password_manager.password_manager import HashingPasswordManager
+from amdb.infrastructure.password_manager.password_manager import (
+ HashingPasswordManager,
+)
@pytest.fixture(scope="package")
diff --git a/tests/unit/application/query_handlers/test_get_movie.py b/tests/unit/application/query_handlers/test_get_movie.py
index 1ce4466..c87297c 100644
--- a/tests/unit/application/query_handlers/test_get_movie.py
+++ b/tests/unit/application/query_handlers/test_get_movie.py
@@ -6,10 +6,14 @@
from amdb.domain.entities.movie import MovieId, Movie
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.queries.get_movie import GetMovieQuery, GetMovieResult
from amdb.application.query_handlers.get_movie import GetMovieHandler
from amdb.application.common.constants.exceptions import (
diff --git a/tests/unit/application/query_handlers/test_get_movie_ratings.py b/tests/unit/application/query_handlers/test_get_movie_ratings.py
index fe6c102..b824a16 100644
--- a/tests/unit/application/query_handlers/test_get_movie_ratings.py
+++ b/tests/unit/application/query_handlers/test_get_movie_ratings.py
@@ -11,11 +11,20 @@
from amdb.application.common.interfaces.user_gateway import UserGateway
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.rating_gateway import RatingGateway
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
-from amdb.application.queries.get_movie_ratings import GetMovieRatingsQuery, GetMovieRatingsResult
-from amdb.application.query_handlers.get_movie_ratings import GetMovieRatingsHandler
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
+from amdb.application.queries.get_movie_ratings import (
+ GetMovieRatingsQuery,
+ GetMovieRatingsResult,
+)
+from amdb.application.query_handlers.get_movie_ratings import (
+ GetMovieRatingsHandler,
+)
from amdb.application.common.constants.exceptions import (
GET_MOVIE_RATINGS_ACCESS_DENIED,
MOVIE_DOES_NOT_EXIST,
@@ -86,7 +95,9 @@ def test_get_movie_ratings(
identity_provider=identity_provider_with_correct_permissions,
)
- get_movie_ratings_result = get_movie_ratings_handler.execute(get_movie_ratings_query)
+ get_movie_ratings_result = get_movie_ratings_handler.execute(
+ get_movie_ratings_query
+ )
expected_get_movie_ratings_result = GetMovieRatingsResult(
ratings=[rating],
rating_count=1,
diff --git a/tests/unit/application/query_handlers/test_get_movie_reviews.py b/tests/unit/application/query_handlers/test_get_movie_reviews.py
index b596920..3aae0d8 100644
--- a/tests/unit/application/query_handlers/test_get_movie_reviews.py
+++ b/tests/unit/application/query_handlers/test_get_movie_reviews.py
@@ -8,14 +8,23 @@
from amdb.domain.entities.movie import MovieId, Movie
from amdb.domain.entities.review import ReviewId, ReviewType, Review
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.user_gateway import UserGateway
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.review_gateway import ReviewGateway
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
-from amdb.application.queries.get_movie_reviews import GetMovieReviewsQuery, GetMovieReviewsResult
-from amdb.application.query_handlers.get_movie_reviews import GetMovieReviewsHandler
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
+from amdb.application.queries.get_movie_reviews import (
+ GetMovieReviewsQuery,
+ GetMovieReviewsResult,
+)
+from amdb.application.query_handlers.get_movie_reviews import (
+ GetMovieReviewsHandler,
+)
from amdb.application.common.constants.exceptions import (
GET_MOVIE_REVIEWS_ACCESS_DENIED,
MOVIE_DOES_NOT_EXIST,
@@ -84,7 +93,9 @@ def test_get_movie_reviews(
identity_provider=identity_provider_with_correct_permissions,
)
- get_movie_reviews_result = get_movie_reviews_handler.execute(get_movie_reviews_query)
+ get_movie_reviews_result = get_movie_reviews_handler.execute(
+ get_movie_reviews_query
+ )
expected_get_movie_reviews_result = GetMovieReviewsResult(
reviews=[review],
review_count=1,
diff --git a/tests/unit/application/query_handlers/test_get_movies.py b/tests/unit/application/query_handlers/test_get_movies.py
index d6aa732..a648328 100644
--- a/tests/unit/application/query_handlers/test_get_movies.py
+++ b/tests/unit/application/query_handlers/test_get_movies.py
@@ -6,13 +6,19 @@
from amdb.domain.entities.movie import MovieId, Movie
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.queries.get_movies import GetMoviesQuery, GetMoviesResult
from amdb.application.query_handlers.get_movies import GetMoviesHandler
-from amdb.application.common.constants.exceptions import GET_MOVIES_ACCESS_DENIED
+from amdb.application.common.constants.exceptions import (
+ GET_MOVIES_ACCESS_DENIED,
+)
from amdb.application.common.exception import ApplicationError
diff --git a/tests/unit/application/query_handlers/test_get_my_ratings.py b/tests/unit/application/query_handlers/test_get_my_ratings.py
index f547a29..eaff9eb 100644
--- a/tests/unit/application/query_handlers/test_get_my_ratings.py
+++ b/tests/unit/application/query_handlers/test_get_my_ratings.py
@@ -11,12 +11,21 @@
from amdb.application.common.interfaces.user_gateway import UserGateway
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.rating_gateway import RatingGateway
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
-from amdb.application.queries.get_my_ratings import GetMyRatingsQuery, GetMyRatingsResult
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
+from amdb.application.queries.get_my_ratings import (
+ GetMyRatingsQuery,
+ GetMyRatingsResult,
+)
from amdb.application.query_handlers.get_my_ratings import GetMyRatingsHandler
-from amdb.application.common.constants.exceptions import GET_MY_RATINGS_ACCESS_DENIED
+from amdb.application.common.constants.exceptions import (
+ GET_MY_RATINGS_ACCESS_DENIED,
+)
from amdb.application.common.exception import ApplicationError
@@ -81,7 +90,9 @@ def test_get_my_ratings(
identity_provider=identity_provider_with_correct_permissions,
)
- get_my_ratings_result = get_my_ratings_handler.execute(get_my_ratings_query)
+ get_my_ratings_result = get_my_ratings_handler.execute(
+ get_my_ratings_query
+ )
expected_get_my_ratings_result = GetMyRatingsResult(
ratings=[rating],
rating_count=1,
diff --git a/tests/unit/application/query_handlers/test_get_my_reviews.py b/tests/unit/application/query_handlers/test_get_my_reviews.py
index 1670532..ac4bafd 100644
--- a/tests/unit/application/query_handlers/test_get_my_reviews.py
+++ b/tests/unit/application/query_handlers/test_get_my_reviews.py
@@ -8,15 +8,24 @@
from amdb.domain.entities.movie import MovieId, Movie
from amdb.domain.entities.review import ReviewId, ReviewType, Review
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.user_gateway import UserGateway
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.review_gateway import ReviewGateway
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
-from amdb.application.queries.get_my_reviews import GetMyReviewsQuery, GetMyReviewsResult
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
+from amdb.application.queries.get_my_reviews import (
+ GetMyReviewsQuery,
+ GetMyReviewsResult,
+)
from amdb.application.query_handlers.get_my_reviews import GetMyReviewsHandler
-from amdb.application.common.constants.exceptions import GET_MY_REVIEWS_ACCESS_DENIED
+from amdb.application.common.constants.exceptions import (
+ GET_MY_REVIEWS_ACCESS_DENIED,
+)
from amdb.application.common.exception import ApplicationError
@@ -83,7 +92,9 @@ def test_get_my_reviews(
identity_provider=identity_provider_with_correct_permissions,
)
- get_my_reviews_result = get_my_reviews_handler.execute(get_my_reviews_query)
+ get_my_reviews_result = get_my_reviews_handler.execute(
+ get_my_reviews_query
+ )
expected_get_my_reviews_result = GetMyReviewsResult(
reviews=[review],
review_count=1,
diff --git a/tests/unit/application/query_handlers/test_get_rating.py b/tests/unit/application/query_handlers/test_get_rating.py
index a881321..4f8be6f 100644
--- a/tests/unit/application/query_handlers/test_get_rating.py
+++ b/tests/unit/application/query_handlers/test_get_rating.py
@@ -11,9 +11,13 @@
from amdb.application.common.interfaces.user_gateway import UserGateway
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.rating_gateway import RatingGateway
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.queries.get_rating import GetRatingQuery, GetRatingResult
from amdb.application.query_handlers.get_rating import GetRatingHandler
from amdb.application.common.constants.exceptions import (
diff --git a/tests/unit/application/query_handlers/test_get_review.py b/tests/unit/application/query_handlers/test_get_review.py
index b4651a1..858996b 100644
--- a/tests/unit/application/query_handlers/test_get_review.py
+++ b/tests/unit/application/query_handlers/test_get_review.py
@@ -8,12 +8,16 @@
from amdb.domain.entities.movie import MovieId, Movie
from amdb.domain.entities.review import ReviewId, ReviewType, Review
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.user_gateway import UserGateway
from amdb.application.common.interfaces.movie_gateway import MovieGateway
from amdb.application.common.interfaces.review_gateway import ReviewGateway
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import IdentityProvider
+from amdb.application.common.interfaces.identity_provider import (
+ IdentityProvider,
+)
from amdb.application.queries.get_review import GetReviewQuery, GetReviewResult
from amdb.application.query_handlers.get_review import GetReviewHandler
from amdb.application.common.constants.exceptions import (
diff --git a/tests/unit/application/query_handlers/test_login.py b/tests/unit/application/query_handlers/test_login.py
index e7736f7..d996d61 100644
--- a/tests/unit/application/query_handlers/test_login.py
+++ b/tests/unit/application/query_handlers/test_login.py
@@ -4,7 +4,9 @@
from amdb.domain.entities.user import UserId, User
from amdb.domain.services.access_concern import AccessConcern
from amdb.application.common.interfaces.user_gateway import UserGateway
-from amdb.application.common.interfaces.permissions_gateway import PermissionsGateway
+from amdb.application.common.interfaces.permissions_gateway import (
+ PermissionsGateway,
+)
from amdb.application.common.interfaces.unit_of_work import UnitOfWork
from amdb.application.common.interfaces.password_manager import PasswordManager
from amdb.application.queries.login import LoginQuery
diff --git a/tests/unit/infrastructure/alembic/test_stairway.py b/tests/unit/infrastructure/alembic/test_stairway.py
index 149eedf..b50338f 100644
--- a/tests/unit/infrastructure/alembic/test_stairway.py
+++ b/tests/unit/infrastructure/alembic/test_stairway.py
@@ -12,12 +12,16 @@
import alembic.script
-def get_revisions(alembic_config: alembic.config.Config) -> list[alembic.script.Script]:
+def get_revisions(
+ alembic_config: alembic.config.Config,
+) -> list[alembic.script.Script]:
# Get directory object with Alembic migrations
revisions_dir = alembic.script.ScriptDirectory.from_config(alembic_config)
# Get & sort migrations, from first to last
- revisions: list[alembic.script.Script] = list(revisions_dir.walk_revisions("base", "heads"))
+ revisions: list[alembic.script.Script] = list(
+ revisions_dir.walk_revisions("base", "heads")
+ )
revisions.reverse()
return revisions
@@ -28,5 +32,7 @@ def test_migrations_stairway(alembic_config: alembic.config.Config):
alembic.command.upgrade(alembic_config, revision.revision)
# We need -1 for downgrading first migration (its down_revision is None)
- alembic.command.downgrade(alembic_config, revision.down_revision or "-1") # type: ignore
+ alembic.command.downgrade(
+ alembic_config, revision.down_revision or "-1"
+ ) # type: ignore
alembic.command.upgrade(alembic_config, revision.revision)
From bd87b926352ab797dfefc06816799749accc68d1 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Mon, 19 Feb 2024 21:24:19 +0400
Subject: [PATCH 02/39] Remove old handlers, add new handlers
---
.../command_handlers/create_movie.py | 12 +-
.../command_handlers/delete_movie.py | 14 +-
.../command_handlers/rate_movie.py | 16 +-
.../command_handlers/register_user.py | 8 +-
.../command_handlers/review_movie.py | 16 +-
.../command_handlers/unrate_movie.py | 12 +-
.../common/constants/exceptions.py | 5 +-
.../{interfaces => gateways}/__init__.py | 0
.../movie_gateway.py => gateways/movie.py} | 0
.../permissions.py} | 17 +-
.../rating_gateway.py => gateways/rating.py} | 0
.../review_gateway.py => gateways/review.py} | 0
.../user_gateway.py => gateways/user.py} | 0
.../{interfaces => }/identity_provider.py | 0
.../{interfaces => }/password_manager.py | 0
.../application/common/readers/__init__.py | 0
src/amdb/application/common/readers/movie.py | 27 +++
src/amdb/application/common/readers/review.py | 14 ++
.../common/{interfaces => }/unit_of_work.py | 0
.../common/view_models/__init__.py | 0
.../common/view_models/detailed_movie.py | 32 ++++
.../common/view_models/non_detailed_movie.py | 20 ++
.../application/common/view_models/review.py | 32 ++++
.../application/queries/detailed_movie.py | 8 +
src/amdb/application/queries/get_movie.py | 17 --
.../application/queries/get_movie_ratings.py | 26 ---
.../application/queries/get_movie_reviews.py | 30 ---
src/amdb/application/queries/get_movies.py | 25 ---
.../application/queries/get_my_ratings.py | 27 ---
.../application/queries/get_my_reviews.py | 29 ---
src/amdb/application/queries/get_rating.py | 19 --
src/amdb/application/queries/get_review.py | 21 ---
.../queries/non_detailed_movies.py | 7 +
src/amdb/application/queries/reviews.py | 10 +
.../query_handlers/detailed_movie.py | 47 +++++
.../query_handlers/get_movie_ratings.py | 63 -------
.../query_handlers/get_movie_reviews.py | 63 -------
.../application/query_handlers/get_movies.py | 49 -----
.../query_handlers/get_my_ratings.py | 55 ------
.../query_handlers/get_my_reviews.py | 55 ------
.../application/query_handlers/get_rating.py | 52 ------
.../application/query_handlers/get_review.py | 54 ------
src/amdb/application/query_handlers/login.py | 8 +-
.../query_handlers/non_detailed_movies.py | 43 +++++
.../{get_movie.py => reviews.py} | 37 ++--
.../command_handlers/test_create_movie.py | 12 +-
.../command_handlers/test_delete_movie.py | 16 +-
.../command_handlers/test_rate_movie.py | 16 +-
.../command_handlers/test_register_user.py | 10 +-
.../command_handlers/test_review_movie.py | 16 +-
.../command_handlers/test_unrate_movie.py | 16 +-
.../query_handlers/test_detailed_movie.py | 174 ++++++++++++++++++
.../query_handlers/test_get_movie.py | 115 ------------
.../query_handlers/test_get_movie_ratings.py | 156 ----------------
.../query_handlers/test_get_movie_reviews.py | 154 ----------------
.../query_handlers/test_get_movies.py | 111 -----------
.../query_handlers/test_get_my_reviews.py | 129 -------------
.../query_handlers/test_get_rating.py | 140 --------------
.../query_handlers/test_get_review.py | 140 --------------
.../application/query_handlers/test_login.py | 10 +-
...ratings.py => test_non_detailed_movies.py} | 83 +++++----
61 files changed, 547 insertions(+), 1721 deletions(-)
rename src/amdb/application/common/{interfaces => gateways}/__init__.py (100%)
rename src/amdb/application/common/{interfaces/movie_gateway.py => gateways/movie.py} (100%)
rename src/amdb/application/common/{interfaces/permissions_gateway.py => gateways/permissions.py} (70%)
rename src/amdb/application/common/{interfaces/rating_gateway.py => gateways/rating.py} (100%)
rename src/amdb/application/common/{interfaces/review_gateway.py => gateways/review.py} (100%)
rename src/amdb/application/common/{interfaces/user_gateway.py => gateways/user.py} (100%)
rename src/amdb/application/common/{interfaces => }/identity_provider.py (100%)
rename src/amdb/application/common/{interfaces => }/password_manager.py (100%)
create mode 100644 src/amdb/application/common/readers/__init__.py
create mode 100644 src/amdb/application/common/readers/movie.py
create mode 100644 src/amdb/application/common/readers/review.py
rename src/amdb/application/common/{interfaces => }/unit_of_work.py (100%)
create mode 100644 src/amdb/application/common/view_models/__init__.py
create mode 100644 src/amdb/application/common/view_models/detailed_movie.py
create mode 100644 src/amdb/application/common/view_models/non_detailed_movie.py
create mode 100644 src/amdb/application/common/view_models/review.py
create mode 100644 src/amdb/application/queries/detailed_movie.py
delete mode 100644 src/amdb/application/queries/get_movie.py
delete mode 100644 src/amdb/application/queries/get_movie_ratings.py
delete mode 100644 src/amdb/application/queries/get_movie_reviews.py
delete mode 100644 src/amdb/application/queries/get_movies.py
delete mode 100644 src/amdb/application/queries/get_my_ratings.py
delete mode 100644 src/amdb/application/queries/get_my_reviews.py
delete mode 100644 src/amdb/application/queries/get_rating.py
delete mode 100644 src/amdb/application/queries/get_review.py
create mode 100644 src/amdb/application/queries/non_detailed_movies.py
create mode 100644 src/amdb/application/queries/reviews.py
create mode 100644 src/amdb/application/query_handlers/detailed_movie.py
delete mode 100644 src/amdb/application/query_handlers/get_movie_ratings.py
delete mode 100644 src/amdb/application/query_handlers/get_movie_reviews.py
delete mode 100644 src/amdb/application/query_handlers/get_movies.py
delete mode 100644 src/amdb/application/query_handlers/get_my_ratings.py
delete mode 100644 src/amdb/application/query_handlers/get_my_reviews.py
delete mode 100644 src/amdb/application/query_handlers/get_rating.py
delete mode 100644 src/amdb/application/query_handlers/get_review.py
create mode 100644 src/amdb/application/query_handlers/non_detailed_movies.py
rename src/amdb/application/query_handlers/{get_movie.py => reviews.py} (54%)
create mode 100644 tests/unit/application/query_handlers/test_detailed_movie.py
delete mode 100644 tests/unit/application/query_handlers/test_get_movie.py
delete mode 100644 tests/unit/application/query_handlers/test_get_movie_ratings.py
delete mode 100644 tests/unit/application/query_handlers/test_get_movie_reviews.py
delete mode 100644 tests/unit/application/query_handlers/test_get_movies.py
delete mode 100644 tests/unit/application/query_handlers/test_get_my_reviews.py
delete mode 100644 tests/unit/application/query_handlers/test_get_rating.py
delete mode 100644 tests/unit/application/query_handlers/test_get_review.py
rename tests/unit/application/query_handlers/{test_get_my_ratings.py => test_non_detailed_movies.py} (50%)
diff --git a/src/amdb/application/command_handlers/create_movie.py b/src/amdb/application/command_handlers/create_movie.py
index 3c481e0..ff40f65 100644
--- a/src/amdb/application/command_handlers/create_movie.py
+++ b/src/amdb/application/command_handlers/create_movie.py
@@ -3,14 +3,10 @@
from amdb.domain.entities.movie import MovieId
from amdb.domain.services.access_concern import AccessConcern
from amdb.domain.services.create_movie import CreateMovie
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
+from amdb.application.common.gateways.permissions import PermissionsGateway
+from amdb.application.common.gateways.movie import MovieGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.common.constants.exceptions import (
CREATE_MOVIE_ACCESS_DENIED,
)
diff --git a/src/amdb/application/command_handlers/delete_movie.py b/src/amdb/application/command_handlers/delete_movie.py
index c4b5963..8b7ab42 100644
--- a/src/amdb/application/command_handlers/delete_movie.py
+++ b/src/amdb/application/command_handlers/delete_movie.py
@@ -1,14 +1,12 @@
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import (
+from amdb.application.common.gateways.permissions import (
PermissionsGateway,
)
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.rating_gateway import RatingGateway
-from amdb.application.common.interfaces.review_gateway import ReviewGateway
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
+from amdb.application.common.gateways.movie import MovieGateway
+from amdb.application.common.gateways.rating import RatingGateway
+from amdb.application.common.gateways.review import ReviewGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.common.constants.exceptions import (
DELETE_MOVIE_ACCESS_DENIED,
MOVIE_DOES_NOT_EXIST,
diff --git a/src/amdb/application/command_handlers/rate_movie.py b/src/amdb/application/command_handlers/rate_movie.py
index ee50707..f576ad2 100644
--- a/src/amdb/application/command_handlers/rate_movie.py
+++ b/src/amdb/application/command_handlers/rate_movie.py
@@ -7,16 +7,12 @@
from amdb.domain.entities.rating import RatingId
from amdb.domain.services.access_concern import AccessConcern
from amdb.domain.services.rate_movie import RateMovie
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.user_gateway import UserGateway
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.rating_gateway import RatingGateway
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
+from amdb.application.common.gateways.permissions import PermissionsGateway
+from amdb.application.common.gateways.user import UserGateway
+from amdb.application.common.gateways.movie import MovieGateway
+from amdb.application.common.gateways.rating import RatingGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.common.constants.exceptions import (
RATE_MOVIE_ACCESS_DENIED,
MOVIE_DOES_NOT_EXIST,
diff --git a/src/amdb/application/command_handlers/register_user.py b/src/amdb/application/command_handlers/register_user.py
index c3f3431..bfc0f6c 100644
--- a/src/amdb/application/command_handlers/register_user.py
+++ b/src/amdb/application/command_handlers/register_user.py
@@ -2,12 +2,12 @@
from amdb.domain.entities.user import UserId
from amdb.domain.services.create_user import CreateUser
-from amdb.application.common.interfaces.user_gateway import UserGateway
-from amdb.application.common.interfaces.permissions_gateway import (
+from amdb.application.common.gateways.user import UserGateway
+from amdb.application.common.gateways.permissions import (
PermissionsGateway,
)
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.password_manager import PasswordManager
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.password_manager import PasswordManager
from amdb.application.common.constants.exceptions import (
USER_NAME_ALREADY_EXISTS,
)
diff --git a/src/amdb/application/command_handlers/review_movie.py b/src/amdb/application/command_handlers/review_movie.py
index 036305e..be4609c 100644
--- a/src/amdb/application/command_handlers/review_movie.py
+++ b/src/amdb/application/command_handlers/review_movie.py
@@ -7,16 +7,12 @@
from amdb.domain.entities.review import ReviewId
from amdb.domain.services.access_concern import AccessConcern
from amdb.domain.services.review_movie import ReviewMovie
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.user_gateway import UserGateway
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.review_gateway import ReviewGateway
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
+from amdb.application.common.gateways.permissions import PermissionsGateway
+from amdb.application.common.gateways.user import UserGateway
+from amdb.application.common.gateways.movie import MovieGateway
+from amdb.application.common.gateways.review import ReviewGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.common.constants.exceptions import (
REVIEW_MOVIE_ACCESS_DENIED,
MOVIE_DOES_NOT_EXIST,
diff --git a/src/amdb/application/command_handlers/unrate_movie.py b/src/amdb/application/command_handlers/unrate_movie.py
index 67e19e2..c766e50 100644
--- a/src/amdb/application/command_handlers/unrate_movie.py
+++ b/src/amdb/application/command_handlers/unrate_movie.py
@@ -3,15 +3,13 @@
from amdb.domain.entities.movie import Movie
from amdb.domain.services.access_concern import AccessConcern
from amdb.domain.services.unrate_movie import UnrateMovie
-from amdb.application.common.interfaces.permissions_gateway import (
+from amdb.application.common.gateways.permissions import (
PermissionsGateway,
)
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.rating_gateway import RatingGateway
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
+from amdb.application.common.gateways.movie import MovieGateway
+from amdb.application.common.gateways.rating import RatingGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.common.constants.exceptions import (
UNRATE_MOVIE_ACCESS_DENIED,
USER_IS_NOT_OWNER,
diff --git a/src/amdb/application/common/constants/exceptions.py b/src/amdb/application/common/constants/exceptions.py
index dfbfecf..19a3df2 100644
--- a/src/amdb/application/common/constants/exceptions.py
+++ b/src/amdb/application/common/constants/exceptions.py
@@ -3,13 +3,10 @@
GET_MOVIE_ACCESS_DENIED = "Access to getting movie is denied"
CREATE_MOVIE_ACCESS_DENIED = "Access to movie creation is denied"
DELETE_MOVIE_ACCESS_DENIED = "Access to movie deletion is denied"
-GET_MOVIE_RATINGS_ACCESS_DENIED = "Access to getting movie ratings is denied"
-GET_MY_RATINGS_ACCESS_DENIED = "Access to getting your ratings is denied"
GET_RATING_ACCESS_DENIED = "Access to getting rating is denied"
RATE_MOVIE_ACCESS_DENIED = "Access to movie rating is denied"
UNRATE_MOVIE_ACCESS_DENIED = "Access to movie unrating is denied"
-GET_MOVIE_REVIEWS_ACCESS_DENIED = "Access to getting movie reviews is denied"
-GET_MY_REVIEWS_ACCESS_DENIED = "Access to getting your reviews is denied"
+GET_REVIEWS_ACCESS_DENIED = "Access to getting movie reviews is denied"
GET_REVIEW_ACCESS_DENIED = "Access to getting review is denied"
REVIEW_MOVIE_ACCESS_DENIED = "Access to movie reviewing is denied"
diff --git a/src/amdb/application/common/interfaces/__init__.py b/src/amdb/application/common/gateways/__init__.py
similarity index 100%
rename from src/amdb/application/common/interfaces/__init__.py
rename to src/amdb/application/common/gateways/__init__.py
diff --git a/src/amdb/application/common/interfaces/movie_gateway.py b/src/amdb/application/common/gateways/movie.py
similarity index 100%
rename from src/amdb/application/common/interfaces/movie_gateway.py
rename to src/amdb/application/common/gateways/movie.py
diff --git a/src/amdb/application/common/interfaces/permissions_gateway.py b/src/amdb/application/common/gateways/permissions.py
similarity index 70%
rename from src/amdb/application/common/interfaces/permissions_gateway.py
rename to src/amdb/application/common/gateways/permissions.py
index 86ac24b..94b29b2 100644
--- a/src/amdb/application/common/interfaces/permissions_gateway.py
+++ b/src/amdb/application/common/gateways/permissions.py
@@ -28,28 +28,13 @@ def for_create_movie(self) -> int:
def for_delete_movie(self) -> int:
raise NotImplementedError
- def for_get_movie_ratings(self) -> int:
- raise NotImplementedError
-
- def for_get_my_ratings(self) -> int:
- raise NotImplementedError
-
- def for_get_rating(self) -> int:
- raise NotImplementedError
-
def for_rate_movie(self) -> int:
raise NotImplementedError
def for_unrate_movie(self) -> int:
raise NotImplementedError
- def for_get_movie_reviews(self) -> int:
- raise NotImplementedError
-
- def for_get_my_reviews(self) -> int:
- raise NotImplementedError
-
- def for_get_review(self) -> int:
+ def for_get_reviews(self) -> int:
raise NotImplementedError
def for_review_movie(self) -> int:
diff --git a/src/amdb/application/common/interfaces/rating_gateway.py b/src/amdb/application/common/gateways/rating.py
similarity index 100%
rename from src/amdb/application/common/interfaces/rating_gateway.py
rename to src/amdb/application/common/gateways/rating.py
diff --git a/src/amdb/application/common/interfaces/review_gateway.py b/src/amdb/application/common/gateways/review.py
similarity index 100%
rename from src/amdb/application/common/interfaces/review_gateway.py
rename to src/amdb/application/common/gateways/review.py
diff --git a/src/amdb/application/common/interfaces/user_gateway.py b/src/amdb/application/common/gateways/user.py
similarity index 100%
rename from src/amdb/application/common/interfaces/user_gateway.py
rename to src/amdb/application/common/gateways/user.py
diff --git a/src/amdb/application/common/interfaces/identity_provider.py b/src/amdb/application/common/identity_provider.py
similarity index 100%
rename from src/amdb/application/common/interfaces/identity_provider.py
rename to src/amdb/application/common/identity_provider.py
diff --git a/src/amdb/application/common/interfaces/password_manager.py b/src/amdb/application/common/password_manager.py
similarity index 100%
rename from src/amdb/application/common/interfaces/password_manager.py
rename to src/amdb/application/common/password_manager.py
diff --git a/src/amdb/application/common/readers/__init__.py b/src/amdb/application/common/readers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/amdb/application/common/readers/movie.py b/src/amdb/application/common/readers/movie.py
new file mode 100644
index 0000000..8a3667b
--- /dev/null
+++ b/src/amdb/application/common/readers/movie.py
@@ -0,0 +1,27 @@
+from typing import Protocol, Optional
+
+from amdb.domain.entities.user import UserId
+from amdb.domain.entities.movie import MovieId
+from amdb.application.common.view_models.detailed_movie import (
+ DetailedMovieViewModel,
+)
+from amdb.application.common.view_models.non_detailed_movie import (
+ NonDetailedMovieViewModel,
+)
+
+
+class MovieViewModelReader(Protocol):
+ def list_non_detailed(
+ self,
+ current_user_id: Optional[UserId],
+ limit: int,
+ offset: int,
+ ) -> list[NonDetailedMovieViewModel]:
+ raise NotImplementedError
+
+ def detailed(
+ self,
+ movie_id: MovieId,
+ current_user_id: Optional[UserId],
+ ) -> Optional[DetailedMovieViewModel]:
+ raise NotImplementedError
diff --git a/src/amdb/application/common/readers/review.py b/src/amdb/application/common/readers/review.py
new file mode 100644
index 0000000..f04f6f1
--- /dev/null
+++ b/src/amdb/application/common/readers/review.py
@@ -0,0 +1,14 @@
+from typing import Protocol
+
+from amdb.domain.entities.movie import MovieId
+from amdb.application.common.view_models.review import ReviewViewModel
+
+
+class ReviewViewModelReader(Protocol):
+ def list(
+ self,
+ movie_id: MovieId,
+ limit: int,
+ offset: int,
+ ) -> list[ReviewViewModel]:
+ raise NotImplementedError
diff --git a/src/amdb/application/common/interfaces/unit_of_work.py b/src/amdb/application/common/unit_of_work.py
similarity index 100%
rename from src/amdb/application/common/interfaces/unit_of_work.py
rename to src/amdb/application/common/unit_of_work.py
diff --git a/src/amdb/application/common/view_models/__init__.py b/src/amdb/application/common/view_models/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/amdb/application/common/view_models/detailed_movie.py b/src/amdb/application/common/view_models/detailed_movie.py
new file mode 100644
index 0000000..61b86e5
--- /dev/null
+++ b/src/amdb/application/common/view_models/detailed_movie.py
@@ -0,0 +1,32 @@
+from datetime import date, datetime
+from typing import Optional
+
+from typing_extensions import TypedDict
+
+from amdb.domain.entities.movie import MovieId
+from amdb.domain.entities.rating import RatingId
+from amdb.domain.entities.review import ReviewId, ReviewType
+
+
+class UserRating(TypedDict):
+ id: RatingId
+ value: float
+ created_at: datetime
+
+
+class UserReview(TypedDict):
+ id: ReviewId
+ title: str
+ content: str
+ type: ReviewType
+ created_at: datetime
+
+
+class DetailedMovieViewModel(TypedDict):
+ id: MovieId
+ title: str
+ release_date: date
+ rating: float
+ rating_count: int
+ user_rating: Optional[UserRating]
+ user_review: Optional[UserReview]
diff --git a/src/amdb/application/common/view_models/non_detailed_movie.py b/src/amdb/application/common/view_models/non_detailed_movie.py
new file mode 100644
index 0000000..03b3c09
--- /dev/null
+++ b/src/amdb/application/common/view_models/non_detailed_movie.py
@@ -0,0 +1,20 @@
+from datetime import date
+from typing import Optional
+
+from typing_extensions import TypedDict
+
+from amdb.domain.entities.movie import MovieId
+from amdb.domain.entities.rating import RatingId
+
+
+class UserRating(TypedDict):
+ id: RatingId
+ value: float
+
+
+class NonDetailedMovieViewModel(TypedDict):
+ id: MovieId
+ title: str
+ release_date: date
+ rating: float
+ user_rating: Optional[UserRating]
diff --git a/src/amdb/application/common/view_models/review.py b/src/amdb/application/common/view_models/review.py
new file mode 100644
index 0000000..be4e8e3
--- /dev/null
+++ b/src/amdb/application/common/view_models/review.py
@@ -0,0 +1,32 @@
+__all__ = ("ReviewViewModel",)
+
+from datetime import datetime
+from typing import Optional
+
+from typing_extensions import TypedDict
+
+from amdb.domain.entities.user import UserId
+from amdb.domain.entities.movie import MovieId
+from amdb.domain.entities.rating import RatingId
+from amdb.domain.entities.review import ReviewId, ReviewType
+
+
+class Rating(TypedDict):
+ id: RatingId
+ value: float
+ created_at: datetime
+
+
+class Review(TypedDict):
+ id: ReviewId
+ title: str
+ content: str
+ type: ReviewType
+ created_at: datetime
+
+
+class ReviewViewModel(TypedDict):
+ user_id: UserId
+ movie_id: MovieId
+ review: Review
+ rating: Optional[Rating]
diff --git a/src/amdb/application/queries/detailed_movie.py b/src/amdb/application/queries/detailed_movie.py
new file mode 100644
index 0000000..5da846a
--- /dev/null
+++ b/src/amdb/application/queries/detailed_movie.py
@@ -0,0 +1,8 @@
+from dataclasses import dataclass
+
+from amdb.domain.entities.movie import MovieId
+
+
+@dataclass(frozen=True, slots=True)
+class GetDetailedMovieQuery:
+ movie_id: MovieId
diff --git a/src/amdb/application/queries/get_movie.py b/src/amdb/application/queries/get_movie.py
deleted file mode 100644
index fb197e7..0000000
--- a/src/amdb/application/queries/get_movie.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from dataclasses import dataclass
-from datetime import date
-
-from amdb.domain.entities.movie import MovieId
-
-
-@dataclass(frozen=True, slots=True)
-class GetMovieQuery:
- movie_id: MovieId
-
-
-@dataclass(frozen=True, slots=True)
-class GetMovieResult:
- title: str
- release_date: date
- rating: float
- rating_count: int
diff --git a/src/amdb/application/queries/get_movie_ratings.py b/src/amdb/application/queries/get_movie_ratings.py
deleted file mode 100644
index 51e1a6a..0000000
--- a/src/amdb/application/queries/get_movie_ratings.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from dataclasses import dataclass
-from datetime import datetime
-
-from amdb.domain.entities.user import UserId
-from amdb.domain.entities.movie import MovieId
-
-
-@dataclass(frozen=True, slots=True)
-class GetMovieRatingsQuery:
- movie_id: MovieId
- limit: int
- offset: int
-
-
-@dataclass(frozen=True, slots=True)
-class Rating:
- user_id: UserId
- movie_id: MovieId
- value: float
- created_at: datetime
-
-
-@dataclass(frozen=True, slots=True)
-class GetMovieRatingsResult:
- ratings: list[Rating]
- rating_count: int
diff --git a/src/amdb/application/queries/get_movie_reviews.py b/src/amdb/application/queries/get_movie_reviews.py
deleted file mode 100644
index 395fd60..0000000
--- a/src/amdb/application/queries/get_movie_reviews.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from dataclasses import dataclass
-from datetime import datetime
-
-from amdb.domain.entities.user import UserId
-from amdb.domain.entities.movie import MovieId
-from amdb.domain.entities.review import ReviewId, ReviewType
-
-
-@dataclass(frozen=True, slots=True)
-class GetMovieReviewsQuery:
- movie_id: MovieId
- limit: int
- offset: int
-
-
-@dataclass(frozen=True, slots=True)
-class Review:
- id: ReviewId
- user_id: UserId
- movie_id: MovieId
- title: str
- content: str
- type: ReviewType
- created_at: datetime
-
-
-@dataclass(frozen=True, slots=True)
-class GetMovieReviewsResult:
- reviews: list[Review]
- review_count: int
diff --git a/src/amdb/application/queries/get_movies.py b/src/amdb/application/queries/get_movies.py
deleted file mode 100644
index 985338d..0000000
--- a/src/amdb/application/queries/get_movies.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from dataclasses import dataclass
-from datetime import date
-
-from amdb.domain.entities.movie import MovieId
-
-
-@dataclass(frozen=True, slots=True)
-class GetMoviesQuery:
- limit: int
- offset: int
-
-
-@dataclass(frozen=True, slots=True)
-class Movie:
- id: MovieId
- title: str
- release_date: date
- rating: float
- rating_count: int
-
-
-@dataclass(frozen=True, slots=True)
-class GetMoviesResult:
- movies: list[Movie]
- movie_count: int
diff --git a/src/amdb/application/queries/get_my_ratings.py b/src/amdb/application/queries/get_my_ratings.py
deleted file mode 100644
index ee2a926..0000000
--- a/src/amdb/application/queries/get_my_ratings.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from dataclasses import dataclass
-from datetime import datetime
-
-from amdb.domain.entities.user import UserId
-from amdb.domain.entities.movie import MovieId
-from amdb.domain.entities.rating import RatingId
-
-
-@dataclass(frozen=True, slots=True)
-class GetMyRatingsQuery:
- limit: int
- offset: int
-
-
-@dataclass(frozen=True, slots=True)
-class Rating:
- id: RatingId
- user_id: UserId
- movie_id: MovieId
- value: float
- created_at: datetime
-
-
-@dataclass(frozen=True, slots=True)
-class GetMyRatingsResult:
- ratings: list[Rating]
- rating_count: int
diff --git a/src/amdb/application/queries/get_my_reviews.py b/src/amdb/application/queries/get_my_reviews.py
deleted file mode 100644
index 85ebd96..0000000
--- a/src/amdb/application/queries/get_my_reviews.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from dataclasses import dataclass
-from datetime import datetime
-
-from amdb.domain.entities.user import UserId
-from amdb.domain.entities.movie import MovieId
-from amdb.domain.entities.review import ReviewId, ReviewType
-
-
-@dataclass(frozen=True, slots=True)
-class GetMyReviewsQuery:
- limit: int
- offset: int
-
-
-@dataclass(frozen=True, slots=True)
-class Review:
- id: ReviewId
- user_id: UserId
- movie_id: MovieId
- title: str
- content: str
- type: ReviewType
- created_at: datetime
-
-
-@dataclass(frozen=True, slots=True)
-class GetMyReviewsResult:
- reviews: list[Review]
- review_count: int
diff --git a/src/amdb/application/queries/get_rating.py b/src/amdb/application/queries/get_rating.py
deleted file mode 100644
index 16745bb..0000000
--- a/src/amdb/application/queries/get_rating.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from dataclasses import dataclass
-from datetime import datetime
-
-from amdb.domain.entities.user import UserId
-from amdb.domain.entities.movie import MovieId
-from amdb.domain.entities.rating import RatingId
-
-
-@dataclass(frozen=True, slots=True)
-class GetRatingQuery:
- rating_id: RatingId
-
-
-@dataclass(frozen=True, slots=True)
-class GetRatingResult:
- user_id: UserId
- movie_id: MovieId
- value: float
- created_at: datetime
diff --git a/src/amdb/application/queries/get_review.py b/src/amdb/application/queries/get_review.py
deleted file mode 100644
index ff8189e..0000000
--- a/src/amdb/application/queries/get_review.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from dataclasses import dataclass
-from datetime import datetime
-
-from amdb.domain.entities.user import UserId
-from amdb.domain.entities.movie import MovieId
-from amdb.domain.entities.review import ReviewId, ReviewType
-
-
-@dataclass(frozen=True, slots=True)
-class GetReviewQuery:
- review_id: ReviewId
-
-
-@dataclass(frozen=True, slots=True)
-class GetReviewResult:
- user_id: UserId
- movie_id: MovieId
- title: str
- content: str
- type: ReviewType
- created_at: datetime
diff --git a/src/amdb/application/queries/non_detailed_movies.py b/src/amdb/application/queries/non_detailed_movies.py
new file mode 100644
index 0000000..7b5ff87
--- /dev/null
+++ b/src/amdb/application/queries/non_detailed_movies.py
@@ -0,0 +1,7 @@
+from dataclasses import dataclass
+
+
+@dataclass(frozen=True, slots=True)
+class GetNonDetailedMoviesQuery:
+ limit: int
+ offset: int
diff --git a/src/amdb/application/queries/reviews.py b/src/amdb/application/queries/reviews.py
new file mode 100644
index 0000000..a3577b0
--- /dev/null
+++ b/src/amdb/application/queries/reviews.py
@@ -0,0 +1,10 @@
+from dataclasses import dataclass
+
+from amdb.domain.entities.movie import MovieId
+
+
+@dataclass(frozen=True, slots=True)
+class GetReviewsQuery:
+ movie_id: MovieId
+ limit: int
+ offset: int
diff --git a/src/amdb/application/query_handlers/detailed_movie.py b/src/amdb/application/query_handlers/detailed_movie.py
new file mode 100644
index 0000000..b001775
--- /dev/null
+++ b/src/amdb/application/query_handlers/detailed_movie.py
@@ -0,0 +1,47 @@
+from amdb.domain.services.access_concern import AccessConcern
+from amdb.application.common.gateways.permissions import PermissionsGateway
+from amdb.application.common.readers.movie import MovieViewModelReader
+from amdb.application.common.identity_provider import IdentityProvider
+from amdb.application.common.constants.exceptions import (
+ GET_MOVIE_ACCESS_DENIED,
+ MOVIE_DOES_NOT_EXIST,
+)
+from amdb.application.common.exception import ApplicationError
+from amdb.application.common.view_models.detailed_movie import DetailedMovieViewModel
+from amdb.application.queries.detailed_movie import GetDetailedMovieQuery
+
+
+class GetDetailedMovieHandler:
+ def __init__(
+ self,
+ *,
+ access_concern: AccessConcern,
+ permissions_gateway: PermissionsGateway,
+ movie_view_model_reader: MovieViewModelReader,
+ identity_provider: IdentityProvider,
+ ) -> None:
+ self._access_concern = access_concern
+ self._permissions_gateway = permissions_gateway
+ self._movie_view_model_reader = movie_view_model_reader
+ self._identity_provider = identity_provider
+
+ def execute(self, query: GetDetailedMovieQuery) -> DetailedMovieViewModel:
+ current_permissions = self._identity_provider.get_permissions()
+ required_permissions = self._permissions_gateway.for_get_movie()
+ access = self._access_concern.authorize(
+ current_permissions=current_permissions,
+ required_permissions=required_permissions,
+ )
+ if not access:
+ raise ApplicationError(GET_MOVIE_ACCESS_DENIED)
+
+ current_user_id = self._identity_provider.get_user_id()
+
+ detailed_movie_view_model = self._movie_view_model_reader.detailed(
+ movie_id=query.movie_id,
+ current_user_id=current_user_id,
+ )
+ if not detailed_movie_view_model:
+ raise ApplicationError(MOVIE_DOES_NOT_EXIST)
+
+ return detailed_movie_view_model
diff --git a/src/amdb/application/query_handlers/get_movie_ratings.py b/src/amdb/application/query_handlers/get_movie_ratings.py
deleted file mode 100644
index 6566f04..0000000
--- a/src/amdb/application/query_handlers/get_movie_ratings.py
+++ /dev/null
@@ -1,63 +0,0 @@
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.rating_gateway import RatingGateway
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.queries.get_movie_ratings import (
- GetMovieRatingsQuery,
- GetMovieRatingsResult,
-)
-from amdb.application.common.constants.exceptions import (
- GET_MOVIE_RATINGS_ACCESS_DENIED,
- MOVIE_DOES_NOT_EXIST,
-)
-from amdb.application.common.exception import ApplicationError
-
-
-class GetMovieRatingsHandler:
- def __init__(
- self,
- *,
- access_concern: AccessConcern,
- permissions_gateway: PermissionsGateway,
- movie_gateway: MovieGateway,
- rating_gateway: RatingGateway,
- identity_provider: IdentityProvider,
- ) -> None:
- self._access_concern = access_concern
- self._permissions_gateway = permissions_gateway
- self._movie_gateway = movie_gateway
- self._rating_gateway = rating_gateway
- self._identity_provider = identity_provider
-
- def execute(self, query: GetMovieRatingsQuery) -> GetMovieRatingsResult:
- current_permissions = self._identity_provider.get_permissions()
- required_permissions = (
- self._permissions_gateway.for_get_movie_ratings()
- )
- access = self._access_concern.authorize(
- current_permissions=current_permissions,
- required_permissions=required_permissions,
- )
- if not access:
- raise ApplicationError(GET_MOVIE_RATINGS_ACCESS_DENIED)
-
- movie = self._movie_gateway.with_id(query.movie_id)
- if not movie:
- raise ApplicationError(MOVIE_DOES_NOT_EXIST)
-
- ratings = self._rating_gateway.list_with_movie_id(
- movie_id=query.movie_id,
- limit=query.limit,
- offset=query.offset,
- )
- get_movie_ratings_result = GetMovieRatingsResult(
- ratings=ratings, # type: ignore
- rating_count=len(ratings),
- )
-
- return get_movie_ratings_result
diff --git a/src/amdb/application/query_handlers/get_movie_reviews.py b/src/amdb/application/query_handlers/get_movie_reviews.py
deleted file mode 100644
index 7d8534f..0000000
--- a/src/amdb/application/query_handlers/get_movie_reviews.py
+++ /dev/null
@@ -1,63 +0,0 @@
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.review_gateway import ReviewGateway
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.common.constants.exceptions import (
- GET_MOVIE_REVIEWS_ACCESS_DENIED,
- MOVIE_DOES_NOT_EXIST,
-)
-from amdb.application.common.exception import ApplicationError
-from amdb.application.queries.get_movie_reviews import (
- GetMovieReviewsQuery,
- GetMovieReviewsResult,
-)
-
-
-class GetMovieReviewsHandler:
- def __init__(
- self,
- *,
- access_concern: AccessConcern,
- permissions_gateway: PermissionsGateway,
- movie_gateway: MovieGateway,
- review_gateway: ReviewGateway,
- identity_provider: IdentityProvider,
- ) -> None:
- self._access_concern = access_concern
- self._permissions_gateway = permissions_gateway
- self._movie_gateway = movie_gateway
- self._review_gateway = review_gateway
- self._identity_provider = identity_provider
-
- def execute(self, query: GetMovieReviewsQuery) -> GetMovieReviewsResult:
- current_permissions = self._identity_provider.get_permissions()
- required_permissions = (
- self._permissions_gateway.for_get_movie_reviews()
- )
- access = self._access_concern.authorize(
- current_permissions=current_permissions,
- required_permissions=required_permissions,
- )
- if not access:
- raise ApplicationError(GET_MOVIE_REVIEWS_ACCESS_DENIED)
-
- movie = self._movie_gateway.with_id(query.movie_id)
- if not movie:
- raise ApplicationError(MOVIE_DOES_NOT_EXIST)
-
- reviews = self._review_gateway.list_with_movie_id(
- movie_id=query.movie_id,
- limit=query.limit,
- offset=query.offset,
- )
- get_movie_reviews_result = GetMovieReviewsResult(
- reviews=reviews, # type: ignore
- review_count=len(reviews),
- )
-
- return get_movie_reviews_result
diff --git a/src/amdb/application/query_handlers/get_movies.py b/src/amdb/application/query_handlers/get_movies.py
deleted file mode 100644
index d06909d..0000000
--- a/src/amdb/application/query_handlers/get_movies.py
+++ /dev/null
@@ -1,49 +0,0 @@
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.queries.get_movies import GetMoviesQuery, GetMoviesResult
-from amdb.application.common.constants.exceptions import (
- GET_MOVIES_ACCESS_DENIED,
-)
-from amdb.application.common.exception import ApplicationError
-
-
-class GetMoviesHandler:
- def __init__(
- self,
- *,
- access_concern: AccessConcern,
- permissions_gateway: PermissionsGateway,
- movie_gateway: MovieGateway,
- identity_provider: IdentityProvider,
- ) -> None:
- self._access_concern = access_concern
- self._permissions_gateway = permissions_gateway
- self._movie_gateway = movie_gateway
- self._identity_provider = identity_provider
-
- def execute(self, query: GetMoviesQuery) -> GetMoviesResult:
- current_permissions = self._identity_provider.get_permissions()
- required_permissions = self._permissions_gateway.for_get_movies()
- access = self._access_concern.authorize(
- current_permissions=current_permissions,
- required_permissions=required_permissions,
- )
- if not access:
- raise ApplicationError(GET_MOVIES_ACCESS_DENIED)
-
- movies = self._movie_gateway.list(
- limit=query.limit,
- offset=query.offset,
- )
- get_movies_result = GetMoviesResult(
- movies=movies, # type: ignore
- movie_count=len(movies),
- )
-
- return get_movies_result
diff --git a/src/amdb/application/query_handlers/get_my_ratings.py b/src/amdb/application/query_handlers/get_my_ratings.py
deleted file mode 100644
index 5fcef7a..0000000
--- a/src/amdb/application/query_handlers/get_my_ratings.py
+++ /dev/null
@@ -1,55 +0,0 @@
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.rating_gateway import RatingGateway
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.queries.get_my_ratings import (
- GetMyRatingsQuery,
- GetMyRatingsResult,
-)
-from amdb.application.common.constants.exceptions import (
- GET_MY_RATINGS_ACCESS_DENIED,
-)
-from amdb.application.common.exception import ApplicationError
-
-
-class GetMyRatingsHandler:
- def __init__(
- self,
- *,
- access_concern: AccessConcern,
- permissions_gateway: PermissionsGateway,
- rating_gateway: RatingGateway,
- identity_provider: IdentityProvider,
- ) -> None:
- self._access_concern = access_concern
- self._permissions_gateway = permissions_gateway
- self._rating_gateway = rating_gateway
- self._identity_provider = identity_provider
-
- def execute(self, query: GetMyRatingsQuery) -> GetMyRatingsResult:
- current_permissions = self._identity_provider.get_permissions()
- required_permissions = self._permissions_gateway.for_get_rating()
- access = self._access_concern.authorize(
- current_permissions=current_permissions,
- required_permissions=required_permissions,
- )
- if not access:
- raise ApplicationError(GET_MY_RATINGS_ACCESS_DENIED)
-
- current_user_id = self._identity_provider.get_user_id()
-
- ratings = self._rating_gateway.list_with_user_id(
- user_id=current_user_id,
- limit=query.limit,
- offset=query.offset,
- )
- get_my_ratings_result = GetMyRatingsResult(
- ratings=ratings, # type: ignore
- rating_count=len(ratings),
- )
-
- return get_my_ratings_result
diff --git a/src/amdb/application/query_handlers/get_my_reviews.py b/src/amdb/application/query_handlers/get_my_reviews.py
deleted file mode 100644
index 9004cbd..0000000
--- a/src/amdb/application/query_handlers/get_my_reviews.py
+++ /dev/null
@@ -1,55 +0,0 @@
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.review_gateway import ReviewGateway
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.common.constants.exceptions import (
- GET_MY_REVIEWS_ACCESS_DENIED,
-)
-from amdb.application.common.exception import ApplicationError
-from amdb.application.queries.get_my_reviews import (
- GetMyReviewsQuery,
- GetMyReviewsResult,
-)
-
-
-class GetMyReviewsHandler:
- def __init__(
- self,
- *,
- access_concern: AccessConcern,
- permissions_gateway: PermissionsGateway,
- review_gateway: ReviewGateway,
- identity_provider: IdentityProvider,
- ) -> None:
- self._access_concern = access_concern
- self._permissions_gateway = permissions_gateway
- self._review_gateway = review_gateway
- self._identity_provider = identity_provider
-
- def execute(self, query: GetMyReviewsQuery) -> GetMyReviewsResult:
- current_permissions = self._identity_provider.get_permissions()
- required_permissions = self._permissions_gateway.for_get_my_reviews()
- access = self._access_concern.authorize(
- current_permissions=current_permissions,
- required_permissions=required_permissions,
- )
- if not access:
- raise ApplicationError(GET_MY_REVIEWS_ACCESS_DENIED)
-
- current_user_id = self._identity_provider.get_user_id()
-
- reviews = self._review_gateway.list_with_user_id(
- user_id=current_user_id,
- limit=query.limit,
- offset=query.offset,
- )
- get_my_reviews_result = GetMyReviewsResult(
- reviews=reviews, # type: ignore
- review_count=len(reviews),
- )
-
- return get_my_reviews_result
diff --git a/src/amdb/application/query_handlers/get_rating.py b/src/amdb/application/query_handlers/get_rating.py
deleted file mode 100644
index 2c902ea..0000000
--- a/src/amdb/application/query_handlers/get_rating.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.rating_gateway import RatingGateway
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.queries.get_rating import GetRatingQuery, GetRatingResult
-from amdb.application.common.constants.exceptions import (
- GET_RATING_ACCESS_DENIED,
- RATING_DOES_NOT_EXIST,
-)
-from amdb.application.common.exception import ApplicationError
-
-
-class GetRatingHandler:
- def __init__(
- self,
- *,
- access_concern: AccessConcern,
- permissions_gateway: PermissionsGateway,
- rating_gateway: RatingGateway,
- identity_provider: IdentityProvider,
- ) -> None:
- self._access_concern = access_concern
- self._permissions_gateway = permissions_gateway
- self._rating_gateway = rating_gateway
- self._identity_provider = identity_provider
-
- def execute(self, query: GetRatingQuery) -> GetRatingResult:
- current_permissions = self._identity_provider.get_permissions()
- required_permissions = self._permissions_gateway.for_get_rating()
- access = self._access_concern.authorize(
- current_permissions=current_permissions,
- required_permissions=required_permissions,
- )
- if not access:
- raise ApplicationError(GET_RATING_ACCESS_DENIED)
-
- rating = self._rating_gateway.with_id(query.rating_id)
- if not rating:
- raise ApplicationError(RATING_DOES_NOT_EXIST)
-
- get_rating_result = GetRatingResult(
- user_id=rating.user_id,
- movie_id=rating.movie_id,
- value=rating.value,
- created_at=rating.created_at,
- )
-
- return get_rating_result
diff --git a/src/amdb/application/query_handlers/get_review.py b/src/amdb/application/query_handlers/get_review.py
deleted file mode 100644
index 43cf7de..0000000
--- a/src/amdb/application/query_handlers/get_review.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.review_gateway import ReviewGateway
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.common.constants.exceptions import (
- GET_REVIEW_ACCESS_DENIED,
- REVIEW_DOES_NOT_EXIST,
-)
-from amdb.application.common.exception import ApplicationError
-from amdb.application.queries.get_review import GetReviewQuery, GetReviewResult
-
-
-class GetReviewHandler:
- def __init__(
- self,
- *,
- access_concern: AccessConcern,
- permissions_gateway: PermissionsGateway,
- review_gateway: ReviewGateway,
- identity_provider: IdentityProvider,
- ) -> None:
- self._access_concern = access_concern
- self._permissions_gateway = permissions_gateway
- self._review_gateway = review_gateway
- self._identity_provider = identity_provider
-
- def execute(self, query: GetReviewQuery) -> GetReviewResult:
- current_permissions = self._identity_provider.get_permissions()
- required_permissions = self._permissions_gateway.for_get_review()
- access = self._access_concern.authorize(
- current_permissions=current_permissions,
- required_permissions=required_permissions,
- )
- if not access:
- raise ApplicationError(GET_REVIEW_ACCESS_DENIED)
-
- review = self._review_gateway.with_id(query.review_id)
- if not review:
- raise ApplicationError(REVIEW_DOES_NOT_EXIST)
-
- get_review_result = GetReviewResult(
- user_id=review.user_id,
- movie_id=review.movie_id,
- title=review.title,
- content=review.content,
- type=review.type,
- created_at=review.created_at,
- )
-
- return get_review_result
diff --git a/src/amdb/application/query_handlers/login.py b/src/amdb/application/query_handlers/login.py
index 47a01e1..be4aa6f 100644
--- a/src/amdb/application/query_handlers/login.py
+++ b/src/amdb/application/query_handlers/login.py
@@ -2,11 +2,9 @@
from amdb.domain.entities.user import UserId
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.user_gateway import UserGateway
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.password_manager import PasswordManager
+from amdb.application.common.gateways.user import UserGateway
+from amdb.application.common.gateways.permissions import PermissionsGateway
+from amdb.application.common.password_manager import PasswordManager
from amdb.application.common.constants.exceptions import (
LOGIN_ACCESS_DENIED,
USER_DOES_NOT_EXIST,
diff --git a/src/amdb/application/query_handlers/non_detailed_movies.py b/src/amdb/application/query_handlers/non_detailed_movies.py
new file mode 100644
index 0000000..8d7c58d
--- /dev/null
+++ b/src/amdb/application/query_handlers/non_detailed_movies.py
@@ -0,0 +1,43 @@
+from amdb.domain.services.access_concern import AccessConcern
+from amdb.application.common.gateways.permissions import PermissionsGateway
+from amdb.application.common.readers.movie import MovieViewModelReader
+from amdb.application.common.identity_provider import IdentityProvider
+from amdb.application.common.constants.exceptions import GET_MOVIES_ACCESS_DENIED
+from amdb.application.common.exception import ApplicationError
+from amdb.application.common.view_models.non_detailed_movie import NonDetailedMovieViewModel
+from amdb.application.queries.non_detailed_movies import GetNonDetailedMoviesQuery
+
+
+class GetNonDetailedMoviesHandler:
+ def __init__(
+ self,
+ *,
+ access_concern: AccessConcern,
+ permissions_gateway: PermissionsGateway,
+ movie_view_model_reader: MovieViewModelReader,
+ identity_provider: IdentityProvider,
+ ) -> None:
+ self._access_concern = access_concern
+ self._permissions_gateway = permissions_gateway
+ self._movie_view_model_reader = movie_view_model_reader
+ self._identity_provider = identity_provider
+
+ def execute(self, query: GetNonDetailedMoviesQuery) -> list[NonDetailedMovieViewModel]:
+ current_permissions = self._identity_provider.get_permissions()
+ required_permissions = self._permissions_gateway.for_get_movies()
+ access = self._access_concern.authorize(
+ current_permissions=current_permissions,
+ required_permissions=required_permissions,
+ )
+ if not access:
+ raise ApplicationError(GET_MOVIES_ACCESS_DENIED)
+
+ current_user_id = self._identity_provider.get_user_id()
+
+ non_detailed_movie_models = self._movie_view_model_reader.list_non_detailed(
+ current_user_id=current_user_id,
+ limit=query.limit,
+ offset=query.offset,
+ )
+
+ return non_detailed_movie_models
diff --git a/src/amdb/application/query_handlers/get_movie.py b/src/amdb/application/query_handlers/reviews.py
similarity index 54%
rename from src/amdb/application/query_handlers/get_movie.py
rename to src/amdb/application/query_handlers/reviews.py
index 0632db5..6c92913 100644
--- a/src/amdb/application/query_handlers/get_movie.py
+++ b/src/amdb/application/query_handlers/reviews.py
@@ -1,52 +1,51 @@
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.queries.get_movie import GetMovieQuery, GetMovieResult
+from amdb.application.common.gateways.permissions import PermissionsGateway
+from amdb.application.common.gateways.movie import MovieGateway
+from amdb.application.common.readers.review import ReviewViewModelReader
+from amdb.application.common.identity_provider import IdentityProvider
+from amdb.application.common.view_models.review import ReviewViewModel
from amdb.application.common.constants.exceptions import (
- GET_MOVIE_ACCESS_DENIED,
+ GET_REVIEWS_ACCESS_DENIED,
MOVIE_DOES_NOT_EXIST,
)
from amdb.application.common.exception import ApplicationError
+from amdb.application.queries.reviews import GetReviewsQuery
-class GetMovieHandler:
+class GetReviewsHandler:
def __init__(
self,
*,
access_concern: AccessConcern,
permissions_gateway: PermissionsGateway,
movie_gateway: MovieGateway,
+ review_view_model_reader: ReviewViewModelReader,
identity_provider: IdentityProvider,
) -> None:
self._access_concern = access_concern
self._permissions_gateway = permissions_gateway
self._movie_gateway = movie_gateway
+ self._review_view_model_reader = review_view_model_reader
self._identity_provider = identity_provider
- def execute(self, query: GetMovieQuery) -> GetMovieResult:
+ def execute(self, query: GetReviewsQuery) -> list[ReviewViewModel]:
current_permissions = self._identity_provider.get_permissions()
- required_permissions = self._permissions_gateway.for_get_movie()
+ required_permissions = self._permissions_gateway.for_get_reviews()
access = self._access_concern.authorize(
current_permissions=current_permissions,
required_permissions=required_permissions,
)
if not access:
- raise ApplicationError(GET_MOVIE_ACCESS_DENIED)
+ raise ApplicationError(GET_REVIEWS_ACCESS_DENIED)
movie = self._movie_gateway.with_id(query.movie_id)
if not movie:
raise ApplicationError(MOVIE_DOES_NOT_EXIST)
- get_movie_result = GetMovieResult(
- title=movie.title,
- release_date=movie.release_date,
- rating=movie.rating,
- rating_count=movie.rating_count,
+ review_view_models = self._review_view_model_reader.list(
+ movie_id=query.movie_id,
+ limit=query.limit,
+ offset=query.offset,
)
- return get_movie_result
+ return review_view_models
diff --git a/tests/unit/application/command_handlers/test_create_movie.py b/tests/unit/application/command_handlers/test_create_movie.py
index 5d017b9..3486bd8 100644
--- a/tests/unit/application/command_handlers/test_create_movie.py
+++ b/tests/unit/application/command_handlers/test_create_movie.py
@@ -5,14 +5,10 @@
from amdb.domain.services.access_concern import AccessConcern
from amdb.domain.services.create_movie import CreateMovie
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
+from amdb.application.common.gateways.permissions import PermissionsGateway
+from amdb.application.common.gateways.movie import MovieGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.commands.create_movie import CreateMovieCommand
from amdb.application.command_handlers.create_movie import CreateMovieHandler
from amdb.application.common.constants.exceptions import (
diff --git a/tests/unit/application/command_handlers/test_delete_movie.py b/tests/unit/application/command_handlers/test_delete_movie.py
index 6821f40..be66daa 100644
--- a/tests/unit/application/command_handlers/test_delete_movie.py
+++ b/tests/unit/application/command_handlers/test_delete_movie.py
@@ -6,16 +6,12 @@
from amdb.domain.entities.movie import MovieId, Movie
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.rating_gateway import RatingGateway
-from amdb.application.common.interfaces.review_gateway import ReviewGateway
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
+from amdb.application.common.gateways.permissions import PermissionsGateway
+from amdb.application.common.gateways.movie import MovieGateway
+from amdb.application.common.gateways.rating import RatingGateway
+from amdb.application.common.gateways.review import ReviewGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.commands.delete_movie import DeleteMovieCommand
from amdb.application.command_handlers.delete_movie import DeleteMovieHandler
from amdb.application.common.constants.exceptions import (
diff --git a/tests/unit/application/command_handlers/test_rate_movie.py b/tests/unit/application/command_handlers/test_rate_movie.py
index 376a737..559f0ef 100644
--- a/tests/unit/application/command_handlers/test_rate_movie.py
+++ b/tests/unit/application/command_handlers/test_rate_movie.py
@@ -11,16 +11,12 @@
from amdb.domain.services.rate_movie import RateMovie
from amdb.domain.constants.exceptions import INVALID_RATING_VALUE
from amdb.domain.exception import DomainError
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.user_gateway import UserGateway
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.rating_gateway import RatingGateway
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
+from amdb.application.common.gateways.permissions import PermissionsGateway
+from amdb.application.common.gateways.user import UserGateway
+from amdb.application.common.gateways.movie import MovieGateway
+from amdb.application.common.gateways.rating import RatingGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.commands.rate_movie import RateMovieCommand
from amdb.application.command_handlers.rate_movie import RateMovieHandler
from amdb.application.common.constants.exceptions import (
diff --git a/tests/unit/application/command_handlers/test_register_user.py b/tests/unit/application/command_handlers/test_register_user.py
index e7aa115..7c3dda9 100644
--- a/tests/unit/application/command_handlers/test_register_user.py
+++ b/tests/unit/application/command_handlers/test_register_user.py
@@ -3,12 +3,10 @@
from amdb.domain.entities.user import UserId, User
from amdb.domain.services.create_user import CreateUser
-from amdb.application.common.interfaces.user_gateway import UserGateway
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.password_manager import PasswordManager
+from amdb.application.common.gateways.user import UserGateway
+from amdb.application.common.gateways.permissions import PermissionsGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.password_manager import PasswordManager
from amdb.application.commands.register_user import RegisterUserCommand
from amdb.application.command_handlers.register_user import RegisterUserHandler
from amdb.application.common.constants.exceptions import (
diff --git a/tests/unit/application/command_handlers/test_review_movie.py b/tests/unit/application/command_handlers/test_review_movie.py
index 20d494d..fd5982d 100644
--- a/tests/unit/application/command_handlers/test_review_movie.py
+++ b/tests/unit/application/command_handlers/test_review_movie.py
@@ -9,16 +9,12 @@
from amdb.domain.entities.review import ReviewId, ReviewType, Review
from amdb.domain.services.access_concern import AccessConcern
from amdb.domain.services.review_movie import ReviewMovie
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.user_gateway import UserGateway
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.review_gateway import ReviewGateway
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
+from amdb.application.common.gateways.permissions import PermissionsGateway
+from amdb.application.common.gateways.user import UserGateway
+from amdb.application.common.gateways.movie import MovieGateway
+from amdb.application.common.gateways.review import ReviewGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.commands.review_movie import ReviewMovieCommand
from amdb.application.command_handlers.review_movie import ReviewMovieHandler
from amdb.application.common.constants.exceptions import (
diff --git a/tests/unit/application/command_handlers/test_unrate_movie.py b/tests/unit/application/command_handlers/test_unrate_movie.py
index 45b5a9f..c3ba201 100644
--- a/tests/unit/application/command_handlers/test_unrate_movie.py
+++ b/tests/unit/application/command_handlers/test_unrate_movie.py
@@ -9,16 +9,12 @@
from amdb.domain.entities.rating import RatingId, Rating
from amdb.domain.services.access_concern import AccessConcern
from amdb.domain.services.unrate_movie import UnrateMovie
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.user_gateway import UserGateway
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.rating_gateway import RatingGateway
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
+from amdb.application.common.gateways.permissions import PermissionsGateway
+from amdb.application.common.gateways.user import UserGateway
+from amdb.application.common.gateways.movie import MovieGateway
+from amdb.application.common.gateways.rating import RatingGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.commands.unrate_movie import UnrateMovieCommand
from amdb.application.command_handlers.unrate_movie import UnrateMovieHandler
from amdb.application.common.constants.exceptions import (
diff --git a/tests/unit/application/query_handlers/test_detailed_movie.py b/tests/unit/application/query_handlers/test_detailed_movie.py
new file mode 100644
index 0000000..8331c92
--- /dev/null
+++ b/tests/unit/application/query_handlers/test_detailed_movie.py
@@ -0,0 +1,174 @@
+from unittest.mock import Mock
+from datetime import date, datetime, timezone
+
+import pytest
+from uuid_extensions import uuid7
+
+from amdb.domain.entities.user import UserId, User
+from amdb.domain.entities.movie import MovieId, Movie
+from amdb.domain.entities.rating import RatingId, Rating
+from amdb.domain.entities.review import ReviewId, ReviewType, Review
+from amdb.domain.services.access_concern import AccessConcern
+from amdb.application.common.gateways.permissions import PermissionsGateway
+from amdb.application.common.gateways.user import UserGateway
+from amdb.application.common.gateways.movie import MovieGateway
+from amdb.application.common.gateways.rating import RatingGateway
+from amdb.application.common.gateways.review import ReviewGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.readers.movie import MovieViewModelReader
+from amdb.application.common.identity_provider import IdentityProvider
+from amdb.application.common.view_models.detailed_movie import (
+ UserRating,
+ UserReview,
+ DetailedMovieViewModel,
+)
+from amdb.application.queries.detailed_movie import GetDetailedMovieQuery
+from amdb.application.query_handlers.detailed_movie import GetDetailedMovieHandler
+from amdb.application.common.constants.exceptions import (
+ GET_MOVIE_ACCESS_DENIED,
+ MOVIE_DOES_NOT_EXIST,
+)
+from amdb.application.common.exception import ApplicationError
+
+
+@pytest.fixture
+def identity_provider_with_correct_permissions(
+ permissions_gateway: PermissionsGateway,
+) -> IdentityProvider:
+ identity_provider = Mock()
+
+ correct_permissions = permissions_gateway.for_get_movie()
+ identity_provider.get_permissions = Mock(return_value=correct_permissions)
+
+ return identity_provider
+
+
+
+def test_get_detailed_movie(
+ user_gateway: UserGateway,
+ movie_gateway: MovieGateway,
+ rating_gateway: RatingGateway,
+ review_gateway: ReviewGateway,
+ unit_of_work: UnitOfWork,
+ permissions_gateway: PermissionsGateway,
+ movie_view_model_reader: MovieViewModelReader,
+ identity_provider_with_correct_permissions: IdentityProvider,
+):
+ user = User(
+ id=UserId(uuid7()),
+ name="John Doe",
+ )
+ user_gateway.save(user)
+
+ movie = Movie(
+ id=MovieId(uuid7()),
+ title="Matrix",
+ release_date=date(1999, 3, 31),
+ rating=8,
+ rating_count=1,
+ )
+ movie_gateway.save(movie)
+
+ rating = Rating(
+ id=RatingId(uuid7()),
+ movie_id=movie.id,
+ user_id=user.id,
+ value=8,
+ created_at=datetime.now(timezone.utc),
+ )
+ rating_gateway.save(rating)
+
+ review = Review(
+ id=ReviewId(uuid7()),
+ user_id=user.id,
+ movie_id=movie.id,
+ title="Not bad",
+ content="Great soundtrack",
+ type=ReviewType.POSITIVE,
+ created_at=datetime.now(timezone.utc),
+ )
+ review_gateway.save(review)
+
+ unit_of_work.commit()
+
+ identity_provider_with_correct_permissions.get_user_id = Mock(
+ return_value=user.id,
+ )
+
+ get_detailed_movie_query = GetDetailedMovieQuery(
+ movie_id=movie.id,
+ )
+ get_detailed_movie_handler = GetDetailedMovieHandler(
+ access_concern=AccessConcern(),
+ permissions_gateway=permissions_gateway,
+ movie_view_model_reader=movie_view_model_reader,
+ identity_provider=identity_provider_with_correct_permissions,
+ )
+
+ expected_result = DetailedMovieViewModel(
+ id=movie.id,
+ title=movie.title,
+ release_date=movie.release_date,
+ rating_count=movie.rating_count,
+ user_rating=UserRating(
+ id=rating.id,
+ value=rating.value,
+ created_at=rating.created_at,
+ ),
+ user_review=UserReview(
+ id=review.id,
+ title=review.title,
+ content=review.content,
+ type=review.type,
+ created_at=review.type,
+ )
+ )
+ result = get_detailed_movie_handler.execute(get_detailed_movie_query)
+
+ assert expected_result == result
+
+
+def test_get_detailed_movie_should_raise_error_when_access_is_denied(
+ permissions_gateway: PermissionsGateway,
+ movie_view_model_reader: MovieViewModelReader,
+ identity_provider_with_incorrect_permissions: IdentityProvider,
+):
+ get_detailed_movie_query = GetDetailedMovieQuery(
+ movie_id=MovieId(uuid7()),
+ )
+ get_detailed_movie_handler = GetDetailedMovieHandler(
+ access_concern=AccessConcern(),
+ permissions_gateway=permissions_gateway,
+ movie_view_model_reader=movie_view_model_reader,
+ identity_provider=identity_provider_with_incorrect_permissions,
+ )
+
+ with pytest.raises(ApplicationError) as error:
+ get_detailed_movie_handler.execute(get_detailed_movie_query)
+
+ assert error.value.message == GET_MOVIE_ACCESS_DENIED
+
+
+def test_get_detailed_movie_should_raise_error_when_movie_does_not_exist(
+ permissions_gateway: PermissionsGateway,
+ movie_view_model_reader: MovieViewModelReader,
+ identity_provider_with_correct_permissions: IdentityProvider,
+):
+ identity_provider_with_correct_permissions.get_user_id = Mock(
+ return_value=UserId(uuid7()),
+ )
+
+ get_detailed_movie_query = GetDetailedMovieQuery(
+ movie_id=MovieId(uuid7()),
+ )
+ get_detailed_movie_handler = GetDetailedMovieHandler(
+ access_concern=AccessConcern(),
+ permissions_gateway=permissions_gateway,
+ movie_view_model_reader=movie_view_model_reader,
+ identity_provider=identity_provider_with_correct_permissions,
+ )
+
+ with pytest.raises(ApplicationError) as error:
+ get_detailed_movie_handler.execute(get_detailed_movie_query)
+
+ assert error.value.message == MOVIE_DOES_NOT_EXIST
diff --git a/tests/unit/application/query_handlers/test_get_movie.py b/tests/unit/application/query_handlers/test_get_movie.py
deleted file mode 100644
index c87297c..0000000
--- a/tests/unit/application/query_handlers/test_get_movie.py
+++ /dev/null
@@ -1,115 +0,0 @@
-from unittest.mock import Mock
-from datetime import date
-
-import pytest
-from uuid_extensions import uuid7
-
-from amdb.domain.entities.movie import MovieId, Movie
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.queries.get_movie import GetMovieQuery, GetMovieResult
-from amdb.application.query_handlers.get_movie import GetMovieHandler
-from amdb.application.common.constants.exceptions import (
- GET_MOVIE_ACCESS_DENIED,
- MOVIE_DOES_NOT_EXIST,
-)
-from amdb.application.common.exception import ApplicationError
-
-
-@pytest.fixture
-def identity_provider_with_correct_permissions(
- permissions_gateway: PermissionsGateway,
-) -> IdentityProvider:
- identity_provider = Mock()
-
- correct_permissions = permissions_gateway.for_get_movie()
- identity_provider.get_permissions = Mock(return_value=correct_permissions)
-
- return identity_provider
-
-
-def test_get_movie(
- permissions_gateway: PermissionsGateway,
- movie_gateway: MovieGateway,
- unit_of_work: UnitOfWork,
- identity_provider_with_correct_permissions: IdentityProvider,
-):
- movie = Movie(
- id=MovieId(uuid7()),
- title="Matrix",
- release_date=date(1999, 3, 31),
- rating=0,
- rating_count=0,
- )
- movie_gateway.save(movie)
-
- unit_of_work.commit()
-
- get_movie_query = GetMovieQuery(
- movie_id=movie.id,
- )
- get_movie_handler = GetMovieHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- movie_gateway=movie_gateway,
- identity_provider=identity_provider_with_correct_permissions,
- )
-
- get_movie_result = get_movie_handler.execute(get_movie_query)
- expected_get_movie_result = GetMovieResult(
- title=movie.title,
- release_date=movie.release_date,
- rating=movie.rating,
- rating_count=movie.rating_count,
- )
-
- assert get_movie_result == expected_get_movie_result
-
-
-def test_get_movie_should_raise_error_when_access_is_denied(
- permissions_gateway: PermissionsGateway,
- movie_gateway: MovieGateway,
- identity_provider_with_incorrect_permissions: IdentityProvider,
-):
- get_movie_query = GetMovieQuery(
- movie_id=MovieId(uuid7()),
- )
- get_movie_handler = GetMovieHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- movie_gateway=movie_gateway,
- identity_provider=identity_provider_with_incorrect_permissions,
- )
-
- with pytest.raises(ApplicationError) as error:
- get_movie_handler.execute(get_movie_query)
-
- assert error.value.message == GET_MOVIE_ACCESS_DENIED
-
-
-def test_get_movie_should_raise_error_when_movie_does_not_exist(
- permissions_gateway: PermissionsGateway,
- movie_gateway: MovieGateway,
- identity_provider_with_correct_permissions: IdentityProvider,
-):
- get_movie_query = GetMovieQuery(
- movie_id=MovieId(uuid7()),
- )
- get_movie_handler = GetMovieHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- movie_gateway=movie_gateway,
- identity_provider=identity_provider_with_correct_permissions,
- )
-
- with pytest.raises(ApplicationError) as error:
- get_movie_handler.execute(get_movie_query)
-
- assert error.value.message == MOVIE_DOES_NOT_EXIST
diff --git a/tests/unit/application/query_handlers/test_get_movie_ratings.py b/tests/unit/application/query_handlers/test_get_movie_ratings.py
deleted file mode 100644
index b824a16..0000000
--- a/tests/unit/application/query_handlers/test_get_movie_ratings.py
+++ /dev/null
@@ -1,156 +0,0 @@
-from unittest.mock import Mock
-from datetime import date, datetime, timezone
-
-import pytest
-from uuid_extensions import uuid7
-
-from amdb.domain.entities.user import UserId, User
-from amdb.domain.entities.movie import MovieId, Movie
-from amdb.domain.entities.rating import RatingId, Rating
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.user_gateway import UserGateway
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.rating_gateway import RatingGateway
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.queries.get_movie_ratings import (
- GetMovieRatingsQuery,
- GetMovieRatingsResult,
-)
-from amdb.application.query_handlers.get_movie_ratings import (
- GetMovieRatingsHandler,
-)
-from amdb.application.common.constants.exceptions import (
- GET_MOVIE_RATINGS_ACCESS_DENIED,
- MOVIE_DOES_NOT_EXIST,
-)
-from amdb.application.common.exception import ApplicationError
-
-
-@pytest.fixture
-def identity_provider_with_correct_permissions(
- permissions_gateway: PermissionsGateway,
-) -> IdentityProvider:
- identity_provider = Mock()
-
- correct_permissions = permissions_gateway.for_get_movie_ratings()
- identity_provider.get_permissions = Mock(return_value=correct_permissions)
-
- return identity_provider
-
-
-def test_get_movie_ratings(
- user_gateway: UserGateway,
- movie_gateway: MovieGateway,
- rating_gateway: RatingGateway,
- permissions_gateway: PermissionsGateway,
- unit_of_work: UnitOfWork,
- identity_provider_with_correct_permissions: IdentityProvider,
-):
- user = User(
- id=UserId(uuid7()),
- name="John Doe",
- )
- user_gateway.save(user)
-
- movie = Movie(
- id=MovieId(uuid7()),
- title="Matrix",
- release_date=date(1999, 3, 31),
- rating=10,
- rating_count=1,
- )
- movie_gateway.save(movie)
-
- rating = Rating(
- id=RatingId(uuid7()),
- movie_id=movie.id,
- user_id=user.id,
- value=10,
- created_at=datetime.now(timezone.utc),
- )
- rating_gateway.save(rating)
-
- unit_of_work.commit()
-
- identity_provider_with_correct_permissions.get_user_id = Mock(
- return_value=user.id,
- )
-
- get_movie_ratings_query = GetMovieRatingsQuery(
- movie_id=movie.id,
- limit=10,
- offset=0,
- )
- get_movie_ratings_handler = GetMovieRatingsHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- movie_gateway=movie_gateway,
- rating_gateway=rating_gateway,
- identity_provider=identity_provider_with_correct_permissions,
- )
-
- get_movie_ratings_result = get_movie_ratings_handler.execute(
- get_movie_ratings_query
- )
- expected_get_movie_ratings_result = GetMovieRatingsResult(
- ratings=[rating],
- rating_count=1,
- )
-
- assert get_movie_ratings_result == expected_get_movie_ratings_result
-
-
-def test_get_movie_ratings_should_raise_error_when_access_is_denied(
- movie_gateway: MovieGateway,
- rating_gateway: RatingGateway,
- permissions_gateway: PermissionsGateway,
- identity_provider_with_incorrect_permissions: IdentityProvider,
-):
- get_movie_ratings_query = GetMovieRatingsQuery(
- movie_id=MovieId(uuid7()),
- limit=10,
- offset=0,
- )
- get_movie_ratings_handler = GetMovieRatingsHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- movie_gateway=movie_gateway,
- rating_gateway=rating_gateway,
- identity_provider=identity_provider_with_incorrect_permissions,
- )
-
- with pytest.raises(ApplicationError) as error:
- get_movie_ratings_handler.execute(get_movie_ratings_query)
-
- assert error.value.message == GET_MOVIE_RATINGS_ACCESS_DENIED
-
-
-def test_get_movie_ratings_should_raise_error_when_movie_does_not_exist(
- movie_gateway: MovieGateway,
- rating_gateway: RatingGateway,
- permissions_gateway: PermissionsGateway,
- identity_provider_with_correct_permissions: IdentityProvider,
-):
- get_movie_ratings_query = GetMovieRatingsQuery(
- movie_id=MovieId(uuid7()),
- limit=10,
- offset=0,
- )
- get_movie_ratings_handler = GetMovieRatingsHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- movie_gateway=movie_gateway,
- rating_gateway=rating_gateway,
- identity_provider=identity_provider_with_correct_permissions,
- )
-
- with pytest.raises(ApplicationError) as error:
- get_movie_ratings_handler.execute(get_movie_ratings_query)
-
- assert error.value.message == MOVIE_DOES_NOT_EXIST
diff --git a/tests/unit/application/query_handlers/test_get_movie_reviews.py b/tests/unit/application/query_handlers/test_get_movie_reviews.py
deleted file mode 100644
index 3aae0d8..0000000
--- a/tests/unit/application/query_handlers/test_get_movie_reviews.py
+++ /dev/null
@@ -1,154 +0,0 @@
-from unittest.mock import Mock
-from datetime import date, datetime, timezone
-
-import pytest
-from uuid_extensions import uuid7
-
-from amdb.domain.entities.user import UserId, User
-from amdb.domain.entities.movie import MovieId, Movie
-from amdb.domain.entities.review import ReviewId, ReviewType, Review
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.user_gateway import UserGateway
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.review_gateway import ReviewGateway
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.queries.get_movie_reviews import (
- GetMovieReviewsQuery,
- GetMovieReviewsResult,
-)
-from amdb.application.query_handlers.get_movie_reviews import (
- GetMovieReviewsHandler,
-)
-from amdb.application.common.constants.exceptions import (
- GET_MOVIE_REVIEWS_ACCESS_DENIED,
- MOVIE_DOES_NOT_EXIST,
-)
-from amdb.application.common.exception import ApplicationError
-
-
-@pytest.fixture
-def identity_provider_with_correct_permissions(
- permissions_gateway: PermissionsGateway,
-) -> IdentityProvider:
- identity_provider = Mock()
-
- correct_permissions = permissions_gateway.for_get_movie_reviews()
- identity_provider.get_permissions = Mock(return_value=correct_permissions)
-
- return identity_provider
-
-
-def test_get_movie_reviews(
- permissions_gateway: PermissionsGateway,
- user_gateway: UserGateway,
- movie_gateway: MovieGateway,
- review_gateway: ReviewGateway,
- unit_of_work: UnitOfWork,
- identity_provider_with_correct_permissions: IdentityProvider,
-):
- user = User(
- id=UserId(uuid7()),
- name="John Doe",
- )
- user_gateway.save(user)
-
- movie = Movie(
- id=MovieId(uuid7()),
- title="Gone girl",
- release_date=date(2014, 10, 3),
- rating=0,
- rating_count=0,
- )
- movie_gateway.save(movie)
-
- review = Review(
- id=ReviewId(uuid7()),
- user_id=user.id,
- movie_id=movie.id,
- title="Masterpice",
- content="Extremely underrated",
- type=ReviewType.POSITIVE,
- created_at=datetime.now(timezone.utc),
- )
- review_gateway.save(review)
-
- unit_of_work.commit()
-
- get_movie_reviews_query = GetMovieReviewsQuery(
- movie_id=movie.id,
- limit=10,
- offset=0,
- )
- get_movie_reviews_handler = GetMovieReviewsHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- movie_gateway=movie_gateway,
- review_gateway=review_gateway,
- identity_provider=identity_provider_with_correct_permissions,
- )
-
- get_movie_reviews_result = get_movie_reviews_handler.execute(
- get_movie_reviews_query
- )
- expected_get_movie_reviews_result = GetMovieReviewsResult(
- reviews=[review],
- review_count=1,
- )
-
- assert get_movie_reviews_result == expected_get_movie_reviews_result
-
-
-def test_get_movie_reviews_should_raise_error_when_access_is_denied(
- permissions_gateway: PermissionsGateway,
- movie_gateway: MovieGateway,
- review_gateway: ReviewGateway,
- identity_provider_with_incorrect_permissions: IdentityProvider,
-):
- get_movie_reviews_query = GetMovieReviewsQuery(
- movie_id=MovieId(uuid7()),
- limit=10,
- offset=0,
- )
- get_movie_reviews_handler = GetMovieReviewsHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- movie_gateway=movie_gateway,
- review_gateway=review_gateway,
- identity_provider=identity_provider_with_incorrect_permissions,
- )
-
- with pytest.raises(ApplicationError) as error:
- get_movie_reviews_handler.execute(get_movie_reviews_query)
-
- assert error.value.message == GET_MOVIE_REVIEWS_ACCESS_DENIED
-
-
-def test_get_movie_reviews_should_raise_error_when_movie_does_not_exist(
- permissions_gateway: PermissionsGateway,
- movie_gateway: MovieGateway,
- review_gateway: ReviewGateway,
- identity_provider_with_correct_permissions: IdentityProvider,
-):
- get_movie_reviews_query = GetMovieReviewsQuery(
- movie_id=MovieId(uuid7()),
- limit=10,
- offset=0,
- )
- get_movie_reviews_handler = GetMovieReviewsHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- movie_gateway=movie_gateway,
- review_gateway=review_gateway,
- identity_provider=identity_provider_with_correct_permissions,
- )
-
- with pytest.raises(ApplicationError) as error:
- get_movie_reviews_handler.execute(get_movie_reviews_query)
-
- assert error.value.message == MOVIE_DOES_NOT_EXIST
diff --git a/tests/unit/application/query_handlers/test_get_movies.py b/tests/unit/application/query_handlers/test_get_movies.py
deleted file mode 100644
index a648328..0000000
--- a/tests/unit/application/query_handlers/test_get_movies.py
+++ /dev/null
@@ -1,111 +0,0 @@
-from unittest.mock import Mock
-from datetime import date
-
-import pytest
-from uuid_extensions import uuid7
-
-from amdb.domain.entities.movie import MovieId, Movie
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.queries.get_movies import GetMoviesQuery, GetMoviesResult
-from amdb.application.query_handlers.get_movies import GetMoviesHandler
-from amdb.application.common.constants.exceptions import (
- GET_MOVIES_ACCESS_DENIED,
-)
-from amdb.application.common.exception import ApplicationError
-
-
-@pytest.fixture
-def identity_provider_with_correct_permissions(
- permissions_gateway: PermissionsGateway,
-) -> IdentityProvider:
- identity_provider = Mock()
-
- correct_permissions = permissions_gateway.for_get_movies()
- identity_provider.get_permissions = Mock(return_value=correct_permissions)
-
- return identity_provider
-
-
-def test_get_movies(
- permissions_gateway: PermissionsGateway,
- movie_gateway: MovieGateway,
- unit_of_work: UnitOfWork,
- identity_provider_with_correct_permissions: IdentityProvider,
-):
- movie1 = Movie(
- id=MovieId(uuid7()),
- title="Matrix",
- release_date=date(1999, 3, 31),
- rating=0,
- rating_count=0,
- )
- movie_gateway.save(movie1)
-
- movie2 = Movie(
- id=MovieId(uuid7()),
- title="There Will Be Blood",
- release_date=date(2007, 9, 26),
- rating=0,
- rating_count=0,
- )
- movie_gateway.save(movie2)
-
- movie3 = Movie(
- id=MovieId(uuid7()),
- title="Mulholland Drive",
- release_date=date(2001, 5, 16),
- rating=0,
- rating_count=0,
- )
- movie_gateway.save(movie3)
-
- unit_of_work.commit()
-
- get_movies_query = GetMoviesQuery(
- limit=10,
- offset=1,
- )
- get_movies_handler = GetMoviesHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- movie_gateway=movie_gateway,
- identity_provider=identity_provider_with_correct_permissions,
- )
-
- get_movies_result = get_movies_handler.execute(get_movies_query)
- expected_get_movies_result = GetMoviesResult(
- movies=[movie2, movie3],
- movie_count=2,
- )
-
- assert get_movies_result == expected_get_movies_result
-
-
-def test_get_movies_should_raise_error_when_access_is_denied(
- permissions_gateway: PermissionsGateway,
- movie_gateway: MovieGateway,
- identity_provider_with_incorrect_permissions: IdentityProvider,
-):
- get_movies_query = GetMoviesQuery(
- limit=10,
- offset=1,
- )
- get_movies_handler = GetMoviesHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- movie_gateway=movie_gateway,
- identity_provider=identity_provider_with_incorrect_permissions,
- )
-
- with pytest.raises(ApplicationError) as error:
- get_movies_handler.execute(get_movies_query)
-
- assert error.value.message == GET_MOVIES_ACCESS_DENIED
diff --git a/tests/unit/application/query_handlers/test_get_my_reviews.py b/tests/unit/application/query_handlers/test_get_my_reviews.py
deleted file mode 100644
index ac4bafd..0000000
--- a/tests/unit/application/query_handlers/test_get_my_reviews.py
+++ /dev/null
@@ -1,129 +0,0 @@
-from unittest.mock import Mock
-from datetime import date, datetime, timezone
-
-import pytest
-from uuid_extensions import uuid7
-
-from amdb.domain.entities.user import UserId, User
-from amdb.domain.entities.movie import MovieId, Movie
-from amdb.domain.entities.review import ReviewId, ReviewType, Review
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.user_gateway import UserGateway
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.review_gateway import ReviewGateway
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.queries.get_my_reviews import (
- GetMyReviewsQuery,
- GetMyReviewsResult,
-)
-from amdb.application.query_handlers.get_my_reviews import GetMyReviewsHandler
-from amdb.application.common.constants.exceptions import (
- GET_MY_REVIEWS_ACCESS_DENIED,
-)
-from amdb.application.common.exception import ApplicationError
-
-
-@pytest.fixture
-def identity_provider_with_correct_permissions(
- permissions_gateway: PermissionsGateway,
-) -> IdentityProvider:
- identity_provider = Mock()
-
- correct_permissions = permissions_gateway.for_get_my_reviews()
- identity_provider.get_permissions = Mock(return_value=correct_permissions)
-
- return identity_provider
-
-
-def test_get_my_reviews(
- permissions_gateway: PermissionsGateway,
- user_gateway: UserGateway,
- movie_gateway: MovieGateway,
- review_gateway: ReviewGateway,
- unit_of_work: UnitOfWork,
- identity_provider_with_correct_permissions: IdentityProvider,
-):
- user = User(
- id=UserId(uuid7()),
- name="John Doe",
- )
- user_gateway.save(user)
-
- movie = Movie(
- id=MovieId(uuid7()),
- title="Gone girl",
- release_date=date(2014, 10, 3),
- rating=0,
- rating_count=0,
- )
- movie_gateway.save(movie)
-
- review = Review(
- id=ReviewId(uuid7()),
- user_id=user.id,
- movie_id=movie.id,
- title="Masterpice",
- content="Extremely underrated",
- type=ReviewType.POSITIVE,
- created_at=datetime.now(timezone.utc),
- )
- review_gateway.save(review)
-
- unit_of_work.commit()
-
- identity_provider_with_correct_permissions.get_user_id = Mock(
- return_value=user.id,
- )
-
- get_my_reviews_query = GetMyReviewsQuery(
- limit=10,
- offset=0,
- )
- get_my_reviews_handler = GetMyReviewsHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- review_gateway=review_gateway,
- identity_provider=identity_provider_with_correct_permissions,
- )
-
- get_my_reviews_result = get_my_reviews_handler.execute(
- get_my_reviews_query
- )
- expected_get_my_reviews_result = GetMyReviewsResult(
- reviews=[review],
- review_count=1,
- )
-
- assert get_my_reviews_result == expected_get_my_reviews_result
-
-
-def test_get_my_reviews_should_raise_error_when_access_is_denied(
- permissions_gateway: PermissionsGateway,
- review_gateway: ReviewGateway,
- identity_provider_with_incorrect_permissions: IdentityProvider,
-):
- get_my_reviews_query = GetMyReviewsQuery(
- limit=10,
- offset=0,
- )
- get_my_reviews_handler = GetMyReviewsHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- review_gateway=review_gateway,
- identity_provider=identity_provider_with_incorrect_permissions,
- )
-
- identity_provider_with_incorrect_permissions.get_user_id = Mock(
- return_value=UserId(uuid7()),
- )
-
- with pytest.raises(ApplicationError) as error:
- get_my_reviews_handler.execute(get_my_reviews_query)
-
- assert error.value.message == GET_MY_REVIEWS_ACCESS_DENIED
diff --git a/tests/unit/application/query_handlers/test_get_rating.py b/tests/unit/application/query_handlers/test_get_rating.py
deleted file mode 100644
index 4f8be6f..0000000
--- a/tests/unit/application/query_handlers/test_get_rating.py
+++ /dev/null
@@ -1,140 +0,0 @@
-from unittest.mock import Mock
-from datetime import date, datetime, timezone
-
-import pytest
-from uuid_extensions import uuid7
-
-from amdb.domain.entities.user import UserId, User
-from amdb.domain.entities.movie import MovieId, Movie
-from amdb.domain.entities.rating import RatingId, Rating
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.user_gateway import UserGateway
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.rating_gateway import RatingGateway
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.queries.get_rating import GetRatingQuery, GetRatingResult
-from amdb.application.query_handlers.get_rating import GetRatingHandler
-from amdb.application.common.constants.exceptions import (
- GET_RATING_ACCESS_DENIED,
- RATING_DOES_NOT_EXIST,
-)
-from amdb.application.common.exception import ApplicationError
-
-
-@pytest.fixture
-def identity_provider_with_correct_permissions(
- permissions_gateway: PermissionsGateway,
-) -> IdentityProvider:
- identity_provider = Mock()
-
- correct_permissions = permissions_gateway.for_get_rating()
- identity_provider.get_permissions = Mock(return_value=correct_permissions)
-
- return identity_provider
-
-
-def test_get_rating(
- user_gateway: UserGateway,
- movie_gateway: MovieGateway,
- rating_gateway: RatingGateway,
- permissions_gateway: PermissionsGateway,
- unit_of_work: UnitOfWork,
- identity_provider_with_correct_permissions: IdentityProvider,
-):
- user = User(
- id=UserId(uuid7()),
- name="John Doe",
- )
- user_gateway.save(user)
-
- movie = Movie(
- id=MovieId(uuid7()),
- title="Matrix",
- release_date=date(1999, 3, 31),
- rating=10,
- rating_count=1,
- )
- movie_gateway.save(movie)
-
- rating = Rating(
- id=RatingId(uuid7()),
- movie_id=movie.id,
- user_id=user.id,
- value=10,
- created_at=datetime.now(timezone.utc),
- )
- rating_gateway.save(rating)
-
- unit_of_work.commit()
-
- identity_provider_with_correct_permissions.get_user_id = Mock(
- return_value=user.id,
- )
-
- get_rating_query = GetRatingQuery(
- rating_id=rating.id,
- )
- get_rating_handler = GetRatingHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- rating_gateway=rating_gateway,
- identity_provider=identity_provider_with_correct_permissions,
- )
-
- get_rating_result = get_rating_handler.execute(get_rating_query)
- expected_get_rating_result = GetRatingResult(
- user_id=rating.user_id,
- movie_id=rating.movie_id,
- value=rating.value,
- created_at=rating.created_at,
- )
-
- assert get_rating_result == expected_get_rating_result
-
-
-def test_get_rating_should_raise_error_when_access_is_denied(
- rating_gateway: RatingGateway,
- permissions_gateway: PermissionsGateway,
- identity_provider_with_incorrect_permissions: IdentityProvider,
-):
- get_rating_query = GetRatingQuery(
- rating_id=RatingId(uuid7()),
- )
- get_rating_handler = GetRatingHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- rating_gateway=rating_gateway,
- identity_provider=identity_provider_with_incorrect_permissions,
- )
-
- with pytest.raises(ApplicationError) as error:
- get_rating_handler.execute(get_rating_query)
-
- assert error.value.message == GET_RATING_ACCESS_DENIED
-
-
-def test_get_rating_should_raise_error_when_rating_does_not_exist(
- rating_gateway: RatingGateway,
- permissions_gateway: PermissionsGateway,
- identity_provider_with_correct_permissions: IdentityProvider,
-):
- get_rating_query = GetRatingQuery(
- rating_id=RatingId(uuid7()),
- )
- get_rating_handler = GetRatingHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- rating_gateway=rating_gateway,
- identity_provider=identity_provider_with_correct_permissions,
- )
-
- with pytest.raises(ApplicationError) as error:
- get_rating_handler.execute(get_rating_query)
-
- assert error.value.message == RATING_DOES_NOT_EXIST
diff --git a/tests/unit/application/query_handlers/test_get_review.py b/tests/unit/application/query_handlers/test_get_review.py
deleted file mode 100644
index 858996b..0000000
--- a/tests/unit/application/query_handlers/test_get_review.py
+++ /dev/null
@@ -1,140 +0,0 @@
-from unittest.mock import Mock
-from datetime import date, datetime, timezone
-
-import pytest
-from uuid_extensions import uuid7
-
-from amdb.domain.entities.user import UserId, User
-from amdb.domain.entities.movie import MovieId, Movie
-from amdb.domain.entities.review import ReviewId, ReviewType, Review
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.user_gateway import UserGateway
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.review_gateway import ReviewGateway
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.queries.get_review import GetReviewQuery, GetReviewResult
-from amdb.application.query_handlers.get_review import GetReviewHandler
-from amdb.application.common.constants.exceptions import (
- GET_REVIEW_ACCESS_DENIED,
- REVIEW_DOES_NOT_EXIST,
-)
-from amdb.application.common.exception import ApplicationError
-
-
-@pytest.fixture
-def identity_provider_with_correct_permissions(
- permissions_gateway: PermissionsGateway,
-) -> IdentityProvider:
- identity_provider = Mock()
-
- correct_permissions = permissions_gateway.for_get_review()
- identity_provider.get_permissions = Mock(return_value=correct_permissions)
-
- return identity_provider
-
-
-def test_get_review(
- permissions_gateway: PermissionsGateway,
- user_gateway: UserGateway,
- movie_gateway: MovieGateway,
- review_gateway: ReviewGateway,
- unit_of_work: UnitOfWork,
- identity_provider_with_correct_permissions: IdentityProvider,
-):
- user = User(
- id=UserId(uuid7()),
- name="John Doe",
- )
- user_gateway.save(user)
-
- movie = Movie(
- id=MovieId(uuid7()),
- title="Gone girl",
- release_date=date(2014, 10, 3),
- rating=0,
- rating_count=0,
- )
- movie_gateway.save(movie)
-
- review = Review(
- id=ReviewId(uuid7()),
- user_id=user.id,
- movie_id=movie.id,
- title="Masterpice",
- content="Extremely underrated",
- type=ReviewType.POSITIVE,
- created_at=datetime.now(timezone.utc),
- )
- review_gateway.save(review)
-
- unit_of_work.commit()
-
- get_review_query = GetReviewQuery(
- review_id=review.id,
- )
- get_review_handler = GetReviewHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- review_gateway=review_gateway,
- identity_provider=identity_provider_with_correct_permissions,
- )
-
- get_review_result = get_review_handler.execute(get_review_query)
- expected_get_review_result = GetReviewResult(
- user_id=review.user_id,
- movie_id=review.movie_id,
- title=review.title,
- content=review.content,
- type=review.type,
- created_at=review.created_at,
- )
-
- assert get_review_result == expected_get_review_result
-
-
-def test_get_review_should_raise_error_when_access_is_denied(
- permissions_gateway: PermissionsGateway,
- review_gateway: ReviewGateway,
- identity_provider_with_incorrect_permissions: IdentityProvider,
-):
- get_review_query = GetReviewQuery(
- review_id=ReviewId(uuid7()),
- )
- get_review_handler = GetReviewHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- review_gateway=review_gateway,
- identity_provider=identity_provider_with_incorrect_permissions,
- )
-
- with pytest.raises(ApplicationError) as error:
- get_review_handler.execute(get_review_query)
-
- assert error.value.message == GET_REVIEW_ACCESS_DENIED
-
-
-def test_get_review_should_raise_error_when_review_does_not_exist(
- permissions_gateway: PermissionsGateway,
- review_gateway: ReviewGateway,
- identity_provider_with_correct_permissions: IdentityProvider,
-):
- get_review_query = GetReviewQuery(
- review_id=ReviewId(uuid7()),
- )
- get_review_handler = GetReviewHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- review_gateway=review_gateway,
- identity_provider=identity_provider_with_correct_permissions,
- )
-
- with pytest.raises(ApplicationError) as error:
- get_review_handler.execute(get_review_query)
-
- assert error.value.message == REVIEW_DOES_NOT_EXIST
diff --git a/tests/unit/application/query_handlers/test_login.py b/tests/unit/application/query_handlers/test_login.py
index d996d61..91cc151 100644
--- a/tests/unit/application/query_handlers/test_login.py
+++ b/tests/unit/application/query_handlers/test_login.py
@@ -3,12 +3,10 @@
from amdb.domain.entities.user import UserId, User
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.user_gateway import UserGateway
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.password_manager import PasswordManager
+from amdb.application.common.gateways.user import UserGateway
+from amdb.application.common.gateways.permissions import PermissionsGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.password_manager import PasswordManager
from amdb.application.queries.login import LoginQuery
from amdb.application.query_handlers.login import LoginHandler
from amdb.application.common.constants.exceptions import (
diff --git a/tests/unit/application/query_handlers/test_get_my_ratings.py b/tests/unit/application/query_handlers/test_non_detailed_movies.py
similarity index 50%
rename from tests/unit/application/query_handlers/test_get_my_ratings.py
rename to tests/unit/application/query_handlers/test_non_detailed_movies.py
index eaff9eb..6b32a6a 100644
--- a/tests/unit/application/query_handlers/test_get_my_ratings.py
+++ b/tests/unit/application/query_handlers/test_non_detailed_movies.py
@@ -8,24 +8,20 @@
from amdb.domain.entities.movie import MovieId, Movie
from amdb.domain.entities.rating import RatingId, Rating
from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.interfaces.user_gateway import UserGateway
-from amdb.application.common.interfaces.movie_gateway import MovieGateway
-from amdb.application.common.interfaces.rating_gateway import RatingGateway
-from amdb.application.common.interfaces.permissions_gateway import (
- PermissionsGateway,
-)
-from amdb.application.common.interfaces.unit_of_work import UnitOfWork
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.queries.get_my_ratings import (
- GetMyRatingsQuery,
- GetMyRatingsResult,
-)
-from amdb.application.query_handlers.get_my_ratings import GetMyRatingsHandler
-from amdb.application.common.constants.exceptions import (
- GET_MY_RATINGS_ACCESS_DENIED,
+from amdb.application.common.gateways.permissions import PermissionsGateway
+from amdb.application.common.gateways.user import UserGateway
+from amdb.application.common.gateways.movie import MovieGateway
+from amdb.application.common.gateways.rating import RatingGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.readers.movie import MovieViewModelReader
+from amdb.application.common.identity_provider import IdentityProvider
+from amdb.application.common.view_models.non_detailed_movie import (
+ UserRating,
+ NonDetailedMovieViewModel,
)
+from amdb.application.queries.non_detailed_movies import GetNonDetailedMoviesQuery
+from amdb.application.query_handlers.non_detailed_movies import GetNonDetailedMoviesHandler
+from amdb.application.common.constants.exceptions import GET_MOVIE_ACCESS_DENIED
from amdb.application.common.exception import ApplicationError
@@ -35,18 +31,19 @@ def identity_provider_with_correct_permissions(
) -> IdentityProvider:
identity_provider = Mock()
- correct_permissions = permissions_gateway.for_get_my_ratings()
+ correct_permissions = permissions_gateway.for_get_movie()
identity_provider.get_permissions = Mock(return_value=correct_permissions)
return identity_provider
-def test_get_my_ratings(
+def test_get_non_detailed_movies(
user_gateway: UserGateway,
movie_gateway: MovieGateway,
rating_gateway: RatingGateway,
- permissions_gateway: PermissionsGateway,
unit_of_work: UnitOfWork,
+ permissions_gateway: PermissionsGateway,
+ movie_view_model_reader: MovieViewModelReader,
identity_provider_with_correct_permissions: IdentityProvider,
):
user = User(
@@ -59,7 +56,7 @@ def test_get_my_ratings(
id=MovieId(uuid7()),
title="Matrix",
release_date=date(1999, 3, 31),
- rating=10,
+ rating=8,
rating_count=1,
)
movie_gateway.save(movie)
@@ -68,7 +65,7 @@ def test_get_my_ratings(
id=RatingId(uuid7()),
movie_id=movie.id,
user_id=user.id,
- value=10,
+ value=8,
created_at=datetime.now(timezone.utc),
)
rating_gateway.save(rating)
@@ -79,45 +76,51 @@ def test_get_my_ratings(
return_value=user.id,
)
- get_my_ratings_query = GetMyRatingsQuery(
+ get_non_detailed_movies_query = GetNonDetailedMoviesQuery(
limit=10,
offset=0,
)
- get_my_ratings_handler = GetMyRatingsHandler(
+ get_non_detailed_movies_handler = GetNonDetailedMoviesHandler(
access_concern=AccessConcern(),
permissions_gateway=permissions_gateway,
- rating_gateway=rating_gateway,
+ movie_view_model_reader=movie_view_model_reader,
identity_provider=identity_provider_with_correct_permissions,
)
- get_my_ratings_result = get_my_ratings_handler.execute(
- get_my_ratings_query
- )
- expected_get_my_ratings_result = GetMyRatingsResult(
- ratings=[rating],
- rating_count=1,
- )
+ expected_result = [
+ NonDetailedMovieViewModel(
+ id=movie.id,
+ title=movie.title,
+ release_date=movie.release_date,
+ rating=movie.rating,
+ user_rating=UserRating(
+ id=rating.id,
+ value=rating.value,
+ ),
+ ),
+ ]
+ result = get_non_detailed_movies_handler.execute(get_non_detailed_movies_query)
- assert get_my_ratings_result == expected_get_my_ratings_result
+ assert expected_result == result
-def test_get_my_ratings_should_raise_error_when_access_is_denied(
- rating_gateway: RatingGateway,
+def test_get_non_detailed_movies_should_raise_error_when_access_is_denied(
permissions_gateway: PermissionsGateway,
+ movie_view_model_reader: MovieViewModelReader,
identity_provider_with_incorrect_permissions: IdentityProvider,
):
- get_my_ratings_query = GetMyRatingsQuery(
+ get_non_detailed_movies_query = GetNonDetailedMoviesQuery(
limit=10,
offset=0,
)
- get_my_ratings_handler = GetMyRatingsHandler(
+ get_non_detailed_movies_handler = GetNonDetailedMoviesHandler(
access_concern=AccessConcern(),
permissions_gateway=permissions_gateway,
- rating_gateway=rating_gateway,
+ movie_view_model_reader=movie_view_model_reader,
identity_provider=identity_provider_with_incorrect_permissions,
)
with pytest.raises(ApplicationError) as error:
- get_my_ratings_handler.execute(get_my_ratings_query)
+ get_non_detailed_movies_handler.execute(get_non_detailed_movies_query)
- assert error.value.message == GET_MY_RATINGS_ACCESS_DENIED
+ assert error.value.message == GET_MOVIE_ACCESS_DENIED
From 17c20e65dfa5b7d6f61864ee336684d5973dc257 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Thu, 22 Feb 2024 20:57:55 +0400
Subject: [PATCH 03/39] Refactor gateway imlementations, add new tests, fix
routes
---
.env.template | 28 --
.pre-commit-config.yaml | 4 +-
config/prod_config.template.toml | 13 +
config/test_config.template.toml | 5 +
docker-compose.yaml | 18 +-
pyproject.toml | 9 +-
.../command_handlers/create_movie.py | 2 +-
.../command_handlers/delete_movie.py | 2 +-
.../command_handlers/rate_movie.py | 4 +-
.../command_handlers/review_movie.py | 4 +-
.../command_handlers/unrate_movie.py | 4 +-
.../common/constants/exceptions.py | 5 -
src/amdb/application/common/gateways/movie.py | 3 -
.../common/gateways/permissions.py | 9 -
.../application/common/gateways/rating.py | 16 -
.../application/common/gateways/review.py | 16 -
.../application/common/identity_provider.py | 9 +-
.../readers/{movie.py => detailed_movie.py} | 15 +-
.../common/readers/non_detailed_movie.py | 16 +
.../application/common/view_models/review.py | 12 +-
.../query_handlers/detailed_movie.py | 36 +--
.../query_handlers/non_detailed_movies.py | 42 +--
.../application/query_handlers/reviews.py | 23 +-
src/amdb/domain/services/access_concern.py | 4 +-
.../auth/raw/identity_provider.py | 9 +-
.../infrastructure/auth/session/config.py | 14 +-
.../auth/session/constants/exceptions.py | 2 -
.../auth/session/identity_provider.py | 43 ++-
.../auth/session/{model.py => session.py} | 0
.../auth/session/session_gateway.py | 8 +
.../auth/session/session_processor.py | 9 +-
.../password_manager/hash_computer.py | 28 ++
.../{model.py => password_hash.py} | 6 +-
.../password_manager/password_hash_gateway.py | 12 +
.../password_manager/password_manager.py | 51 ++-
.../persistence/alembic/config.py | 2 +-
.../migrations/versions/65f8840f4494_.py | 9 +-
.../migrations/versions/85a348467b90_.py | 4 +-
.../migrations/versions/9e92de201574_.py | 4 +-
.../persistence/redis/config.py | 15 +-
.../redis/mappers}/__init__.py | 0
.../{gateways => mappers}/permissions.py | 8 +-
.../redis/{gateways => mappers}/session.py | 8 +-
.../persistence/sqlalchemy/config.py | 24 +-
.../persistence/sqlalchemy/gateway_factory.py | 49 ---
.../persistence/sqlalchemy/gateways/movie.py | 52 ----
.../persistence/sqlalchemy/gateways/rating.py | 101 ------
.../persistence/sqlalchemy/gateways/review.py | 96 ------
.../persistence/sqlalchemy/gateways/user.py | 37 ---
.../sqlalchemy/gateways/user_password_hash.py | 37 ---
.../mappers/entities}/__init__.py | 0
.../sqlalchemy/mappers/entities/movie.py | 53 ++++
.../sqlalchemy/mappers/entities/rating.py | 68 ++++
.../sqlalchemy/mappers/entities/review.py | 68 ++++
.../sqlalchemy/mappers/entities/user.py | 41 +++
.../persistence/sqlalchemy/mappers/movie.py | 24 --
.../sqlalchemy/mappers/password_hash.py | 41 +++
.../persistence/sqlalchemy/mappers/rating.py | 26 --
.../persistence/sqlalchemy/mappers/review.py | 34 --
.../persistence/sqlalchemy/mappers/user.py | 18 --
.../sqlalchemy/mappers/user_password_hash.py | 31 --
.../view_models}/__init__.py | 0
.../mappers/view_models/detailed_movie.py | 119 +++++++
.../mappers/view_models/non_detailed_movie.py | 91 ++++++
.../sqlalchemy/mappers/view_models/review.py | 109 +++++++
.../persistence/sqlalchemy/models/movie.py | 2 +-
...user_password_hash.py => password_hash.py} | 2 +-
.../persistence/sqlalchemy/models/rating.py | 10 +-
.../persistence/sqlalchemy/models/review.py | 10 +-
.../persistence/sqlalchemy/models/user.py | 2 +-
src/amdb/infrastructure/security/__init__.py | 0
src/amdb/infrastructure/security/hasher.py | 30 --
src/amdb/main/cli/__main__.py | 18 +-
src/amdb/main/cli/app.py | 47 ++-
src/amdb/main/cli/di.py | 49 ---
src/amdb/main/config.py | 51 ---
src/amdb/main/ioc.py | 294 +++++++-----------
src/amdb/main/web_api/__main__.py | 29 +-
src/amdb/main/web_api/app.py | 15 +-
src/amdb/main/web_api/config.py | 63 +---
src/amdb/main/web_api/di.py | 53 ++--
src/amdb/presentation/cli/movie.py | 103 +-----
src/amdb/presentation/handler_factory.py | 66 +---
.../web_api/dependencies/identity_provider.py | 27 +-
.../web_api/exception_handlers.py | 6 +-
.../web_api/routers/auth/login.py | 13 +-
.../web_api/routers/auth/register.py | 13 +-
.../web_api/routers/movies/get_movie.py | 34 --
.../web_api/routers/movies/get_movies.py | 55 +++-
.../web_api/routers/movies/router.py | 13 +-
.../routers/ratings/get_movie_ratings.py | 43 ---
.../web_api/routers/ratings/get_my_ratings.py | 39 ---
.../web_api/routers/ratings/get_rating.py | 35 ---
.../web_api/routers/ratings/rate_movie.py | 7 +-
.../web_api/routers/ratings/router.py | 26 --
.../web_api/routers/ratings/unrate_movie.py | 7 +-
.../routers/reviews/get_movie_reviews.py | 43 ---
.../web_api/routers/reviews/get_my_reviews.py | 35 ---
.../web_api/routers/reviews/get_review.py | 34 --
.../web_api/routers/reviews/get_reviews.py | 28 ++
.../web_api/routers/reviews/review_movie.py | 7 +-
.../web_api/routers/reviews/router.py | 24 +-
.../command_handlers/test_create_movie.py | 2 +-
.../command_handlers/test_delete_movie.py | 2 +-
.../command_handlers/test_rate_movie.py | 8 +-
.../command_handlers/test_review_movie.py | 6 +-
.../command_handlers/test_unrate_movie.py | 6 +-
tests/unit/application/conftest.py | 110 ++++---
.../query_handlers/test_detailed_movie.py | 80 ++---
.../query_handlers/test_get_reviews.py | 121 +++++++
.../test_non_detailed_movies.py | 70 ++---
tests/unit/conftest.py | 41 +--
.../infrastructure/alembic/test_stairway.py | 7 +-
113 files changed, 1477 insertions(+), 1959 deletions(-)
delete mode 100644 .env.template
create mode 100644 config/prod_config.template.toml
create mode 100644 config/test_config.template.toml
rename src/amdb/application/common/readers/{movie.py => detailed_movie.py} (51%)
create mode 100644 src/amdb/application/common/readers/non_detailed_movie.py
delete mode 100644 src/amdb/infrastructure/auth/session/constants/exceptions.py
rename src/amdb/infrastructure/auth/session/{model.py => session.py} (100%)
create mode 100644 src/amdb/infrastructure/auth/session/session_gateway.py
create mode 100644 src/amdb/infrastructure/password_manager/hash_computer.py
rename src/amdb/infrastructure/password_manager/{model.py => password_hash.py} (55%)
create mode 100644 src/amdb/infrastructure/password_manager/password_hash_gateway.py
rename src/amdb/infrastructure/{auth/session/constants => persistence/redis/mappers}/__init__.py (100%)
rename src/amdb/infrastructure/persistence/redis/{gateways => mappers}/permissions.py (90%)
rename src/amdb/infrastructure/persistence/redis/{gateways => mappers}/session.py (83%)
delete mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/gateway_factory.py
delete mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/gateways/movie.py
delete mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/gateways/rating.py
delete mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/gateways/review.py
delete mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/gateways/user.py
delete mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/gateways/user_password_hash.py
rename src/amdb/infrastructure/persistence/{redis/gateways => sqlalchemy/mappers/entities}/__init__.py (100%)
create mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/movie.py
create mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/rating.py
create mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/review.py
create mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/user.py
delete mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/mappers/movie.py
create mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/mappers/password_hash.py
delete mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/mappers/rating.py
delete mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/mappers/review.py
delete mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/mappers/user.py
delete mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/mappers/user_password_hash.py
rename src/amdb/infrastructure/persistence/sqlalchemy/{gateways => mappers/view_models}/__init__.py (100%)
create mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/detailed_movie.py
create mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/non_detailed_movie.py
create mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/review.py
rename src/amdb/infrastructure/persistence/sqlalchemy/models/{user_password_hash.py => password_hash.py} (91%)
delete mode 100644 src/amdb/infrastructure/security/__init__.py
delete mode 100644 src/amdb/infrastructure/security/hasher.py
delete mode 100644 src/amdb/main/cli/di.py
delete mode 100644 src/amdb/main/config.py
delete mode 100644 src/amdb/presentation/web_api/routers/movies/get_movie.py
delete mode 100644 src/amdb/presentation/web_api/routers/ratings/get_movie_ratings.py
delete mode 100644 src/amdb/presentation/web_api/routers/ratings/get_my_ratings.py
delete mode 100644 src/amdb/presentation/web_api/routers/ratings/get_rating.py
delete mode 100644 src/amdb/presentation/web_api/routers/reviews/get_movie_reviews.py
delete mode 100644 src/amdb/presentation/web_api/routers/reviews/get_my_reviews.py
delete mode 100644 src/amdb/presentation/web_api/routers/reviews/get_review.py
create mode 100644 src/amdb/presentation/web_api/routers/reviews/get_reviews.py
create mode 100644 tests/unit/application/query_handlers/test_get_reviews.py
diff --git a/.env.template b/.env.template
deleted file mode 100644
index 719e826..0000000
--- a/.env.template
+++ /dev/null
@@ -1,28 +0,0 @@
-TEST_POSTGRES_USER=
-TEST_POSTGRES_PASSWORD=
-TEST_POSTGRES_DB=
-TEST_POSTGRES_HOST=
-TEST_POSTGRES_PORT=
-
-TEST_REDIS_HOST=
-TEST_REDIS_PORT=
-TEST_REDIS_DB=
-TEST_REDIS_PASSWORD=
-
-POSTGRES_USER=
-POSTGRES_PASSWORD=
-POSTGRES_DB=
-POSTGRES_HOST=
-POSTGRES_PORT=
-
-REDIS_HOST=
-REDIS_PORT=
-REDIS_DB=
-REDIS_PASSWORD=
-
-FASTAPI_VERSION=
-
-UVICORN_HOST=
-UVICORN_PORT=
-
-SESSION_LIFETIME= # Minutes
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c7832c5..e457d2a 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -17,4 +17,6 @@ repos:
hooks:
- id: mypy
files: ^src/
- additional_dependencies: [types-redis]
+ additional_dependencies:
+ - types-redis
+ - types-toml
diff --git a/config/prod_config.template.toml b/config/prod_config.template.toml
new file mode 100644
index 0000000..bc037b7
--- /dev/null
+++ b/config/prod_config.template.toml
@@ -0,0 +1,13 @@
+[postgres]
+url = "postgresql://postgres:1234@127.0.0.1:5432/amdb"
+
+[redis]
+url = "redis://:1234@127.0.0.1:6379/0"
+
+[auth-session]
+lifetime = 3600 # Minutes
+
+[web-api]
+version = "0.5.0"
+host = "127.0.0.1"
+port = 8000
diff --git a/config/test_config.template.toml b/config/test_config.template.toml
new file mode 100644
index 0000000..e662e37
--- /dev/null
+++ b/config/test_config.template.toml
@@ -0,0 +1,5 @@
+[postgres]
+url = "postgresql://postgres:1234@127.0.0.1:5433/amdb_test"
+
+[redis]
+url = "redis://:1234@127.0.0.1:6378/1"
diff --git a/docker-compose.yaml b/docker-compose.yaml
index a9ff65e..a12f994 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -13,23 +13,7 @@ services:
dockerfile: ./Dockerfile
target: web_api
environment:
- - POSTGRES_USER
- - POSTGRES_PASSWORD
- - POSTGRES_DB
- - POSTGRES_HOST=postgres
- - POSTGRES_PORT
-
- - FASTAPI_VERSION
-
- - UVICORN_HOST=0.0.0.0
- - UVICORN_PORT
-
- - REDIS_HOST=redis
- - REDIS_PORT
- - REDIS_DB
- - REDIS_PASSWORD
-
- - SESSION_LIFETIME
+ - CONFIG_PATH
postgres:
profiles: [web_api]
diff --git a/pyproject.toml b/pyproject.toml
index 1c0694a..8167880 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -25,10 +25,11 @@ maintainers = [
]
dependencies = [
"uuid7",
- "sqlalchemy==2.0.23",
- "psycopg2-binary==2.9.9",
- "alembic==1.13.0",
- "redis>=5.0.0",
+ "toml==0.10.*",
+ "sqlalchemy==2.0.*",
+ "psycopg2-binary==2.9.*",
+ "alembic==1.13.*",
+ "redis==5.0.*",
]
[project.optional-dependencies]
diff --git a/src/amdb/application/command_handlers/create_movie.py b/src/amdb/application/command_handlers/create_movie.py
index ff40f65..76d503f 100644
--- a/src/amdb/application/command_handlers/create_movie.py
+++ b/src/amdb/application/command_handlers/create_movie.py
@@ -33,7 +33,7 @@ def __init__(
self._identity_provider = identity_provider
def execute(self, command: CreateMovieCommand) -> MovieId:
- current_permissions = self._identity_provider.get_permissions()
+ current_permissions = self._identity_provider.permissions()
required_permissions = self._permissions_gateway.for_create_movie()
access = self._access_concern.authorize(
current_permissions=current_permissions,
diff --git a/src/amdb/application/command_handlers/delete_movie.py b/src/amdb/application/command_handlers/delete_movie.py
index 8b7ab42..e61e465 100644
--- a/src/amdb/application/command_handlers/delete_movie.py
+++ b/src/amdb/application/command_handlers/delete_movie.py
@@ -36,7 +36,7 @@ def __init__(
self._identity_provider = identity_provider
def execute(self, command: DeleteMovieCommand) -> None:
- current_permissions = self._identity_provider.get_permissions()
+ current_permissions = self._identity_provider.permissions()
required_permissions = self._permissions_gateway.for_delete_movie()
access = self._access_concern.authorize(
current_permissions=current_permissions,
diff --git a/src/amdb/application/command_handlers/rate_movie.py b/src/amdb/application/command_handlers/rate_movie.py
index f576ad2..a01142c 100644
--- a/src/amdb/application/command_handlers/rate_movie.py
+++ b/src/amdb/application/command_handlers/rate_movie.py
@@ -45,7 +45,7 @@ def __init__(
self._identity_provider = identity_provider
def execute(self, command: RateMovieCommand) -> RatingId:
- current_permissions = self._identity_provider.get_permissions()
+ current_permissions = self._identity_provider.permissions()
required_permissions = self._permissions_gateway.for_rate_movie()
access = self._access_concern.authorize(
current_permissions=current_permissions,
@@ -58,7 +58,7 @@ def execute(self, command: RateMovieCommand) -> RatingId:
if not movie:
raise ApplicationError(MOVIE_DOES_NOT_EXIST)
- current_user_id = self._identity_provider.get_user_id()
+ current_user_id = self._identity_provider.user_id()
rating = self._rating_gateway.with_user_id_and_movie_id(
user_id=current_user_id,
diff --git a/src/amdb/application/command_handlers/review_movie.py b/src/amdb/application/command_handlers/review_movie.py
index be4609c..7f0ae3d 100644
--- a/src/amdb/application/command_handlers/review_movie.py
+++ b/src/amdb/application/command_handlers/review_movie.py
@@ -45,7 +45,7 @@ def __init__(
self._identity_provider = identity_provider
def execute(self, command: ReviewMovieCommand) -> ReviewId:
- current_permissions = self._identity_provider.get_permissions()
+ current_permissions = self._identity_provider.permissions()
required_permissions = self._permissions_gateway.for_review_movie()
access = self._access_concern.authorize(
current_permissions=current_permissions,
@@ -58,7 +58,7 @@ def execute(self, command: ReviewMovieCommand) -> ReviewId:
if not movie:
raise ApplicationError(MOVIE_DOES_NOT_EXIST)
- current_user_id = self._identity_provider.get_user_id()
+ current_user_id = self._identity_provider.user_id()
review = self._review_gateway.with_movie_id_and_user_id(
user_id=current_user_id,
diff --git a/src/amdb/application/command_handlers/unrate_movie.py b/src/amdb/application/command_handlers/unrate_movie.py
index c766e50..e32c577 100644
--- a/src/amdb/application/command_handlers/unrate_movie.py
+++ b/src/amdb/application/command_handlers/unrate_movie.py
@@ -40,7 +40,7 @@ def __init__(
self._identity_provider = identity_provider
def execute(self, command: UnrateMovieCommand) -> None:
- current_permissions = self._identity_provider.get_permissions()
+ current_permissions = self._identity_provider.permissions()
required_permissions = self._permissions_gateway.for_unrate_movie()
access = self._access_concern.authorize(
current_permissions=current_permissions,
@@ -53,7 +53,7 @@ def execute(self, command: UnrateMovieCommand) -> None:
if not rating:
raise ApplicationError(RATING_DOES_NOT_EXIST)
- current_user_id = self._identity_provider.get_user_id()
+ current_user_id = self._identity_provider.user_id()
if current_user_id != rating.user_id:
raise ApplicationError(USER_IS_NOT_OWNER)
diff --git a/src/amdb/application/common/constants/exceptions.py b/src/amdb/application/common/constants/exceptions.py
index 19a3df2..68aacdd 100644
--- a/src/amdb/application/common/constants/exceptions.py
+++ b/src/amdb/application/common/constants/exceptions.py
@@ -1,13 +1,8 @@
LOGIN_ACCESS_DENIED = "Access to login in denied"
-GET_MOVIES_ACCESS_DENIED = "Access to getting movies is denied"
-GET_MOVIE_ACCESS_DENIED = "Access to getting movie is denied"
CREATE_MOVIE_ACCESS_DENIED = "Access to movie creation is denied"
DELETE_MOVIE_ACCESS_DENIED = "Access to movie deletion is denied"
-GET_RATING_ACCESS_DENIED = "Access to getting rating is denied"
RATE_MOVIE_ACCESS_DENIED = "Access to movie rating is denied"
UNRATE_MOVIE_ACCESS_DENIED = "Access to movie unrating is denied"
-GET_REVIEWS_ACCESS_DENIED = "Access to getting movie reviews is denied"
-GET_REVIEW_ACCESS_DENIED = "Access to getting review is denied"
REVIEW_MOVIE_ACCESS_DENIED = "Access to movie reviewing is denied"
USER_IS_NOT_OWNER = "User is not owner"
diff --git a/src/amdb/application/common/gateways/movie.py b/src/amdb/application/common/gateways/movie.py
index 0c74df9..3bad7bd 100644
--- a/src/amdb/application/common/gateways/movie.py
+++ b/src/amdb/application/common/gateways/movie.py
@@ -15,6 +15,3 @@ def update(self, movie: Movie) -> None:
def delete(self, movie: Movie) -> None:
raise NotImplementedError
-
- def list(self, limit: int, offset: int) -> list[Movie]:
- raise NotImplementedError
diff --git a/src/amdb/application/common/gateways/permissions.py b/src/amdb/application/common/gateways/permissions.py
index 94b29b2..501c912 100644
--- a/src/amdb/application/common/gateways/permissions.py
+++ b/src/amdb/application/common/gateways/permissions.py
@@ -16,12 +16,6 @@ def for_new_user(self) -> int:
def for_login(self) -> int:
raise NotImplementedError
- def for_get_movies(self) -> int:
- raise NotImplementedError
-
- def for_get_movie(self) -> int:
- raise NotImplementedError
-
def for_create_movie(self) -> int:
raise NotImplementedError
@@ -34,8 +28,5 @@ def for_rate_movie(self) -> int:
def for_unrate_movie(self) -> int:
raise NotImplementedError
- def for_get_reviews(self) -> int:
- raise NotImplementedError
-
def for_review_movie(self) -> int:
raise NotImplementedError
diff --git a/src/amdb/application/common/gateways/rating.py b/src/amdb/application/common/gateways/rating.py
index 9c5c3b1..af29b70 100644
--- a/src/amdb/application/common/gateways/rating.py
+++ b/src/amdb/application/common/gateways/rating.py
@@ -16,22 +16,6 @@ def with_user_id_and_movie_id(
) -> Optional[Rating]:
raise NotImplementedError
- def list_with_movie_id(
- self,
- movie_id: MovieId,
- limit: int,
- offset: int,
- ) -> list[Rating]:
- raise NotImplementedError
-
- def list_with_user_id(
- self,
- user_id: UserId,
- limit: int,
- offset: int,
- ) -> list[Rating]:
- raise NotImplementedError
-
def save(self, rating: Rating) -> None:
raise NotImplementedError
diff --git a/src/amdb/application/common/gateways/review.py b/src/amdb/application/common/gateways/review.py
index 37f842f..ab2b5ca 100644
--- a/src/amdb/application/common/gateways/review.py
+++ b/src/amdb/application/common/gateways/review.py
@@ -16,22 +16,6 @@ def with_movie_id_and_user_id(
) -> Optional[Review]:
raise NotImplementedError
- def list_with_movie_id(
- self,
- movie_id: MovieId,
- limit: int,
- offset: int,
- ) -> list[Review]:
- raise NotImplementedError
-
- def list_with_user_id(
- self,
- user_id: UserId,
- limit: int,
- offset: int,
- ) -> list[Review]:
- raise NotImplementedError
-
def save(self, review: Review) -> None:
raise NotImplementedError
diff --git a/src/amdb/application/common/identity_provider.py b/src/amdb/application/common/identity_provider.py
index 915e505..769cd06 100644
--- a/src/amdb/application/common/identity_provider.py
+++ b/src/amdb/application/common/identity_provider.py
@@ -1,11 +1,14 @@
-from typing import Protocol
+from typing import Protocol, Optional
from amdb.domain.entities.user import UserId
class IdentityProvider(Protocol):
- def get_user_id(self) -> UserId:
+ def user_id(self) -> UserId:
raise NotImplementedError
- def get_permissions(self) -> int:
+ def user_id_or_none(self) -> Optional[UserId]:
+ raise NotImplementedError
+
+ def permissions(self) -> int:
raise NotImplementedError
diff --git a/src/amdb/application/common/readers/movie.py b/src/amdb/application/common/readers/detailed_movie.py
similarity index 51%
rename from src/amdb/application/common/readers/movie.py
rename to src/amdb/application/common/readers/detailed_movie.py
index 8a3667b..9cff8e4 100644
--- a/src/amdb/application/common/readers/movie.py
+++ b/src/amdb/application/common/readers/detailed_movie.py
@@ -5,21 +5,10 @@
from amdb.application.common.view_models.detailed_movie import (
DetailedMovieViewModel,
)
-from amdb.application.common.view_models.non_detailed_movie import (
- NonDetailedMovieViewModel,
-)
-
-class MovieViewModelReader(Protocol):
- def list_non_detailed(
- self,
- current_user_id: Optional[UserId],
- limit: int,
- offset: int,
- ) -> list[NonDetailedMovieViewModel]:
- raise NotImplementedError
- def detailed(
+class DetailedMovieViewModelReader(Protocol):
+ def one(
self,
movie_id: MovieId,
current_user_id: Optional[UserId],
diff --git a/src/amdb/application/common/readers/non_detailed_movie.py b/src/amdb/application/common/readers/non_detailed_movie.py
new file mode 100644
index 0000000..f799f3a
--- /dev/null
+++ b/src/amdb/application/common/readers/non_detailed_movie.py
@@ -0,0 +1,16 @@
+from typing import Protocol, Optional
+
+from amdb.domain.entities.user import UserId
+from amdb.application.common.view_models.non_detailed_movie import (
+ NonDetailedMovieViewModel,
+)
+
+
+class NonDetailedMovieViewModelReader(Protocol):
+ def list(
+ self,
+ current_user_id: Optional[UserId],
+ limit: int,
+ offset: int,
+ ) -> list[NonDetailedMovieViewModel]:
+ raise NotImplementedError
diff --git a/src/amdb/application/common/view_models/review.py b/src/amdb/application/common/view_models/review.py
index be4e8e3..8091a8b 100644
--- a/src/amdb/application/common/view_models/review.py
+++ b/src/amdb/application/common/view_models/review.py
@@ -1,23 +1,20 @@
-__all__ = ("ReviewViewModel",)
-
from datetime import datetime
from typing import Optional
from typing_extensions import TypedDict
from amdb.domain.entities.user import UserId
-from amdb.domain.entities.movie import MovieId
from amdb.domain.entities.rating import RatingId
from amdb.domain.entities.review import ReviewId, ReviewType
-class Rating(TypedDict):
+class UserRating(TypedDict):
id: RatingId
value: float
created_at: datetime
-class Review(TypedDict):
+class UserReview(TypedDict):
id: ReviewId
title: str
content: str
@@ -27,6 +24,5 @@ class Review(TypedDict):
class ReviewViewModel(TypedDict):
user_id: UserId
- movie_id: MovieId
- review: Review
- rating: Optional[Rating]
+ user_review: UserReview
+ user_rating: Optional[UserRating]
diff --git a/src/amdb/application/query_handlers/detailed_movie.py b/src/amdb/application/query_handlers/detailed_movie.py
index b001775..fc139ca 100644
--- a/src/amdb/application/query_handlers/detailed_movie.py
+++ b/src/amdb/application/query_handlers/detailed_movie.py
@@ -1,13 +1,12 @@
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.gateways.permissions import PermissionsGateway
-from amdb.application.common.readers.movie import MovieViewModelReader
-from amdb.application.common.identity_provider import IdentityProvider
-from amdb.application.common.constants.exceptions import (
- GET_MOVIE_ACCESS_DENIED,
- MOVIE_DOES_NOT_EXIST,
+from amdb.application.common.readers.detailed_movie import (
+ DetailedMovieViewModelReader,
)
+from amdb.application.common.identity_provider import IdentityProvider
+from amdb.application.common.constants.exceptions import MOVIE_DOES_NOT_EXIST
from amdb.application.common.exception import ApplicationError
-from amdb.application.common.view_models.detailed_movie import DetailedMovieViewModel
+from amdb.application.common.view_models.detailed_movie import (
+ DetailedMovieViewModel,
+)
from amdb.application.queries.detailed_movie import GetDetailedMovieQuery
@@ -15,29 +14,16 @@ class GetDetailedMovieHandler:
def __init__(
self,
*,
- access_concern: AccessConcern,
- permissions_gateway: PermissionsGateway,
- movie_view_model_reader: MovieViewModelReader,
+ detailed_movie_reader: DetailedMovieViewModelReader,
identity_provider: IdentityProvider,
) -> None:
- self._access_concern = access_concern
- self._permissions_gateway = permissions_gateway
- self._movie_view_model_reader = movie_view_model_reader
+ self._detailed_movie_reader = detailed_movie_reader
self._identity_provider = identity_provider
def execute(self, query: GetDetailedMovieQuery) -> DetailedMovieViewModel:
- current_permissions = self._identity_provider.get_permissions()
- required_permissions = self._permissions_gateway.for_get_movie()
- access = self._access_concern.authorize(
- current_permissions=current_permissions,
- required_permissions=required_permissions,
- )
- if not access:
- raise ApplicationError(GET_MOVIE_ACCESS_DENIED)
-
- current_user_id = self._identity_provider.get_user_id()
+ current_user_id = self._identity_provider.user_id_or_none()
- detailed_movie_view_model = self._movie_view_model_reader.detailed(
+ detailed_movie_view_model = self._detailed_movie_reader.one(
movie_id=query.movie_id,
current_user_id=current_user_id,
)
diff --git a/src/amdb/application/query_handlers/non_detailed_movies.py b/src/amdb/application/query_handlers/non_detailed_movies.py
index 8d7c58d..d525d1e 100644
--- a/src/amdb/application/query_handlers/non_detailed_movies.py
+++ b/src/amdb/application/query_handlers/non_detailed_movies.py
@@ -1,40 +1,32 @@
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.gateways.permissions import PermissionsGateway
-from amdb.application.common.readers.movie import MovieViewModelReader
+from amdb.application.common.readers.non_detailed_movie import (
+ NonDetailedMovieViewModelReader,
+)
from amdb.application.common.identity_provider import IdentityProvider
-from amdb.application.common.constants.exceptions import GET_MOVIES_ACCESS_DENIED
-from amdb.application.common.exception import ApplicationError
-from amdb.application.common.view_models.non_detailed_movie import NonDetailedMovieViewModel
-from amdb.application.queries.non_detailed_movies import GetNonDetailedMoviesQuery
+from amdb.application.common.view_models.non_detailed_movie import (
+ NonDetailedMovieViewModel,
+)
+from amdb.application.queries.non_detailed_movies import (
+ GetNonDetailedMoviesQuery,
+)
class GetNonDetailedMoviesHandler:
def __init__(
self,
*,
- access_concern: AccessConcern,
- permissions_gateway: PermissionsGateway,
- movie_view_model_reader: MovieViewModelReader,
+ non_detailed_movie_reader: NonDetailedMovieViewModelReader,
identity_provider: IdentityProvider,
) -> None:
- self._access_concern = access_concern
- self._permissions_gateway = permissions_gateway
- self._movie_view_model_reader = movie_view_model_reader
+ self._non_detailed_movie_reader = non_detailed_movie_reader
self._identity_provider = identity_provider
- def execute(self, query: GetNonDetailedMoviesQuery) -> list[NonDetailedMovieViewModel]:
- current_permissions = self._identity_provider.get_permissions()
- required_permissions = self._permissions_gateway.for_get_movies()
- access = self._access_concern.authorize(
- current_permissions=current_permissions,
- required_permissions=required_permissions,
- )
- if not access:
- raise ApplicationError(GET_MOVIES_ACCESS_DENIED)
-
- current_user_id = self._identity_provider.get_user_id()
+ def execute(
+ self,
+ query: GetNonDetailedMoviesQuery,
+ ) -> list[NonDetailedMovieViewModel]:
+ current_user_id = self._identity_provider.user_id_or_none()
- non_detailed_movie_models = self._movie_view_model_reader.list_non_detailed(
+ non_detailed_movie_models = self._non_detailed_movie_reader.list(
current_user_id=current_user_id,
limit=query.limit,
offset=query.offset,
diff --git a/src/amdb/application/query_handlers/reviews.py b/src/amdb/application/query_handlers/reviews.py
index 6c92913..736add7 100644
--- a/src/amdb/application/query_handlers/reviews.py
+++ b/src/amdb/application/query_handlers/reviews.py
@@ -1,13 +1,7 @@
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.gateways.permissions import PermissionsGateway
from amdb.application.common.gateways.movie import MovieGateway
from amdb.application.common.readers.review import ReviewViewModelReader
-from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.common.view_models.review import ReviewViewModel
-from amdb.application.common.constants.exceptions import (
- GET_REVIEWS_ACCESS_DENIED,
- MOVIE_DOES_NOT_EXIST,
-)
+from amdb.application.common.constants.exceptions import MOVIE_DOES_NOT_EXIST
from amdb.application.common.exception import ApplicationError
from amdb.application.queries.reviews import GetReviewsQuery
@@ -16,28 +10,13 @@ class GetReviewsHandler:
def __init__(
self,
*,
- access_concern: AccessConcern,
- permissions_gateway: PermissionsGateway,
movie_gateway: MovieGateway,
review_view_model_reader: ReviewViewModelReader,
- identity_provider: IdentityProvider,
) -> None:
- self._access_concern = access_concern
- self._permissions_gateway = permissions_gateway
self._movie_gateway = movie_gateway
self._review_view_model_reader = review_view_model_reader
- self._identity_provider = identity_provider
def execute(self, query: GetReviewsQuery) -> list[ReviewViewModel]:
- current_permissions = self._identity_provider.get_permissions()
- required_permissions = self._permissions_gateway.for_get_reviews()
- access = self._access_concern.authorize(
- current_permissions=current_permissions,
- required_permissions=required_permissions,
- )
- if not access:
- raise ApplicationError(GET_REVIEWS_ACCESS_DENIED)
-
movie = self._movie_gateway.with_id(query.movie_id)
if not movie:
raise ApplicationError(MOVIE_DOES_NOT_EXIST)
diff --git a/src/amdb/domain/services/access_concern.py b/src/amdb/domain/services/access_concern.py
index 29ad163..3405fce 100644
--- a/src/amdb/domain/services/access_concern.py
+++ b/src/amdb/domain/services/access_concern.py
@@ -1,6 +1,8 @@
class AccessConcern:
def authorize(
- self, current_permissions: int, required_permissions: int
+ self,
+ current_permissions: int,
+ required_permissions: int,
) -> bool:
return (
current_permissions & required_permissions == required_permissions
diff --git a/src/amdb/infrastructure/auth/raw/identity_provider.py b/src/amdb/infrastructure/auth/raw/identity_provider.py
index 1032843..ec0556d 100644
--- a/src/amdb/infrastructure/auth/raw/identity_provider.py
+++ b/src/amdb/infrastructure/auth/raw/identity_provider.py
@@ -1,3 +1,5 @@
+from typing import Optional
+
from amdb.domain.entities.user import UserId
@@ -6,8 +8,11 @@ def __init__(self, user_id: UserId, permissions: int) -> None:
self._user_id = user_id
self._permissions = permissions
- def get_user_id(self) -> UserId:
+ def user_id(self) -> UserId:
+ return self._user_id
+
+ def user_id_or_none(self) -> Optional[UserId]:
return self._user_id
- def get_permissions(self) -> int:
+ def permissions(self) -> int:
return self._permissions
diff --git a/src/amdb/infrastructure/auth/session/config.py b/src/amdb/infrastructure/auth/session/config.py
index 016398e..8100d97 100644
--- a/src/amdb/infrastructure/auth/session/config.py
+++ b/src/amdb/infrastructure/auth/session/config.py
@@ -1,7 +1,19 @@
from dataclasses import dataclass
from datetime import timedelta
+from typing import Union
+from os import PathLike
+
+import toml
@dataclass(frozen=True, slots=True)
class SessionConfig:
- session_lifetime: timedelta
+ lifetime: timedelta
+
+ @classmethod
+ def from_toml(cls, path: Union[PathLike, str]) -> "SessionConfig":
+ toml_as_dict = toml.load(path)
+ session_section_as_dict = toml_as_dict["auth-session"]
+ return SessionConfig(
+ lifetime=session_section_as_dict["lifetime"],
+ )
diff --git a/src/amdb/infrastructure/auth/session/constants/exceptions.py b/src/amdb/infrastructure/auth/session/constants/exceptions.py
deleted file mode 100644
index b2d0e7a..0000000
--- a/src/amdb/infrastructure/auth/session/constants/exceptions.py
+++ /dev/null
@@ -1,2 +0,0 @@
-NO_SESSION_ID = "Session id is not passed"
-SESSION_DOES_NOT_EXIST = "Session doesn't exist"
diff --git a/src/amdb/infrastructure/auth/session/identity_provider.py b/src/amdb/infrastructure/auth/session/identity_provider.py
index 5c3eea8..0ab7ea3 100644
--- a/src/amdb/infrastructure/auth/session/identity_provider.py
+++ b/src/amdb/infrastructure/auth/session/identity_provider.py
@@ -1,15 +1,14 @@
from typing import Optional, cast
from amdb.domain.entities.user import UserId
-from amdb.infrastructure.persistence.redis.gateways.session import (
- RedisSessionGateway,
-)
-from amdb.infrastructure.persistence.redis.gateways.permissions import (
- RedisPermissionsGateway,
-)
+from amdb.application.common.gateways.permissions import PermissionsGateway
from amdb.infrastructure.exception import InfrastructureError
-from .constants.exceptions import NO_SESSION_ID, SESSION_DOES_NOT_EXIST
-from .model import SessionId
+from .session import SessionId, Session
+from .session_gateway import SessionGateway
+
+
+NO_SESSION_ID = "Session id is not passed"
+SESSION_DOES_NOT_EXIST = "Session doesn't exist"
class SessionIdentityProvider:
@@ -17,24 +16,29 @@ def __init__(
self,
*,
session_id: Optional[SessionId],
- session_gateway: RedisSessionGateway,
- permissions_gateway: RedisPermissionsGateway,
+ session_gateway: SessionGateway,
+ permissions_gateway: PermissionsGateway,
) -> None:
self._session_id = session_id
self._session_gateway = session_gateway
self._permissions_gateway = permissions_gateway
- def get_user_id(self) -> UserId:
- return self._get_user_id()
+ def user_id(self) -> UserId:
+ return self._session().user_id
+
+ def user_id_or_none(self) -> Optional[UserId]:
+ session = self._session_or_none()
+ return session.user_id if session else None
- def get_permissions(self) -> int:
- user_id = self._get_user_id()
- permissions = self._permissions_gateway.with_user_id(user_id)
+ def permissions(self) -> int:
+ session = self._session()
+
+ permissions = self._permissions_gateway.with_user_id(session.user_id)
permissions = cast(int, permissions)
return permissions
- def _get_user_id(self) -> UserId:
+ def _session(self) -> Session:
if not self._session_id:
raise InfrastructureError(NO_SESSION_ID)
@@ -42,4 +46,9 @@ def _get_user_id(self) -> UserId:
if not session:
raise InfrastructureError(SESSION_DOES_NOT_EXIST)
- return session.user_id
+ return session
+
+ def _session_or_none(self) -> Optional[Session]:
+ if self._session_id:
+ return self._session_gateway.with_id(self._session_id)
+ return None
diff --git a/src/amdb/infrastructure/auth/session/model.py b/src/amdb/infrastructure/auth/session/session.py
similarity index 100%
rename from src/amdb/infrastructure/auth/session/model.py
rename to src/amdb/infrastructure/auth/session/session.py
diff --git a/src/amdb/infrastructure/auth/session/session_gateway.py b/src/amdb/infrastructure/auth/session/session_gateway.py
new file mode 100644
index 0000000..979ba31
--- /dev/null
+++ b/src/amdb/infrastructure/auth/session/session_gateway.py
@@ -0,0 +1,8 @@
+from typing import Optional, Protocol
+
+from .session import SessionId, Session
+
+
+class SessionGateway(Protocol):
+ def with_id(self, session_id: SessionId) -> Optional[Session]:
+ raise NotImplementedError
diff --git a/src/amdb/infrastructure/auth/session/session_processor.py b/src/amdb/infrastructure/auth/session/session_processor.py
index 25da7c0..3890c10 100644
--- a/src/amdb/infrastructure/auth/session/session_processor.py
+++ b/src/amdb/infrastructure/auth/session/session_processor.py
@@ -1,13 +1,16 @@
from uuid import uuid4
from amdb.domain.entities.user import UserId
-from .model import SessionId, Session
+from .session import SessionId, Session
class SessionProcessor:
def create(self, user_id: UserId) -> Session:
- session_id = uuid4().hex + uuid4().hex
return Session(
- id=SessionId(session_id),
+ id=self._gen_session_id(),
user_id=user_id,
)
+
+ def _gen_session_id(self) -> SessionId:
+ random_value = uuid4().hex + uuid4().hex + uuid4().hex
+ return SessionId(random_value)
diff --git a/src/amdb/infrastructure/password_manager/hash_computer.py b/src/amdb/infrastructure/password_manager/hash_computer.py
new file mode 100644
index 0000000..a0b6881
--- /dev/null
+++ b/src/amdb/infrastructure/password_manager/hash_computer.py
@@ -0,0 +1,28 @@
+import hashlib
+
+
+class HashComputer:
+ _ALGORITHM = "sha256"
+ _ITERATIONS = 10000
+
+ def hash(self, value: bytes, salt: bytes) -> bytes:
+ return hashlib.pbkdf2_hmac(
+ hash_name=self._ALGORITHM,
+ password=value,
+ salt=salt,
+ iterations=self._ITERATIONS,
+ )
+
+ def verify(
+ self,
+ value: bytes,
+ hashed_value: bytes,
+ salt: bytes,
+ ) -> bool:
+ computed_hash = hashlib.pbkdf2_hmac(
+ hash_name=self._ALGORITHM,
+ password=value,
+ salt=salt,
+ iterations=self._ITERATIONS,
+ )
+ return computed_hash == hashed_value
diff --git a/src/amdb/infrastructure/password_manager/model.py b/src/amdb/infrastructure/password_manager/password_hash.py
similarity index 55%
rename from src/amdb/infrastructure/password_manager/model.py
rename to src/amdb/infrastructure/password_manager/password_hash.py
index 3e982ef..3cfc0ac 100644
--- a/src/amdb/infrastructure/password_manager/model.py
+++ b/src/amdb/infrastructure/password_manager/password_hash.py
@@ -1,10 +1,10 @@
from dataclasses import dataclass
from amdb.domain.entities.user import UserId
-from amdb.infrastructure.security.hasher import HashData
@dataclass(frozen=True, slots=True)
-class UserPasswordHash:
+class PasswordHash:
user_id: UserId
- password_hash: HashData
+ hash: bytes
+ salt: bytes
diff --git a/src/amdb/infrastructure/password_manager/password_hash_gateway.py b/src/amdb/infrastructure/password_manager/password_hash_gateway.py
new file mode 100644
index 0000000..7cbe02c
--- /dev/null
+++ b/src/amdb/infrastructure/password_manager/password_hash_gateway.py
@@ -0,0 +1,12 @@
+from typing import Protocol, Optional
+
+from amdb.domain.entities.user import UserId
+from amdb.infrastructure.password_manager.password_hash import PasswordHash
+
+
+class PasswordHashGateway(Protocol):
+ def with_user_id(self, user_id: UserId) -> Optional[PasswordHash]:
+ raise NotImplementedError
+
+ def save(self, password_hash: PasswordHash) -> None:
+ raise NotImplementedError
diff --git a/src/amdb/infrastructure/password_manager/password_manager.py b/src/amdb/infrastructure/password_manager/password_manager.py
index 254471f..e324f67 100644
--- a/src/amdb/infrastructure/password_manager/password_manager.py
+++ b/src/amdb/infrastructure/password_manager/password_manager.py
@@ -1,30 +1,47 @@
-from typing import cast
+import os
from amdb.domain.entities.user import UserId
-from amdb.infrastructure.persistence.sqlalchemy.gateways.user_password_hash import (
- SQLAlchemyUserPasswordHashGateway,
-)
-from amdb.infrastructure.security.hasher import Hasher
-from .model import UserPasswordHash
+from amdb.infrastructure.exception import InfrastructureError
+from .password_hash import PasswordHash
+from .password_hash_gateway import PasswordHashGateway
+from .hash_computer import HashComputer
+
+
+PASSWORD_HASH_DOES_NOT_EXIST = "Password hash doesn't exist"
class HashingPasswordManager:
def __init__(
self,
- hasher: Hasher,
- user_password_hash_gateway: SQLAlchemyUserPasswordHashGateway,
+ hash_computer: HashComputer,
+ password_hash_gateway: PasswordHashGateway,
) -> None:
- self._hasher = hasher
- self._user_password_hash_gateway = user_password_hash_gateway
+ self._hash_computer = hash_computer
+ self._password_hash_gateway = password_hash_gateway
def set(self, user_id: UserId, password: str) -> None:
- password_hash = self._hasher.hash(password.encode())
- user_password_hash = UserPasswordHash(user_id, password_hash)
- self._user_password_hash_gateway.save(user_password_hash)
+ salt = self._gen_random_bytes()
+ hash = self._hash_computer.hash(
+ value=password.encode(),
+ salt=salt,
+ )
+ password_hash = PasswordHash(
+ user_id=user_id,
+ hash=hash,
+ salt=salt,
+ )
+ self._password_hash_gateway.save(password_hash)
def verify(self, user_id: UserId, password: str) -> bool:
- user_password_hash = self._user_password_hash_gateway.get(user_id)
- user_password_hash = cast(UserPasswordHash, user_password_hash)
- return self._hasher.verify(
- password.encode(), user_password_hash.password_hash
+ password_hash = self._password_hash_gateway.with_user_id(user_id)
+ if not password_hash:
+ raise InfrastructureError(PASSWORD_HASH_DOES_NOT_EXIST)
+
+ return self._hash_computer.verify(
+ value=password.encode(),
+ hashed_value=password_hash.hash,
+ salt=password_hash.salt,
)
+
+ def _gen_random_bytes(self) -> bytes:
+ return os.urandom(32)
diff --git a/src/amdb/infrastructure/persistence/alembic/config.py b/src/amdb/infrastructure/persistence/alembic/config.py
index 5d7f07d..ea4e137 100644
--- a/src/amdb/infrastructure/persistence/alembic/config.py
+++ b/src/amdb/infrastructure/persistence/alembic/config.py
@@ -5,6 +5,6 @@
ALEMBIC_CONFIG = str(
importlib.resources.files(
- amdb.infrastructure.persistence.alembic
+ amdb.infrastructure.persistence.alembic,
).joinpath("alembic.ini"),
)
diff --git a/src/amdb/infrastructure/persistence/alembic/migrations/versions/65f8840f4494_.py b/src/amdb/infrastructure/persistence/alembic/migrations/versions/65f8840f4494_.py
index fa79316..11fb0b3 100644
--- a/src/amdb/infrastructure/persistence/alembic/migrations/versions/65f8840f4494_.py
+++ b/src/amdb/infrastructure/persistence/alembic/migrations/versions/65f8840f4494_.py
@@ -102,15 +102,18 @@ def upgrade() -> None:
)
with op.batch_alter_table("ratings") as batch_op:
batch_op.add_column(
- sa.Column("id", sa.Uuid(), nullable=False, default="uuid7()")
+ sa.Column("id", sa.Uuid(), nullable=False, default="uuid7()"),
)
batch_op.drop_constraint("pk_ratings", type_="primary")
batch_op.create_primary_key("pk_ratings", ["id"])
batch_op.create_unique_constraint(
- "uq_ratings", ("user_id", "movie_id")
+ "uq_ratings",
+ ("user_id", "movie_id"),
)
op.create_unique_constraint(
- "uq_reviews", "reviews", ("user_id", "movie_id")
+ "uq_reviews",
+ "reviews",
+ ("user_id", "movie_id"),
)
diff --git a/src/amdb/infrastructure/persistence/alembic/migrations/versions/85a348467b90_.py b/src/amdb/infrastructure/persistence/alembic/migrations/versions/85a348467b90_.py
index 853078e..77d5d02 100644
--- a/src/amdb/infrastructure/persistence/alembic/migrations/versions/85a348467b90_.py
+++ b/src/amdb/infrastructure/persistence/alembic/migrations/versions/85a348467b90_.py
@@ -36,7 +36,9 @@ def upgrade() -> None:
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(
- ["movie_id"], ["movies.id"], ondelete="CASCADE"
+ ["movie_id"],
+ ["movies.id"],
+ ondelete="CASCADE",
),
sa.PrimaryKeyConstraint("id"),
)
diff --git a/src/amdb/infrastructure/persistence/alembic/migrations/versions/9e92de201574_.py b/src/amdb/infrastructure/persistence/alembic/migrations/versions/9e92de201574_.py
index 22b25a5..be2a42b 100644
--- a/src/amdb/infrastructure/persistence/alembic/migrations/versions/9e92de201574_.py
+++ b/src/amdb/infrastructure/persistence/alembic/migrations/versions/9e92de201574_.py
@@ -50,7 +50,9 @@ def upgrade() -> None:
sa.Column("value", sa.Float(), nullable=False),
sa.Column("created_at", sa.TIMESTAMP(timezone=True), nullable=True),
sa.ForeignKeyConstraint(
- ["movie_id"], ["movies.id"], ondelete="CASCADE"
+ ["movie_id"],
+ ["movies.id"],
+ ondelete="CASCADE",
),
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("movie_id", "user_id"),
diff --git a/src/amdb/infrastructure/persistence/redis/config.py b/src/amdb/infrastructure/persistence/redis/config.py
index c623ebe..3a4abc8 100644
--- a/src/amdb/infrastructure/persistence/redis/config.py
+++ b/src/amdb/infrastructure/persistence/redis/config.py
@@ -1,9 +1,16 @@
from dataclasses import dataclass
+from typing import Union
+from os import PathLike
+
+import toml
@dataclass(frozen=True, slots=True)
class RedisConfig:
- host: str
- port: int
- db: int
- password: str
+ url: str
+
+ @classmethod
+ def from_toml(cls, path: Union[PathLike, str]) -> "RedisConfig":
+ toml_as_dict = toml.load(path)
+ redis_section_as_dict = toml_as_dict["redis"]
+ return RedisConfig(url=redis_section_as_dict["url"])
diff --git a/src/amdb/infrastructure/auth/session/constants/__init__.py b/src/amdb/infrastructure/persistence/redis/mappers/__init__.py
similarity index 100%
rename from src/amdb/infrastructure/auth/session/constants/__init__.py
rename to src/amdb/infrastructure/persistence/redis/mappers/__init__.py
diff --git a/src/amdb/infrastructure/persistence/redis/gateways/permissions.py b/src/amdb/infrastructure/persistence/redis/mappers/permissions.py
similarity index 90%
rename from src/amdb/infrastructure/persistence/redis/gateways/permissions.py
rename to src/amdb/infrastructure/persistence/redis/mappers/permissions.py
index 35fcaf8..e5554bd 100644
--- a/src/amdb/infrastructure/persistence/redis/gateways/permissions.py
+++ b/src/amdb/infrastructure/persistence/redis/mappers/permissions.py
@@ -1,18 +1,18 @@
-from typing import Optional
+from typing import Optional, cast
from redis.client import Redis
from amdb.domain.entities.user import UserId
-class RedisPermissionsGateway:
+class PermissionsMapper:
def __init__(self, redis: Redis) -> None:
self._redis = redis
def with_user_id(self, user_id: UserId) -> Optional[int]:
permissions = self._redis.get(f"permissions:user_id:{user_id.hex}")
if permissions:
- return int(permissions)
+ return int(cast(str, permissions))
return None
def set(self, user_id: UserId, permissions: int) -> None:
@@ -54,7 +54,7 @@ def for_rate_movie(self) -> int:
def for_unrate_movie(self) -> int:
return 4
- def for_get_movie_reviews(self) -> int:
+ def for_get_reviews(self) -> int:
return 4
def for_get_my_reviews(self) -> int:
diff --git a/src/amdb/infrastructure/persistence/redis/gateways/session.py b/src/amdb/infrastructure/persistence/redis/mappers/session.py
similarity index 83%
rename from src/amdb/infrastructure/persistence/redis/gateways/session.py
rename to src/amdb/infrastructure/persistence/redis/mappers/session.py
index 40dbd4f..72b0714 100644
--- a/src/amdb/infrastructure/persistence/redis/gateways/session.py
+++ b/src/amdb/infrastructure/persistence/redis/mappers/session.py
@@ -1,14 +1,14 @@
from datetime import timedelta
-from typing import Optional
+from typing import Optional, cast
from uuid import UUID
from redis import Redis
from amdb.domain.entities.user import UserId
-from amdb.infrastructure.auth.session.model import SessionId, Session
+from amdb.infrastructure.auth.session.session import SessionId, Session
-class RedisSessionGateway:
+class SessionMapper:
def __init__(
self,
*,
@@ -34,6 +34,6 @@ def with_id(self, session_id: SessionId) -> Optional[Session]:
if user_id:
return Session(
id=session_id,
- user_id=UserId(UUID(user_id)),
+ user_id=UserId(UUID(cast(str, user_id))),
)
return None
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/config.py b/src/amdb/infrastructure/persistence/sqlalchemy/config.py
index 7445a03..5a44813 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/config.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/config.py
@@ -1,20 +1,16 @@
from dataclasses import dataclass
+from typing import Union
+from os import PathLike
+
+import toml
@dataclass(frozen=True, slots=True)
class PostgresConfig:
- host: str
- port: str
- name: str
- user: str
- password: str
+ url: str
- @property
- def dsn(self) -> str:
- return "postgresql://{}:{}@{}:{}/{}".format(
- self.user,
- self.password,
- self.host,
- self.port,
- self.name,
- )
+ @classmethod
+ def from_toml(cls, path: Union[PathLike, str]) -> "PostgresConfig":
+ toml_as_dict = toml.load(path)
+ postgres_section_as_dict = toml_as_dict["postgres"]
+ return PostgresConfig(url=postgres_section_as_dict["url"])
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/gateway_factory.py b/src/amdb/infrastructure/persistence/sqlalchemy/gateway_factory.py
deleted file mode 100644
index b30327c..0000000
--- a/src/amdb/infrastructure/persistence/sqlalchemy/gateway_factory.py
+++ /dev/null
@@ -1,49 +0,0 @@
-from contextlib import contextmanager
-from typing import Iterator
-
-from sqlalchemy.orm import Session, sessionmaker
-
-from .gateways.user import SQLAlchemyUserGateway
-from .gateways.movie import SQLAlchemyMovieGateway
-from .gateways.rating import SQLAlchemyRatingGateway
-from .gateways.user_password_hash import SQLAlchemyUserPasswordHashGateway
-from .gateways.review import SQLAlchemyReviewGateway
-from .mappers.user import UserMapper
-from .mappers.movie import MovieMapper
-from .mappers.rating import RatingMapper
-from .mappers.user_password_hash import UserPasswordHashMapper
-from .mappers.review import ReviewMapper
-
-
-@contextmanager
-def build_sqlalchemy_gateway_factory(
- sessionmaker: sessionmaker[Session],
-) -> Iterator["SQLAlchemyGatewayFactory"]:
- session = sessionmaker()
- yield SQLAlchemyGatewayFactory(session)
- session.close()
-
-
-class SQLAlchemyGatewayFactory:
- def __init__(self, session: Session) -> None:
- self._session = session
-
- def user(self) -> SQLAlchemyUserGateway:
- return SQLAlchemyUserGateway(self._session, UserMapper())
-
- def movie(self) -> SQLAlchemyMovieGateway:
- return SQLAlchemyMovieGateway(self._session, MovieMapper())
-
- def rating(self) -> SQLAlchemyRatingGateway:
- return SQLAlchemyRatingGateway(self._session, RatingMapper())
-
- def user_password_hash(self) -> SQLAlchemyUserPasswordHashGateway:
- return SQLAlchemyUserPasswordHashGateway(
- self._session, UserPasswordHashMapper()
- )
-
- def review(self) -> SQLAlchemyReviewGateway:
- return SQLAlchemyReviewGateway(self._session, ReviewMapper())
-
- def unit_of_work(self) -> Session:
- return self._session
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/movie.py b/src/amdb/infrastructure/persistence/sqlalchemy/gateways/movie.py
deleted file mode 100644
index b7e38a6..0000000
--- a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/movie.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from typing import Optional
-
-from sqlalchemy import select
-from sqlalchemy.orm.session import Session
-
-from amdb.domain.entities.movie import MovieId, Movie as MovieEntity
-from amdb.infrastructure.persistence.sqlalchemy.models.movie import (
- Movie as MovieModel,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.movie import (
- MovieMapper,
-)
-
-
-class SQLAlchemyMovieGateway:
- def __init__(
- self,
- session: Session,
- mapper: MovieMapper,
- ) -> None:
- self._session = session
- self._mapper = mapper
-
- def with_id(self, movie_id: MovieId) -> Optional[MovieEntity]:
- movie_model = self._session.get(MovieModel, movie_id)
- if movie_model:
- return self._mapper.to_entity(movie_model)
- return None
-
- def save(self, movie: MovieEntity) -> None:
- movie_model = self._mapper.to_model(movie)
- self._session.add(movie_model)
-
- def update(self, movie: MovieEntity) -> None:
- movie_model = self._mapper.to_model(movie)
- self._session.merge(movie_model)
-
- def delete(self, movie: MovieEntity) -> None:
- movie_model = self._session.get(MovieModel, movie.id)
- if movie_model:
- self._session.delete(movie_model)
-
- def list(self, limit: int, offset: int) -> list[MovieEntity]:
- statement = select(MovieModel).limit(limit).offset(offset)
- movie_models = self._session.scalars(statement)
-
- movie_entities = []
- for movie_model in movie_models:
- movie_entity = self._mapper.to_entity(movie_model)
- movie_entities.append(movie_entity)
-
- return movie_entities
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/rating.py b/src/amdb/infrastructure/persistence/sqlalchemy/gateways/rating.py
deleted file mode 100644
index c20f165..0000000
--- a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/rating.py
+++ /dev/null
@@ -1,101 +0,0 @@
-from typing import Optional
-
-from sqlalchemy import select, delete, and_
-from sqlalchemy.orm.session import Session
-
-from amdb.domain.entities.user import UserId
-from amdb.domain.entities.movie import MovieId
-from amdb.domain.entities.rating import RatingId, Rating as RatingEntity
-from amdb.infrastructure.persistence.sqlalchemy.models.rating import (
- Rating as RatingModel,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.rating import (
- RatingMapper,
-)
-
-
-class SQLAlchemyRatingGateway:
- def __init__(
- self,
- session: Session,
- mapper: RatingMapper,
- ) -> None:
- self._session = session
- self._mapper = mapper
-
- def with_id(self, id: RatingId) -> Optional[RatingEntity]:
- rating_model = self._session.get(RatingModel, id)
- if rating_model:
- return self._mapper.to_entity(rating_model)
- return None
-
- def with_user_id_and_movie_id(
- self,
- user_id: UserId,
- movie_id: MovieId,
- ) -> Optional[RatingEntity]:
- statement = select(RatingModel).where(
- and_(
- RatingModel.user_id == user_id,
- RatingModel.movie_id == movie_id,
- ),
- )
- rating_model = self._session.scalar(statement)
- if rating_model:
- return self._mapper.to_entity(rating_model)
- return None
-
- def list_with_movie_id(
- self,
- movie_id: MovieId,
- limit: int,
- offset: int,
- ) -> list[RatingEntity]:
- statement = (
- select(RatingModel)
- .where(RatingModel.movie_id == movie_id)
- .limit(limit)
- .offset(offset)
- )
- rating_models = self._session.scalars(statement)
-
- rating_entities = []
- for rating_model in rating_models:
- rating_entity = self._mapper.to_entity(rating_model)
- rating_entities.append(rating_entity)
-
- return rating_entities
-
- def list_with_user_id(
- self,
- user_id: UserId,
- limit: int,
- offset: int,
- ) -> list[RatingEntity]:
- statement = (
- select(RatingModel)
- .where(RatingModel.user_id == user_id)
- .limit(limit)
- .offset(offset)
- )
- rating_models = self._session.scalars(statement)
-
- rating_entities = []
- for rating_model in rating_models:
- rating_entity = self._mapper.to_entity(rating_model)
- rating_entities.append(rating_entity)
-
- return rating_entities
-
- def save(self, rating: RatingEntity) -> None:
- rating_model = self._mapper.to_model(rating)
- self._session.add(rating_model)
-
- def delete(self, rating: RatingEntity) -> None:
- rating_model = self._session.get(RatingModel, rating.id)
- if rating_model:
- self._session.delete(rating_model)
-
- def delete_with_movie_id(self, movie_id: MovieId) -> None:
- statement = delete(RatingModel).where(RatingModel.movie_id == movie_id)
- self._session.execute(statement)
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/review.py b/src/amdb/infrastructure/persistence/sqlalchemy/gateways/review.py
deleted file mode 100644
index 5c76ea7..0000000
--- a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/review.py
+++ /dev/null
@@ -1,96 +0,0 @@
-from typing import Optional
-
-from sqlalchemy import select, delete, and_
-from sqlalchemy.orm.session import Session
-
-from amdb.domain.entities.user import UserId
-from amdb.domain.entities.movie import MovieId
-from amdb.domain.entities.review import ReviewId, Review as ReviewEntity
-from amdb.infrastructure.persistence.sqlalchemy.models.review import (
- Review as ReviewModel,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.review import (
- ReviewMapper,
-)
-
-
-class SQLAlchemyReviewGateway:
- def __init__(
- self,
- session: Session,
- mapper: ReviewMapper,
- ) -> None:
- self._session = session
- self._mapper = mapper
-
- def with_id(self, review_id: ReviewId) -> Optional[ReviewEntity]:
- review_model = self._session.get(ReviewModel, review_id)
- if review_model:
- return self._mapper.to_entity(review_model)
- return None
-
- def with_movie_id_and_user_id(
- self,
- user_id: UserId,
- movie_id: MovieId,
- ) -> Optional[ReviewEntity]:
- statement = select(ReviewModel).where(
- and_(
- ReviewModel.user_id == user_id,
- ReviewModel.movie_id == movie_id,
- ),
- )
- review_model = self._session.scalar(statement)
- if review_model:
- return self._mapper.to_entity(review_model)
- return None
-
- def list_with_movie_id(
- self,
- movie_id: MovieId,
- limit: int,
- offset: int,
- ) -> list[ReviewEntity]:
- statement = (
- select(ReviewModel)
- .where(ReviewModel.movie_id == movie_id)
- .limit(limit)
- .offset(offset)
- )
- review_models = self._session.scalars(statement)
-
- review_entities = []
- for review_model in review_models:
- review_entity = self._mapper.to_entity(review_model)
- review_entities.append(review_entity)
-
- return review_entities
-
- def list_with_user_id(
- self,
- user_id: UserId,
- limit: int,
- offset: int,
- ) -> list[ReviewEntity]:
- statement = (
- select(ReviewModel)
- .where(ReviewModel.user_id == user_id)
- .limit(limit)
- .offset(offset)
- )
- review_models = self._session.scalars(statement)
-
- review_entities = []
- for review_model in review_models:
- review_entity = self._mapper.to_entity(review_model)
- review_entities.append(review_entity)
-
- return review_entities
-
- def save(self, review: ReviewEntity) -> None:
- review_model = self._mapper.to_model(review)
- self._session.add(review_model)
-
- def delete_with_movie_id(self, movie_id: MovieId) -> None:
- statement = delete(ReviewModel).where(ReviewModel.movie_id == movie_id)
- self._session.execute(statement)
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/user.py b/src/amdb/infrastructure/persistence/sqlalchemy/gateways/user.py
deleted file mode 100644
index 2d22d1b..0000000
--- a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/user.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from typing import Optional
-
-from sqlalchemy import select
-from sqlalchemy.orm.session import Session
-
-from amdb.domain.entities.user import UserId, User as UserEntity
-from amdb.infrastructure.persistence.sqlalchemy.models.user import (
- User as UserModel,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.user import UserMapper
-
-
-class SQLAlchemyUserGateway:
- def __init__(
- self,
- session: Session,
- mapper: UserMapper,
- ) -> None:
- self._session = session
- self._mapper = mapper
-
- def with_id(self, user_id: UserId) -> Optional[UserEntity]:
- user_model = self._session.get(UserModel, user_id)
- if user_model:
- return self._mapper.to_entity(user_model)
- return None
-
- def with_name(self, user_name: str) -> Optional[UserEntity]:
- statement = select(UserModel).where(UserModel.name == user_name)
- user_model = self._session.scalar(statement)
- if user_model:
- return self._mapper.to_entity(user_model)
- return None
-
- def save(self, user: UserEntity) -> None:
- user_model = self._mapper.to_model(user)
- self._session.add(user_model)
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/user_password_hash.py b/src/amdb/infrastructure/persistence/sqlalchemy/gateways/user_password_hash.py
deleted file mode 100644
index 31b3980..0000000
--- a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/user_password_hash.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from typing import Optional
-
-from sqlalchemy.orm.session import Session
-
-from amdb.domain.entities.user import UserId
-from amdb.infrastructure.persistence.sqlalchemy.models.user_password_hash import (
- UserPasswordHash as UserPasswordHashModel,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.user_password_hash import (
- UserPasswordHashMapper,
-)
-from amdb.infrastructure.password_manager.model import UserPasswordHash
-
-
-class SQLAlchemyUserPasswordHashGateway:
- def __init__(
- self,
- session: Session,
- mapper: UserPasswordHashMapper,
- ) -> None:
- self._session = session
- self._mapper = mapper
-
- def get(self, user_id: UserId) -> Optional[UserPasswordHash]:
- user_password_hash_model = self._session.get(
- UserPasswordHashModel, user_id
- )
- if user_password_hash_model:
- return self._mapper.to_password_manager_model(
- user_password_hash_model
- )
- return None
-
- def save(self, user_password_hash: UserPasswordHash) -> None:
- user_password_hash_model = self._mapper.to_model(user_password_hash)
- self._session.add(user_password_hash_model)
- self._session.commit()
diff --git a/src/amdb/infrastructure/persistence/redis/gateways/__init__.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/__init__.py
similarity index 100%
rename from src/amdb/infrastructure/persistence/redis/gateways/__init__.py
rename to src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/__init__.py
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/movie.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/movie.py
new file mode 100644
index 0000000..67ca571
--- /dev/null
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/movie.py
@@ -0,0 +1,53 @@
+from typing import Annotated, Optional
+
+from sqlalchemy import Connection, Row, select, insert, update, delete
+
+from amdb.domain.entities.movie import MovieId, Movie
+from amdb.infrastructure.persistence.sqlalchemy.models.movie import MovieModel
+
+
+class MovieMapper:
+ def __init__(self, connection: Connection) -> None:
+ self._connection = connection
+
+ def with_id(self, movie_id: MovieId) -> Optional[Movie]:
+ statement = select(MovieModel).where(MovieModel.id == movie_id)
+ row = self._connection.execute(statement).one_or_none()
+ if row:
+ return self._to_entity(row) # type: ignore
+ return None
+
+ def save(self, movie: Movie) -> None:
+ statement = insert(MovieModel).values(
+ id=movie.id,
+ title=movie.title,
+ release_date=movie.release_date,
+ rating=movie.rating,
+ rating_count=movie.rating_count,
+ )
+ self._connection.execute(statement)
+
+ def update(self, movie: Movie) -> None:
+ statement = update(MovieModel).values(
+ title=movie.title,
+ release_date=movie.release_date,
+ rating=movie.rating,
+ rating_count=movie.rating_count,
+ )
+ self._connection.execute(statement)
+
+ def delete(self, movie: Movie) -> None:
+ statement = delete(MovieModel).where(MovieModel.id == movie.id)
+ self._connection.execute(statement)
+
+ def _to_entity(
+ self,
+ row: Annotated[MovieModel, Row[tuple[MovieModel]]],
+ ) -> Movie:
+ return Movie(
+ id=MovieId(row.id),
+ title=row.title,
+ release_date=row.release_date,
+ rating=row.rating,
+ rating_count=row.rating_count,
+ )
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/rating.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/rating.py
new file mode 100644
index 0000000..846e8f5
--- /dev/null
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/rating.py
@@ -0,0 +1,68 @@
+from typing import Annotated, Optional
+
+from sqlalchemy import Connection, Row, select, insert, delete, and_
+
+from amdb.domain.entities.user import UserId
+from amdb.domain.entities.movie import MovieId
+from amdb.domain.entities.rating import RatingId, Rating
+from amdb.infrastructure.persistence.sqlalchemy.models.rating import (
+ RatingModel,
+)
+
+
+class RatingMapper:
+ def __init__(self, connection: Connection) -> None:
+ self._connection = connection
+
+ def with_id(self, rating_id: RatingId) -> Optional[Rating]:
+ statement = select(RatingModel).where(RatingModel.id == rating_id)
+ row = self._connection.execute(statement).one_or_none()
+ if row:
+ return self._to_entity(row) # type: ignore
+ return None
+
+ def with_user_id_and_movie_id(
+ self,
+ user_id: UserId,
+ movie_id: MovieId,
+ ) -> Optional[Rating]:
+ statement = select(RatingModel).where(
+ and_(
+ RatingModel.user_id == user_id,
+ RatingModel.movie_id == movie_id,
+ ),
+ )
+ row = self._connection.execute(statement).one_or_none()
+ if row:
+ return self._to_entity(row) # type: ignore
+ return None
+
+ def save(self, rating: Rating) -> None:
+ statement = insert(RatingModel).values(
+ id=rating.id,
+ movie_id=rating.movie_id,
+ user_id=rating.user_id,
+ value=rating.value,
+ created_at=rating.created_at,
+ )
+ self._connection.execute(statement)
+
+ def delete(self, rating: Rating) -> None:
+ statement = delete(RatingModel).where(RatingModel.id == rating.id)
+ self._connection.execute(statement)
+
+ def delete_with_movie_id(self, movie_id: MovieId) -> None:
+ statement = delete(RatingModel).where(RatingModel.movie_id == movie_id)
+ self._connection.execute(statement)
+
+ def _to_entity(
+ self,
+ row: Annotated[RatingModel, Row[tuple[RatingModel]]],
+ ) -> Rating:
+ return Rating(
+ id=RatingId(row.id),
+ movie_id=MovieId(row.movie_id),
+ user_id=UserId(row.user_id),
+ value=row.value,
+ created_at=row.created_at,
+ )
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/review.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/review.py
new file mode 100644
index 0000000..d08255d
--- /dev/null
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/review.py
@@ -0,0 +1,68 @@
+from typing import Annotated, Optional
+
+from sqlalchemy import Connection, Row, select, insert, delete, and_
+
+from amdb.domain.entities.user import UserId
+from amdb.domain.entities.movie import MovieId
+from amdb.domain.entities.review import ReviewId, ReviewType, Review
+from amdb.infrastructure.persistence.sqlalchemy.models.review import (
+ ReviewModel,
+)
+
+
+class ReviewMapper:
+ def __init__(self, connection: Connection) -> None:
+ self._connection = connection
+
+ def with_id(self, review_id: ReviewId) -> Optional[Review]:
+ statement = select(ReviewModel).where(ReviewModel.id == review_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,
+ movie_id: MovieId,
+ ) -> Optional[Review]:
+ statement = select(ReviewModel).where(
+ and_(
+ ReviewModel.user_id == user_id,
+ ReviewModel.movie_id == movie_id,
+ ),
+ )
+ row = self._connection.execute(statement).one_or_none()
+ if row:
+ return self._to_entity(row) # type: ignore
+ return None
+
+ def save(self, review: Review) -> None:
+ statement = insert(ReviewModel).values(
+ id=review.id,
+ user_id=review.user_id,
+ movie_id=review.movie_id,
+ title=review.title,
+ content=review.content,
+ type=review.type.value,
+ created_at=review.created_at,
+ )
+ self._connection.execute(statement)
+
+ def delete_with_movie_id(self, movie_id: MovieId) -> None:
+ statement = delete(ReviewModel).where(ReviewModel.movie_id == movie_id)
+ self._connection.execute(statement)
+
+ def _to_entity(
+ self,
+ row: Annotated[ReviewModel, Row[tuple[ReviewModel]]],
+ ) -> Review:
+ return Review(
+ id=ReviewId(row.id),
+ user_id=UserId(row.user_id),
+ movie_id=MovieId(row.movie_id),
+ title=row.title,
+ content=row.content,
+ type=ReviewType(row.type),
+ created_at=row.created_at,
+ )
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/user.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/user.py
new file mode 100644
index 0000000..db427ac
--- /dev/null
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/user.py
@@ -0,0 +1,41 @@
+from typing import Annotated, Optional
+
+from sqlalchemy import Connection, Row, select, insert
+
+from amdb.domain.entities.user import UserId, User
+from amdb.infrastructure.persistence.sqlalchemy.models.user import UserModel
+
+
+class UserMapper:
+ def __init__(self, connection: Connection) -> None:
+ self._connection = connection
+
+ def with_id(self, user_id: UserId) -> Optional[User]:
+ statement = select(UserModel).where(UserModel.id == user_id)
+ row = self._connection.execute(statement).one_or_none()
+ if row:
+ return self._to_entity(row) # type: ignore
+ return None
+
+ def with_name(self, user_name: str) -> Optional[User]:
+ statement = select(UserModel).where(UserModel.name == user_name)
+ row = self._connection.execute(statement).one_or_none()
+ if row:
+ return self._to_entity(row) # type: ignore
+ return None
+
+ def save(self, user: User) -> None:
+ statement = insert(UserModel).values(
+ id=UserId(user.id),
+ name=user.name,
+ )
+ self._connection.execute(statement)
+
+ def _to_entity(
+ self,
+ row: Annotated[UserModel, Row[tuple[UserModel]]],
+ ) -> User:
+ return User(
+ id=UserId(row.id),
+ name=row.name,
+ )
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/movie.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/movie.py
deleted file mode 100644
index 520ab2f..0000000
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/movie.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from amdb.domain.entities.movie import MovieId, Movie as MovieEntity
-from amdb.infrastructure.persistence.sqlalchemy.models.movie import (
- Movie as MovieModel,
-)
-
-
-class MovieMapper:
- def to_model(self, movie: MovieEntity) -> MovieModel:
- return MovieModel(
- id=movie.id,
- title=movie.title,
- release_date=movie.release_date,
- rating=movie.rating,
- rating_count=movie.rating_count,
- )
-
- def to_entity(self, movie: MovieModel) -> MovieEntity:
- return MovieEntity(
- id=MovieId(movie.id),
- title=movie.title,
- release_date=movie.release_date,
- rating=movie.rating,
- rating_count=movie.rating_count,
- )
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/password_hash.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/password_hash.py
new file mode 100644
index 0000000..9688223
--- /dev/null
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/password_hash.py
@@ -0,0 +1,41 @@
+from typing import Annotated, Optional
+
+from sqlalchemy import Connection, Row, select, insert
+
+from amdb.domain.entities.user import UserId
+from amdb.infrastructure.password_manager.password_hash import PasswordHash
+from amdb.infrastructure.persistence.sqlalchemy.models.password_hash import (
+ PasswordHashModel,
+)
+
+
+class PasswordHashMapper:
+ def __init__(self, connection: Connection) -> None:
+ self._connection = connection
+
+ def with_user_id(self, user_id: UserId) -> Optional[PasswordHash]:
+ statement = select(PasswordHashModel).where(
+ PasswordHashModel.user_id == user_id,
+ )
+ row = self._connection.execute(statement).one_or_none()
+ if row:
+ return self._to_data_structure(row) # type: ignore
+ return None
+
+ def save(self, password_hash: PasswordHash) -> None:
+ statement = insert(PasswordHashModel).values(
+ user_id=password_hash.user_id,
+ hash=password_hash.hash,
+ salt=password_hash.salt,
+ )
+ self._connection.execute(statement)
+
+ def _to_data_structure(
+ self,
+ row: Annotated[PasswordHashModel, Row],
+ ) -> PasswordHash:
+ return PasswordHash(
+ user_id=UserId(row.user_id),
+ hash=row.hash,
+ salt=row.salt,
+ )
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/rating.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/rating.py
deleted file mode 100644
index ed34246..0000000
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/rating.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from amdb.domain.entities.user import UserId
-from amdb.domain.entities.movie import MovieId
-from amdb.domain.entities.rating import RatingId, Rating as RatingEntity
-from amdb.infrastructure.persistence.sqlalchemy.models.rating import (
- Rating as RatingModel,
-)
-
-
-class RatingMapper:
- def to_model(self, rating: RatingEntity) -> RatingModel:
- return RatingModel(
- id=rating.id,
- movie_id=rating.movie_id,
- user_id=rating.user_id,
- value=rating.value,
- created_at=rating.created_at,
- )
-
- def to_entity(self, rating: RatingModel) -> RatingEntity:
- return RatingEntity(
- id=RatingId(rating.id),
- movie_id=MovieId(rating.movie_id),
- user_id=UserId(rating.user_id),
- value=rating.value,
- created_at=rating.created_at,
- )
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/review.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/review.py
deleted file mode 100644
index 8a28c94..0000000
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/review.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from amdb.domain.entities.user import UserId
-from amdb.domain.entities.movie import MovieId
-from amdb.domain.entities.review import (
- ReviewId,
- ReviewType,
- Review as ReviewEntity,
-)
-from amdb.infrastructure.persistence.sqlalchemy.models.review import (
- Review as ReviewModel,
-)
-
-
-class ReviewMapper:
- def to_model(self, review: ReviewEntity) -> ReviewModel:
- return ReviewModel(
- id=review.id,
- user_id=review.user_id,
- movie_id=review.movie_id,
- title=review.title,
- content=review.content,
- type=review.type.value,
- created_at=review.created_at,
- )
-
- def to_entity(self, review: ReviewModel) -> ReviewEntity:
- return ReviewEntity(
- id=ReviewId(review.id),
- user_id=UserId(review.user_id),
- movie_id=MovieId(review.movie_id),
- title=review.title,
- content=review.content,
- type=ReviewType(review.type),
- created_at=review.created_at,
- )
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/user.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/user.py
deleted file mode 100644
index 86f28fc..0000000
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/user.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from amdb.domain.entities.user import UserId, User as UserEntity
-from amdb.infrastructure.persistence.sqlalchemy.models.user import (
- User as UserModel,
-)
-
-
-class UserMapper:
- def to_model(self, user: UserEntity) -> UserModel:
- return UserModel(
- id=user.id,
- name=user.name,
- )
-
- def to_entity(self, user: UserModel) -> UserEntity:
- return UserEntity(
- id=UserId(user.id),
- name=user.name,
- )
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/user_password_hash.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/user_password_hash.py
deleted file mode 100644
index 7f0e15e..0000000
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/user_password_hash.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from amdb.domain.entities.user import UserId
-from amdb.infrastructure.persistence.sqlalchemy.models.user_password_hash import (
- UserPasswordHash as UserPasswordHashModel,
-)
-from amdb.infrastructure.security.hasher import HashData
-from amdb.infrastructure.password_manager.model import UserPasswordHash
-
-
-class UserPasswordHashMapper:
- def to_model(
- self,
- user_password_hash: UserPasswordHash,
- ) -> UserPasswordHashModel:
- return UserPasswordHashModel(
- user_id=user_password_hash.user_id,
- hash=user_password_hash.password_hash.hash,
- salt=user_password_hash.password_hash.salt,
- )
-
- def to_password_manager_model(
- self,
- user_password_hash: UserPasswordHashModel,
- ) -> UserPasswordHash:
- password_hash = HashData(
- hash=user_password_hash.hash,
- salt=user_password_hash.salt,
- )
- return UserPasswordHash(
- user_id=UserId(user_password_hash.user_id),
- password_hash=password_hash,
- )
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/gateways/__init__.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/__init__.py
similarity index 100%
rename from src/amdb/infrastructure/persistence/sqlalchemy/gateways/__init__.py
rename to src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/__init__.py
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/detailed_movie.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/detailed_movie.py
new file mode 100644
index 0000000..de14469
--- /dev/null
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/detailed_movie.py
@@ -0,0 +1,119 @@
+__all__ = ("DetailedMovieViewModelMapper",)
+
+from typing import Optional, TypedDict
+from datetime import date, datetime
+from uuid import UUID
+
+from sqlalchemy import Connection, Row, text
+
+from amdb.domain.entities.user import UserId
+from amdb.domain.entities.movie import MovieId
+from amdb.domain.entities.rating import RatingId
+from amdb.domain.entities.review import ReviewId, ReviewType
+from amdb.application.common.view_models.detailed_movie import (
+ UserRating,
+ UserReview,
+ DetailedMovieViewModel,
+)
+
+
+class RowAsDict(TypedDict):
+ movie_id: UUID
+ movie_title: str
+ movie_release_date: date
+ movie_rating: float
+ movie_rating_count: int
+ user_rating_id: Optional[UUID]
+ user_rating_value: Optional[float]
+ user_rating_created_at: Optional[datetime]
+ user_review_id: Optional[UUID]
+ user_review_title: Optional[str]
+ user_review_content: Optional[str]
+ user_review_type: Optional[int]
+ user_review_created_at: Optional[datetime]
+
+ @classmethod # type: ignore
+ def from_row(cls, row: Row) -> "RowAsDict":
+ return RowAsDict(**row._mapping) # noqa: SLF001
+
+
+class DetailedMovieViewModelMapper:
+ def __init__(self, connection: Connection) -> None:
+ self._connection = connection
+
+ def one(
+ self,
+ movie_id: MovieId,
+ current_user_id: Optional[UserId],
+ ) -> Optional[DetailedMovieViewModel]:
+ 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,
+ urt.id user_rating_id,
+ urt.value user_rating_value,
+ urt.created_at user_rating_created_at,
+ urv.id user_review_id,
+ urv.title user_review_title,
+ urv.content user_review_content,
+ urv.type user_review_type,
+ urv.created_at user_review_created_at
+ FROM
+ movies m
+ LEFT JOIN ratings urt
+ ON urt.user_id = :current_user_id
+ LEFT JOIN reviews urv
+ ON urv.user_id = :current_user_id
+ WHERE
+ m.id = :movie_id
+ LIMIT 1
+ """,
+ )
+ parameters = {
+ "movie_id": movie_id,
+ "current_user_id": current_user_id,
+ }
+ row = self._connection.execute(statement, parameters).fetchone()
+ if row:
+ row_as_dict = RowAsDict.from_row(row) # type: ignore
+ return self._to_view_model(row_as_dict)
+ return None
+
+ def _to_view_model(
+ self,
+ row_as_dict: RowAsDict,
+ ) -> DetailedMovieViewModel:
+ if row_as_dict["user_rating_id"]:
+ user_rating = UserRating(
+ id=RatingId(row_as_dict["user_rating_id"]), # type: ignore
+ value=row_as_dict["user_rating_value"], # type: ignore
+ created_at=row_as_dict["user_rating_created_at"], # type: ignore
+ )
+ else:
+ user_rating = None
+
+ if row_as_dict["user_review_id"]:
+ user_review = UserReview(
+ id=ReviewId(row_as_dict["user_review_id"]), # type: ignore
+ title=row_as_dict["user_review_title"], # type: ignore
+ content=row_as_dict["user_review_content"], # type: ignore
+ type=ReviewType(row_as_dict["user_review_type"]), # type: ignore
+ created_at=row_as_dict["user_review_created_at"], # type: ignore
+ )
+ else:
+ user_review = None
+
+ detailed_movie_view_model = DetailedMovieViewModel(
+ 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"],
+ user_rating=user_rating,
+ user_review=user_review,
+ )
+ return detailed_movie_view_model
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/non_detailed_movie.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/non_detailed_movie.py
new file mode 100644
index 0000000..b9b65ce
--- /dev/null
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/non_detailed_movie.py
@@ -0,0 +1,91 @@
+__all__ = ("NonDetailedMovieViewModelMapper",)
+
+from typing import Optional, TypedDict
+from datetime import date
+from uuid import UUID
+
+from sqlalchemy import Connection, Row, text
+
+from amdb.domain.entities.user import UserId
+from amdb.domain.entities.movie import MovieId
+from amdb.domain.entities.rating import RatingId
+from amdb.application.common.view_models.non_detailed_movie import (
+ UserRating,
+ NonDetailedMovieViewModel,
+)
+
+
+class RowAsDict(TypedDict):
+ movie_id: UUID
+ movie_title: str
+ movie_release_date: date
+ movie_rating: float
+ user_rating_id: Optional[UUID]
+ user_rating_value: Optional[float]
+
+ @classmethod # type: ignore
+ def from_row(cls, row: Row) -> "RowAsDict":
+ return RowAsDict(**row._mapping) # noqa: SLF001
+
+
+class NonDetailedMovieViewModelMapper:
+ def __init__(self, connection: Connection) -> None:
+ self._connection = connection
+
+ def list(
+ self,
+ current_user_id: Optional[UserId],
+ limit: int,
+ offset: int,
+ ) -> list[NonDetailedMovieViewModel]:
+ statement = text(
+ """
+ SELECT
+ m.id movie_id,
+ m.title movie_title,
+ m.release_date movie_release_date,
+ m.rating movie_rating,
+ urt.id user_rating_id,
+ urt.value user_rating_value
+ FROM
+ movies m
+ LEFT JOIN ratings urt
+ ON urt.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()
+
+ non_detailed_view_models = []
+ for row in rows:
+ row_as_dict = RowAsDict.from_row(row) # type: ignore
+ non_detailed_view_model = self._to_view_model(row_as_dict)
+ non_detailed_view_models.append(non_detailed_view_model)
+
+ return non_detailed_view_models
+
+ def _to_view_model(
+ self,
+ row_as_dict: RowAsDict,
+ ) -> NonDetailedMovieViewModel:
+ if row_as_dict["user_rating_id"]:
+ user_rating = UserRating(
+ id=RatingId(row_as_dict["user_rating_id"]), # type: ignore
+ value=row_as_dict["user_rating_value"], # type: ignore
+ )
+ else:
+ user_rating = None
+
+ non_detailed_movie_view_model = NonDetailedMovieViewModel(
+ 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"],
+ user_rating=user_rating,
+ )
+ return non_detailed_movie_view_model
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/review.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/review.py
new file mode 100644
index 0000000..a3443a1
--- /dev/null
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/review.py
@@ -0,0 +1,109 @@
+__all__ = ("ReviewViewModelMapper",)
+
+from typing import Optional, TypedDict
+from datetime import datetime
+from uuid import UUID
+
+from sqlalchemy import Connection, Row, text
+
+from amdb.domain.entities.user import UserId
+from amdb.domain.entities.movie import MovieId
+from amdb.domain.entities.rating import RatingId
+from amdb.domain.entities.review import ReviewId, ReviewType
+from amdb.application.common.view_models.review import (
+ UserRating,
+ UserReview,
+ ReviewViewModel,
+)
+
+
+class RowAsDict(TypedDict):
+ user_id: UUID
+ user_review_id: UUID
+ user_review_title: str
+ user_review_content: str
+ user_review_type: int
+ user_review_created_at: datetime
+ user_rating_id: Optional[UUID]
+ user_rating_value: Optional[float]
+ user_rating_created_at: Optional[datetime]
+
+ @classmethod # type: ignore
+ def from_row(cls, row: Row) -> "RowAsDict":
+ return RowAsDict(row._mapping) # noqa: SLF001
+
+
+class ReviewViewModelMapper:
+ def __init__(self, connection: Connection) -> None:
+ self._connection = connection
+
+ def list(
+ self,
+ movie_id: MovieId,
+ limit: int,
+ offset: int,
+ ) -> list[ReviewViewModel]:
+ statement = text(
+ """
+ SELECT
+ urv.user_id user_id,
+ urv.id user_review_id,
+ urv.title user_review_title,
+ urv.content user_review_content,
+ urv.type user_review_type,
+ urv.created_at user_review_created_at,
+ urt.id user_rating_id,
+ urt.value user_rating_value,
+ urt.created_at user_rating_created_at
+ FROM
+ reviews urv
+ LEFT JOIN ratings urt
+ ON urt.movie_id = urv.movie_id
+ AND urt.user_id = urv.user_id
+ WHERE
+ urv.movie_id = :movie_id
+ LIMIT :limit OFFSET :offset
+ """,
+ )
+ parameters = {
+ "movie_id": movie_id,
+ "limit": limit,
+ "offset": offset,
+ }
+ rows = self._connection.execute(statement, parameters).fetchall()
+
+ review_view_models = []
+ for row in rows:
+ row_as_dict = RowAsDict.from_row(row) # type: ignore
+ review_view_model = self._to_view_model(row_as_dict)
+ review_view_models.append(review_view_model)
+
+ return review_view_models
+
+ def _to_view_model(
+ self,
+ row_as_dict: RowAsDict,
+ ) -> ReviewViewModel:
+ user_review = UserReview(
+ id=ReviewId(row_as_dict["user_review_id"]),
+ title=row_as_dict["user_review_title"],
+ content=row_as_dict["user_review_content"],
+ type=ReviewType(row_as_dict["user_review_type"]),
+ created_at=row_as_dict["user_review_created_at"],
+ )
+
+ if row_as_dict["user_rating_id"]:
+ user_rating = UserRating(
+ id=RatingId(row_as_dict["user_rating_id"]), # type: ignore
+ value=row_as_dict["user_rating_value"], # type: ignore
+ created_at=row_as_dict["user_rating_created_at"], # type: ignore
+ )
+ else:
+ user_rating = None
+
+ review_view_model = ReviewViewModel(
+ user_id=UserId(row_as_dict["user_id"]),
+ user_review=user_review,
+ user_rating=user_rating,
+ )
+ return review_view_model
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/models/movie.py b/src/amdb/infrastructure/persistence/sqlalchemy/models/movie.py
index 3794544..d6fa74a 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/models/movie.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/models/movie.py
@@ -6,7 +6,7 @@
from .base import Model
-class Movie(Model):
+class MovieModel(Model):
__tablename__ = "movies"
id: Mapped[UUID] = mapped_column(
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/models/user_password_hash.py b/src/amdb/infrastructure/persistence/sqlalchemy/models/password_hash.py
similarity index 91%
rename from src/amdb/infrastructure/persistence/sqlalchemy/models/user_password_hash.py
rename to src/amdb/infrastructure/persistence/sqlalchemy/models/password_hash.py
index f042808..48d8a60 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/models/user_password_hash.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/models/password_hash.py
@@ -6,7 +6,7 @@
from .base import Model
-class UserPasswordHash(Model):
+class PasswordHashModel(Model):
__tablename__ = "user_password_hashes"
user_id: Mapped[UUID] = mapped_column(
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/models/rating.py b/src/amdb/infrastructure/persistence/sqlalchemy/models/rating.py
index 3f1942b..baaab42 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/models/rating.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/models/rating.py
@@ -5,11 +5,11 @@
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .base import Model
-from .user import User
-from .movie import Movie
+from .user import UserModel
+from .movie import MovieModel
-class Rating(Model):
+class RatingModel(Model):
__tablename__ = "ratings"
id: Mapped[UUID] = mapped_column(
@@ -24,7 +24,7 @@ class Rating(Model):
value: Mapped[float]
created_at: Mapped[datetime]
- movie: Mapped[Movie] = relationship()
- user: Mapped[User] = relationship()
+ movie: Mapped[MovieModel] = relationship()
+ user: Mapped[UserModel] = relationship()
__table_args__ = (UniqueConstraint("user_id", "movie_id"),)
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/models/review.py b/src/amdb/infrastructure/persistence/sqlalchemy/models/review.py
index 46c2646..90c4f6f 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/models/review.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/models/review.py
@@ -5,11 +5,11 @@
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .base import Model
-from .user import User
-from .movie import Movie
+from .user import UserModel
+from .movie import MovieModel
-class Review(Model):
+class ReviewModel(Model):
__tablename__ = "reviews"
id: Mapped[UUID] = mapped_column(
@@ -26,5 +26,5 @@ class Review(Model):
type: Mapped[int]
created_at: Mapped[datetime]
- user: Mapped[User] = relationship()
- movie: Mapped[Movie] = relationship()
+ user: Mapped[UserModel] = relationship()
+ movie: Mapped[MovieModel] = relationship()
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/models/user.py b/src/amdb/infrastructure/persistence/sqlalchemy/models/user.py
index 52d58f1..52514d5 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/models/user.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/models/user.py
@@ -5,7 +5,7 @@
from .base import Model
-class User(Model):
+class UserModel(Model):
__tablename__ = "users"
id: Mapped[UUID] = mapped_column(
diff --git a/src/amdb/infrastructure/security/__init__.py b/src/amdb/infrastructure/security/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/src/amdb/infrastructure/security/hasher.py b/src/amdb/infrastructure/security/hasher.py
deleted file mode 100644
index ba1af36..0000000
--- a/src/amdb/infrastructure/security/hasher.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import os
-import hashlib
-from dataclasses import dataclass
-
-
-@dataclass(frozen=True, slots=True)
-class HashData:
- hash: bytes
- salt: bytes
-
-
-class Hasher:
- def hash(self, value: bytes) -> HashData:
- salt = os.urandom(32)
- hash = hashlib.pbkdf2_hmac(
- hash_name="sha256",
- password=value,
- salt=salt,
- iterations=10000,
- )
- return HashData(hash=hash, salt=salt)
-
- def verify(self, value: bytes, hash_data: HashData) -> bool:
- hash = hashlib.pbkdf2_hmac(
- hash_name="sha256",
- password=value,
- salt=hash_data.salt,
- iterations=10000,
- )
- return hash == hash_data.hash
diff --git a/src/amdb/main/cli/__main__.py b/src/amdb/main/cli/__main__.py
index 35c4a92..e9d6f6f 100644
--- a/src/amdb/main/cli/__main__.py
+++ b/src/amdb/main/cli/__main__.py
@@ -1,11 +1,23 @@
-from amdb.main.config import build_generic_config
+import os
+
+from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
+from amdb.infrastructure.persistence.redis.config import RedisConfig
from .app import create_app
def main() -> None:
- generic_config = build_generic_config()
+ path_to_config = os.getenv("CONFIG_PATH")
+ if not path_to_config:
+ message = "Path to config env var is not set"
+ raise ValueError(message)
+
+ postgres_config = PostgresConfig.from_toml(path_to_config)
+ redis_config = RedisConfig.from_toml(path_to_config)
- app = create_app(generic_config)
+ app = create_app(
+ postgres_config=postgres_config,
+ redis_config=redis_config,
+ )
app()
diff --git a/src/amdb/main/cli/app.py b/src/amdb/main/cli/app.py
index bbc2648..abdc0b8 100644
--- a/src/amdb/main/cli/app.py
+++ b/src/amdb/main/cli/app.py
@@ -1,14 +1,53 @@
+from typing import cast
+from uuid import UUID
+
import typer
+from sqlalchemy import create_engine
+from redis import Redis
+from amdb.domain.entities.user import UserId
+from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
+from amdb.infrastructure.persistence.redis.config import RedisConfig
+from amdb.infrastructure.persistence.redis.mappers.permissions import (
+ PermissionsMapper,
+)
+from amdb.infrastructure.password_manager.hash_computer import HashComputer
+from amdb.infrastructure.auth.raw.identity_provider import RawIdentityProvider
from amdb.presentation.cli.setup import setup_typer_command_handlers
-from amdb.main.config import GenericConfig
-from .di import create_dependencies_dict
+from amdb.main.ioc import IoC
+
+
+IDENTITY_PROVIDER_USER_ID = UserId(
+ UUID("00000000-0000-0000-0000-000000000000"),
+)
+IDENTITY_PROVIDER_PERMISSIONS = 12
+
+def create_app(
+ postgres_config: PostgresConfig,
+ redis_config: RedisConfig,
+) -> typer.Typer:
+ sqlalchemy_engine = create_engine(postgres_config.url)
+ redis = Redis.from_url(redis_config.url, decode_responses=True)
+ permissions_mapper = PermissionsMapper(cast(Redis, redis))
+
+ ioc = IoC(
+ sqlalchemy_engine=sqlalchemy_engine,
+ permissions_mapper=permissions_mapper,
+ hash_computer=HashComputer(),
+ )
+ raw_identity_provider = RawIdentityProvider(
+ user_id=IDENTITY_PROVIDER_USER_ID,
+ permissions=IDENTITY_PROVIDER_PERMISSIONS,
+ )
+ dependencies = {
+ "ioc": ioc,
+ "identity_provider": raw_identity_provider,
+ }
-def create_app(generic_config: GenericConfig) -> typer.Typer:
app = typer.Typer(
rich_markup_mode="rich",
- context_settings={"obj": create_dependencies_dict(generic_config)},
+ context_settings={"obj": dependencies},
)
setup_typer_command_handlers(app)
diff --git a/src/amdb/main/cli/di.py b/src/amdb/main/cli/di.py
deleted file mode 100644
index 8f1f9d8..0000000
--- a/src/amdb/main/cli/di.py
+++ /dev/null
@@ -1,49 +0,0 @@
-from typing import TypedDict
-from uuid import UUID
-
-from sqlalchemy import create_engine
-from sqlalchemy.orm.session import sessionmaker
-from redis.client import Redis
-
-from amdb.domain.entities.user import UserId
-from amdb.infrastructure.persistence.redis.gateways.permissions import (
- RedisPermissionsGateway,
-)
-from amdb.infrastructure.auth.raw.identity_provider import RawIdentityProvider
-from amdb.infrastructure.security.hasher import Hasher
-from amdb.main.config import GenericConfig
-from amdb.main.ioc import IoC
-
-
-IDENTITY_PROVIDER_USER_ID = UserId(
- UUID("00000000-0000-0000-0000-000000000000")
-)
-IDENTITY_PROVIDER_PERMISSIONS = 12
-
-
-class DependenciesDict(TypedDict):
- ioc: IoC
- identity_provider: RawIdentityProvider
-
-
-def create_dependencies_dict(
- generic_config: GenericConfig,
-) -> DependenciesDict:
- redis = Redis(
- host=generic_config.redis.host,
- port=generic_config.redis.port,
- db=generic_config.redis.db,
- password=generic_config.redis.password,
- )
- engine = create_engine(generic_config.postgres.dsn)
- ioc = IoC(
- sessionmaker=sessionmaker(engine),
- permissions_gateway=RedisPermissionsGateway(redis),
- hasher=Hasher(),
- )
- identity_provider = RawIdentityProvider(
- user_id=IDENTITY_PROVIDER_USER_ID,
- permissions=IDENTITY_PROVIDER_PERMISSIONS,
- )
-
- return DependenciesDict(ioc=ioc, identity_provider=identity_provider)
diff --git a/src/amdb/main/config.py b/src/amdb/main/config.py
deleted file mode 100644
index 9bdb79b..0000000
--- a/src/amdb/main/config.py
+++ /dev/null
@@ -1,51 +0,0 @@
-import os
-from dataclasses import dataclass
-
-from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
-from amdb.infrastructure.persistence.redis.config import RedisConfig
-
-
-POSTGRES_HOST_ENV = "POSTGRES_HOST"
-POSTGRES_PORT_ENV = "POSTGRES_PORT"
-POSTGRES_NAME_ENV = "POSTGRES_DB"
-POSTGRES_USER_ENV = "POSTGRES_USER"
-POSTGRES_PASSWORD_ENV = "POSTGRES_PASSWORD"
-
-REDIS_HOST_ENV = "REDIS_HOST"
-REDIS_PORT_ENV = "REDIS_PORT"
-REDIS_DB_ENV = "REDIS_DB"
-REDIS_PASSWORD_ENV = "REDIS_PASSWORD"
-
-
-def build_generic_config() -> "GenericConfig":
- postgres_config = PostgresConfig(
- host=_get_env(POSTGRES_HOST_ENV),
- port=_get_env(POSTGRES_PORT_ENV),
- name=_get_env(POSTGRES_NAME_ENV),
- user=_get_env(POSTGRES_USER_ENV),
- password=_get_env(POSTGRES_PASSWORD_ENV),
- )
- redis_config = RedisConfig(
- host=_get_env(REDIS_HOST_ENV),
- port=int(_get_env(REDIS_PORT_ENV)),
- db=int(_get_env(REDIS_DB_ENV)),
- password=_get_env(REDIS_PASSWORD_ENV),
- )
- return GenericConfig(
- postgres=postgres_config,
- redis=redis_config,
- )
-
-
-def _get_env(key: str) -> str:
- value = os.getenv(key)
- if value is None:
- message = f"Env variable {key} is not set"
- raise ValueError(message)
- return value
-
-
-@dataclass(frozen=True, slots=True)
-class GenericConfig:
- postgres: PostgresConfig
- redis: RedisConfig
diff --git a/src/amdb/main/ioc.py b/src/amdb/main/ioc.py
index 32a5969..53b769e 100644
--- a/src/amdb/main/ioc.py
+++ b/src/amdb/main/ioc.py
@@ -1,7 +1,7 @@
from contextlib import contextmanager
from typing import Iterator
-from sqlalchemy.orm import Session, sessionmaker
+from sqlalchemy import Engine
from amdb.domain.services.access_concern import AccessConcern
from amdb.domain.services.create_user import CreateUser
@@ -9,9 +9,7 @@
from amdb.domain.services.rate_movie import RateMovie
from amdb.domain.services.unrate_movie import UnrateMovie
from amdb.domain.services.review_movie import ReviewMovie
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
+from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.command_handlers.register_user import RegisterUserHandler
from amdb.application.command_handlers.create_movie import CreateMovieHandler
from amdb.application.command_handlers.delete_movie import DeleteMovieHandler
@@ -19,25 +17,41 @@
from amdb.application.command_handlers.unrate_movie import UnrateMovieHandler
from amdb.application.command_handlers.review_movie import ReviewMovieHandler
from amdb.application.query_handlers.login import LoginHandler
-from amdb.application.query_handlers.get_movies import GetMoviesHandler
-from amdb.application.query_handlers.get_movie import GetMovieHandler
-from amdb.application.query_handlers.get_movie_ratings import (
- GetMovieRatingsHandler,
+from amdb.application.query_handlers.detailed_movie import (
+ GetDetailedMovieHandler,
+)
+from amdb.application.query_handlers.non_detailed_movies import (
+ GetNonDetailedMoviesHandler,
+)
+from amdb.application.query_handlers.reviews import GetReviewsHandler
+from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.user import (
+ UserMapper,
+)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.movie import (
+ MovieMapper,
+)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.rating import (
+ RatingMapper,
+)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.review import (
+ ReviewMapper,
+)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.non_detailed_movie import (
+ NonDetailedMovieViewModelMapper,
)
-from amdb.application.query_handlers.get_my_ratings import GetMyRatingsHandler
-from amdb.application.query_handlers.get_rating import GetRatingHandler
-from amdb.application.query_handlers.get_movie_reviews import (
- GetMovieReviewsHandler,
+from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.detailed_movie import (
+ DetailedMovieViewModelMapper,
)
-from amdb.application.query_handlers.get_my_reviews import GetMyReviewsHandler
-from amdb.application.query_handlers.get_review import GetReviewHandler
-from amdb.infrastructure.persistence.sqlalchemy.gateway_factory import (
- build_sqlalchemy_gateway_factory,
+from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.review import (
+ ReviewViewModelMapper,
)
-from amdb.infrastructure.persistence.redis.gateways.permissions import (
- RedisPermissionsGateway,
+from amdb.infrastructure.persistence.sqlalchemy.mappers.password_hash import (
+ PasswordHashMapper,
)
-from amdb.infrastructure.security.hasher import Hasher
+from amdb.infrastructure.persistence.redis.mappers.permissions import (
+ PermissionsMapper,
+)
+from amdb.infrastructure.password_manager.hash_computer import HashComputer
from amdb.infrastructure.password_manager.password_manager import (
HashingPasswordManager,
)
@@ -47,74 +61,70 @@
class IoC(HandlerFactory):
def __init__(
self,
- sessionmaker: sessionmaker[Session],
- permissions_gateway: RedisPermissionsGateway,
- hasher: Hasher,
+ sqlalchemy_engine: Engine,
+ permissions_mapper: PermissionsMapper,
+ hash_computer: HashComputer,
) -> None:
- self._sessionmaker = sessionmaker
- self._permissions_gateway = permissions_gateway
- self._hasher = hasher
+ self._sqlalchemy_engine = sqlalchemy_engine
+ self._permissions_mapper = permissions_mapper
+ self._hash_computer = hash_computer
@contextmanager
def register_user(self) -> Iterator[RegisterUserHandler]:
- with build_sqlalchemy_gateway_factory(
- self._sessionmaker
- ) as gateway_factory:
- hashing_password_manager = HashingPasswordManager(
- hasher=self._hasher,
- user_password_hash_gateway=gateway_factory.user_password_hash(),
+ with self._sqlalchemy_engine.connect() as sqlalchemy_connection:
+ password_manager = HashingPasswordManager(
+ hash_computer=self._hash_computer,
+ password_hash_gateway=PasswordHashMapper(
+ sqlalchemy_connection,
+ ),
)
yield RegisterUserHandler(
create_user=CreateUser(),
- user_gateway=gateway_factory.user(),
- permissions_gateway=self._permissions_gateway,
- unit_of_work=gateway_factory.unit_of_work(),
- password_manager=hashing_password_manager,
+ user_gateway=UserMapper(sqlalchemy_connection),
+ permissions_gateway=self._permissions_mapper,
+ unit_of_work=sqlalchemy_connection,
+ password_manager=password_manager,
)
@contextmanager
def login(self) -> Iterator[LoginHandler]:
- with build_sqlalchemy_gateway_factory(
- self._sessionmaker
- ) as gateway_factory:
- hashing_password_manager = HashingPasswordManager(
- hasher=self._hasher,
- user_password_hash_gateway=gateway_factory.user_password_hash(),
+ with self._sqlalchemy_engine.connect() as sqlalchemy_connection:
+ password_manager = HashingPasswordManager(
+ hash_computer=self._hash_computer,
+ password_hash_gateway=PasswordHashMapper(
+ sqlalchemy_connection,
+ ),
)
yield LoginHandler(
access_concern=AccessConcern(),
- user_gateway=gateway_factory.user(),
- permissions_gateway=self._permissions_gateway,
- password_manager=hashing_password_manager,
+ user_gateway=UserMapper(sqlalchemy_connection),
+ permissions_gateway=self._permissions_mapper,
+ password_manager=password_manager,
)
@contextmanager
- def get_movies(
+ def get_non_detailed_movies(
self,
identity_provider: IdentityProvider,
- ) -> Iterator[GetMoviesHandler]:
- with build_sqlalchemy_gateway_factory(
- self._sessionmaker
- ) as gateway_factory:
- yield GetMoviesHandler(
- access_concern=AccessConcern(),
- permissions_gateway=self._permissions_gateway,
- movie_gateway=gateway_factory.movie(),
+ ) -> Iterator[GetNonDetailedMoviesHandler]:
+ with self._sqlalchemy_engine.connect() as sqlalchemy_connection:
+ yield GetNonDetailedMoviesHandler(
+ non_detailed_movie_reader=NonDetailedMovieViewModelMapper(
+ sqlalchemy_connection,
+ ),
identity_provider=identity_provider,
)
@contextmanager
- def get_movie(
+ def get_detailed_movie(
self,
identity_provider: IdentityProvider,
- ) -> Iterator[GetMovieHandler]:
- with build_sqlalchemy_gateway_factory(
- self._sessionmaker
- ) as gateway_factory:
- yield GetMovieHandler(
- access_concern=AccessConcern(),
- permissions_gateway=self._permissions_gateway,
- movie_gateway=gateway_factory.movie(),
+ ) -> Iterator[GetDetailedMovieHandler]:
+ with self._sqlalchemy_engine.connect() as sqlalchemy_connection:
+ yield GetDetailedMovieHandler(
+ detailed_movie_reader=DetailedMovieViewModelMapper(
+ sqlalchemy_connection,
+ ),
identity_provider=identity_provider,
)
@@ -123,15 +133,13 @@ def create_movie(
self,
identity_provider: IdentityProvider,
) -> Iterator[CreateMovieHandler]:
- with build_sqlalchemy_gateway_factory(
- self._sessionmaker
- ) as gateway_factory:
+ with self._sqlalchemy_engine.connect() as sqlalchemy_connection:
yield CreateMovieHandler(
access_concern=AccessConcern(),
create_movie=CreateMovie(),
- permissions_gateway=self._permissions_gateway,
- movie_gateway=gateway_factory.movie(),
- unit_of_work=gateway_factory.unit_of_work(),
+ permissions_gateway=self._permissions_mapper,
+ movie_gateway=MovieMapper(sqlalchemy_connection),
+ unit_of_work=sqlalchemy_connection,
identity_provider=identity_provider,
)
@@ -140,62 +148,14 @@ def delete_movie(
self,
identity_provider: IdentityProvider,
) -> Iterator[DeleteMovieHandler]:
- with build_sqlalchemy_gateway_factory(
- self._sessionmaker
- ) as gateway_factory:
+ with self._sqlalchemy_engine.connect() as sqlalchemy_connection:
yield DeleteMovieHandler(
access_concern=AccessConcern(),
- permissions_gateway=self._permissions_gateway,
- movie_gateway=gateway_factory.movie(),
- rating_gateway=gateway_factory.rating(),
- review_gateway=gateway_factory.review(),
- unit_of_work=gateway_factory.unit_of_work(),
- identity_provider=identity_provider,
- )
-
- @contextmanager
- def get_movie_ratings(
- self,
- identity_provider: IdentityProvider,
- ) -> Iterator[GetMovieRatingsHandler]:
- with build_sqlalchemy_gateway_factory(
- self._sessionmaker
- ) as gateway_factory:
- yield GetMovieRatingsHandler(
- access_concern=AccessConcern(),
- permissions_gateway=self._permissions_gateway,
- movie_gateway=gateway_factory.movie(),
- rating_gateway=gateway_factory.rating(),
- identity_provider=identity_provider,
- )
-
- @contextmanager
- def get_my_ratings(
- self,
- identity_provider: IdentityProvider,
- ) -> Iterator[GetMyRatingsHandler]:
- with build_sqlalchemy_gateway_factory(
- self._sessionmaker
- ) as gateway_factory:
- yield GetMyRatingsHandler(
- access_concern=AccessConcern(),
- permissions_gateway=self._permissions_gateway,
- rating_gateway=gateway_factory.rating(),
- identity_provider=identity_provider,
- )
-
- @contextmanager
- def get_rating(
- self,
- identity_provider: IdentityProvider,
- ) -> Iterator[GetRatingHandler]:
- with build_sqlalchemy_gateway_factory(
- self._sessionmaker
- ) as gateway_factory:
- yield GetRatingHandler(
- access_concern=AccessConcern(),
- permissions_gateway=self._permissions_gateway,
- rating_gateway=gateway_factory.rating(),
+ permissions_gateway=self._permissions_mapper,
+ movie_gateway=MovieMapper(sqlalchemy_connection),
+ rating_gateway=RatingMapper(sqlalchemy_connection),
+ review_gateway=ReviewMapper(sqlalchemy_connection),
+ unit_of_work=sqlalchemy_connection,
identity_provider=identity_provider,
)
@@ -204,17 +164,15 @@ def rate_movie(
self,
identity_provider: IdentityProvider,
) -> Iterator[RateMovieHandler]:
- with build_sqlalchemy_gateway_factory(
- self._sessionmaker
- ) as gateway_factory:
+ with self._sqlalchemy_engine.connect() as sqlalchemy_connection:
yield RateMovieHandler(
access_concern=AccessConcern(),
rate_movie=RateMovie(),
- permissions_gateway=self._permissions_gateway,
- user_gateway=gateway_factory.user(),
- movie_gateway=gateway_factory.movie(),
- rating_gateway=gateway_factory.rating(),
- unit_of_work=gateway_factory.unit_of_work(),
+ permissions_gateway=self._permissions_mapper,
+ user_gateway=UserMapper(sqlalchemy_connection),
+ movie_gateway=MovieMapper(sqlalchemy_connection),
+ rating_gateway=RatingMapper(sqlalchemy_connection),
+ unit_of_work=sqlalchemy_connection,
identity_provider=identity_provider,
)
@@ -223,63 +181,25 @@ def unrate_movie(
self,
identity_provider: IdentityProvider,
) -> Iterator[UnrateMovieHandler]:
- with build_sqlalchemy_gateway_factory(
- self._sessionmaker
- ) as gateway_factory:
+ with self._sqlalchemy_engine.connect() as sqlalchemy_connection:
yield UnrateMovieHandler(
access_concern=AccessConcern(),
unrate_movie=UnrateMovie(),
- permissions_gateway=self._permissions_gateway,
- movie_gateway=gateway_factory.movie(),
- rating_gateway=gateway_factory.rating(),
- unit_of_work=gateway_factory.unit_of_work(),
- identity_provider=identity_provider,
- )
-
- @contextmanager
- def get_movie_reviews(
- self,
- identity_provider: IdentityProvider,
- ) -> Iterator[GetMovieReviewsHandler]:
- with build_sqlalchemy_gateway_factory(
- self._sessionmaker
- ) as gateway_factory:
- yield GetMovieReviewsHandler(
- access_concern=AccessConcern(),
- permissions_gateway=self._permissions_gateway,
- movie_gateway=gateway_factory.movie(),
- review_gateway=gateway_factory.review(),
- identity_provider=identity_provider,
- )
-
- @contextmanager
- def get_my_reviews(
- self,
- identity_provider: IdentityProvider,
- ) -> Iterator[GetMyReviewsHandler]:
- with build_sqlalchemy_gateway_factory(
- self._sessionmaker
- ) as gateway_factory:
- yield GetMyReviewsHandler(
- access_concern=AccessConcern(),
- permissions_gateway=self._permissions_gateway,
- review_gateway=gateway_factory.review(),
+ permissions_gateway=self._permissions_mapper,
+ movie_gateway=MovieMapper(sqlalchemy_connection),
+ rating_gateway=RatingMapper(sqlalchemy_connection),
+ unit_of_work=sqlalchemy_connection,
identity_provider=identity_provider,
)
@contextmanager
- def get_review(
- self,
- identity_provider: IdentityProvider,
- ) -> Iterator[GetReviewHandler]:
- with build_sqlalchemy_gateway_factory(
- self._sessionmaker
- ) as gateway_factory:
- yield GetReviewHandler(
- access_concern=AccessConcern(),
- permissions_gateway=self._permissions_gateway,
- review_gateway=gateway_factory.review(),
- identity_provider=identity_provider,
+ def get_reviews(self) -> Iterator[GetReviewsHandler]:
+ with self._sqlalchemy_engine.connect() as sqlalchemy_connection:
+ yield GetReviewsHandler(
+ movie_gateway=MovieMapper(sqlalchemy_connection),
+ review_view_model_reader=ReviewViewModelMapper(
+ sqlalchemy_connection,
+ ),
)
@contextmanager
@@ -287,16 +207,14 @@ def review_movie(
self,
identity_provider: IdentityProvider,
) -> Iterator[ReviewMovieHandler]:
- with build_sqlalchemy_gateway_factory(
- self._sessionmaker
- ) as gateway_factory:
+ with self._sqlalchemy_engine.connect() as sqlalchemy_connection:
yield ReviewMovieHandler(
access_concern=AccessConcern(),
review_movie=ReviewMovie(),
- permissions_gateway=self._permissions_gateway,
- user_gateway=gateway_factory.user(),
- movie_gateway=gateway_factory.movie(),
- review_gateway=gateway_factory.review(),
- unit_of_work=gateway_factory.unit_of_work(),
+ permissions_gateway=self._permissions_mapper,
+ user_gateway=UserMapper(sqlalchemy_connection),
+ movie_gateway=MovieMapper(sqlalchemy_connection),
+ review_gateway=ReviewMapper(sqlalchemy_connection),
+ unit_of_work=sqlalchemy_connection,
identity_provider=identity_provider,
)
diff --git a/src/amdb/main/web_api/__main__.py b/src/amdb/main/web_api/__main__.py
index 60d67f1..f321b67 100644
--- a/src/amdb/main/web_api/__main__.py
+++ b/src/amdb/main/web_api/__main__.py
@@ -1,26 +1,37 @@
import asyncio
+import os
from uvicorn import Server, Config
-from amdb.main.config import build_generic_config
-from .config import build_web_api_config
+from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
+from amdb.infrastructure.persistence.redis.config import RedisConfig
+from amdb.infrastructure.auth.session.config import SessionConfig
+from .config import WebAPIConfig
from .app import create_app
async def main() -> None:
- web_api_config = build_web_api_config()
- generic_config = build_generic_config()
+ path_to_config = os.getenv("CONFIG_PATH")
+ if not path_to_config:
+ message = "Path to config env var is not set"
+ raise ValueError(message)
+
+ web_api_config = WebAPIConfig.from_toml(path_to_config)
+ postgres_config = PostgresConfig.from_toml(path_to_config)
+ redis_config = RedisConfig.from_toml(path_to_config)
+ session_config = SessionConfig.from_toml(path_to_config)
app = create_app(
- fastapi_config=web_api_config.fastapi,
- session_config=web_api_config.session,
- generic_config=generic_config,
+ web_api_config=web_api_config,
+ postgres_config=postgres_config,
+ redis_config=redis_config,
+ session_config=session_config,
)
server = Server(
Config(
app=app,
- host=web_api_config.uvicorn.host,
- port=web_api_config.uvicorn.port,
+ host=web_api_config.host,
+ port=web_api_config.port,
),
)
diff --git a/src/amdb/main/web_api/app.py b/src/amdb/main/web_api/app.py
index d19e906..dfa1555 100644
--- a/src/amdb/main/web_api/app.py
+++ b/src/amdb/main/web_api/app.py
@@ -1,25 +1,28 @@
from fastapi import FastAPI
+from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
+from amdb.infrastructure.persistence.redis.config import RedisConfig
from amdb.infrastructure.auth.session.config import SessionConfig
from amdb.presentation.web_api.exception_handlers import (
setup_exception_handlers,
)
from amdb.presentation.web_api.routers.setup import setup_routers
-from amdb.main.config import GenericConfig
-from .config import FastAPIConfig
from .di import setup_dependecies
+from .config import WebAPIConfig
def create_app(
- fastapi_config: FastAPIConfig,
+ web_api_config: WebAPIConfig,
+ postgres_config: PostgresConfig,
+ redis_config: RedisConfig,
session_config: SessionConfig,
- generic_config: GenericConfig,
) -> FastAPI:
- app = FastAPI(version=fastapi_config.version)
+ app = FastAPI(version=web_api_config.version)
setup_dependecies(
app=app,
session_config=session_config,
- generic_config=generic_config,
+ postgres_config=postgres_config,
+ redis_config=redis_config,
)
setup_exception_handlers(app)
setup_routers(app)
diff --git a/src/amdb/main/web_api/config.py b/src/amdb/main/web_api/config.py
index a490322..60a0108 100644
--- a/src/amdb/main/web_api/config.py
+++ b/src/amdb/main/web_api/config.py
@@ -1,59 +1,22 @@
-import os
from dataclasses import dataclass
-from datetime import timedelta
+from typing import Union
+from os import PathLike
-from amdb.infrastructure.auth.session.config import SessionConfig
-
-
-FASTAPI_VERSION_ENV = "FASTAPI_VERSION"
-
-UVICORN_HOST_ENV = "UVICORN_HOST"
-UVICORN_PORT_ENV = "UVICORN_PORT"
-
-SESSION_LIFETIME_ENV = "SESSION_LIFETIME"
-
-
-def build_web_api_config() -> "WebAPIConfig":
- fastapi_config = FastAPIConfig(
- version=_get_env(FASTAPI_VERSION_ENV),
- )
- uvicorn_config = UvicornConfig(
- host=_get_env(UVICORN_HOST_ENV),
- port=int(_get_env(UVICORN_PORT_ENV)),
- )
- session_config = SessionConfig(
- session_lifetime=timedelta(
- minutes=int(_get_env(SESSION_LIFETIME_ENV))
- ),
- )
- return WebAPIConfig(
- fastapi=fastapi_config,
- uvicorn=uvicorn_config,
- session=session_config,
- )
-
-
-def _get_env(key: str) -> str:
- value = os.getenv(key)
- if value is None:
- message = f"Env variable {key} is not set"
- raise ValueError(message)
- return value
+import toml
@dataclass(frozen=True, slots=True)
-class FastAPIConfig:
+class WebAPIConfig:
version: str
-
-
-@dataclass(frozen=True, slots=True)
-class UvicornConfig:
host: str
port: int
-
-@dataclass(frozen=True, slots=True)
-class WebAPIConfig:
- fastapi: FastAPIConfig
- uvicorn: UvicornConfig
- session: SessionConfig
+ @classmethod
+ def from_toml(cls, path: Union[PathLike, str]) -> "WebAPIConfig":
+ toml_as_dict = toml.load(path)
+ web_api_section_as_dict = toml_as_dict["web-api"]
+ return WebAPIConfig(
+ version=web_api_section_as_dict["version"],
+ host=web_api_section_as_dict["host"],
+ port=web_api_section_as_dict["port"],
+ )
diff --git a/src/amdb/main/web_api/di.py b/src/amdb/main/web_api/di.py
index 948002c..2a03b52 100644
--- a/src/amdb/main/web_api/di.py
+++ b/src/amdb/main/web_api/di.py
@@ -1,53 +1,46 @@
+from typing import cast
+
from fastapi import FastAPI
from sqlalchemy import create_engine
-from sqlalchemy.orm.session import sessionmaker
from redis.client import Redis
-from amdb.infrastructure.security.hasher import Hasher
+from amdb.infrastructure.auth.session.session_processor import SessionProcessor
+from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
+from amdb.infrastructure.persistence.redis.config import RedisConfig
from amdb.infrastructure.auth.session.config import SessionConfig
-from amdb.infrastructure.persistence.redis.gateways.session import (
- RedisSessionGateway,
-)
-from amdb.infrastructure.persistence.redis.gateways.permissions import (
- RedisPermissionsGateway,
+from amdb.infrastructure.persistence.redis.mappers.session import SessionMapper
+from amdb.infrastructure.persistence.redis.mappers.permissions import (
+ PermissionsMapper,
)
-from amdb.infrastructure.auth.session.session_processor import SessionProcessor
+from amdb.infrastructure.password_manager.hash_computer import HashComputer
from amdb.presentation.handler_factory import HandlerFactory
from amdb.presentation.web_api.dependencies.depends_stub import Stub
-from amdb.main.config import GenericConfig
from amdb.main.ioc import IoC
def setup_dependecies(
app: FastAPI,
session_config: SessionConfig,
- generic_config: GenericConfig,
+ postgres_config: PostgresConfig,
+ redis_config: RedisConfig,
) -> None:
- redis = Redis(
- host=generic_config.redis.host,
- port=generic_config.redis.port,
- db=generic_config.redis.db,
- password=generic_config.redis.password,
- decode_responses=True,
- )
- redis_session_gateway = RedisSessionGateway(
- redis=redis,
- session_lifetime=session_config.session_lifetime,
+ redis = Redis.from_url(redis_config.url, decode_responses=True)
+ session_mapper = SessionMapper(
+ redis=cast(Redis, redis),
+ session_lifetime=session_config.lifetime,
)
- app.dependency_overrides[Stub(RedisSessionGateway)] = (
- lambda: redis_session_gateway
- ) # type: ignore
+ app.dependency_overrides[Stub(SessionMapper)] = lambda: session_mapper # type: ignore
- redis_permissions_gateway = RedisPermissionsGateway(redis)
- app.dependency_overrides[Stub(RedisPermissionsGateway)] = (
- lambda: redis_permissions_gateway
+ permissions_mapper = PermissionsMapper(cast(Redis, redis))
+ app.dependency_overrides[Stub(PermissionsMapper)] = (
+ lambda: permissions_mapper
) # type: ignore
- engine = create_engine(generic_config.postgres.dsn)
+ sqlalchemy_engine = create_engine(postgres_config.url)
ioc = IoC(
- sessionmaker=sessionmaker(engine),
- permissions_gateway=redis_permissions_gateway,
- hasher=Hasher(),
+ sqlalchemy_engine=sqlalchemy_engine,
+ permissions_mapper=permissions_mapper,
+ hash_computer=HashComputer(),
)
app.dependency_overrides[HandlerFactory] = lambda: ioc # type: ignore
diff --git a/src/amdb/presentation/cli/movie.py b/src/amdb/presentation/cli/movie.py
index 7cbad71..a430a1d 100644
--- a/src/amdb/presentation/cli/movie.py
+++ b/src/amdb/presentation/cli/movie.py
@@ -8,116 +8,15 @@
import rich.table
from amdb.domain.entities.movie import MovieId
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
+from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.commands.create_movie import CreateMovieCommand
from amdb.application.commands.delete_movie import DeleteMovieCommand
-from amdb.application.queries.get_movies import GetMoviesQuery
-from amdb.application.queries.get_movie import GetMovieQuery
from amdb.presentation.handler_factory import HandlerFactory
movie_commands = typer.Typer(name="movie")
-@movie_commands.command()
-def list(
- ctx: typer.Context,
- limit: Annotated[
- int,
- typer.Option(
- "--limit",
- "-l",
- help="Number of movies that should be [blue]listed[/blue].",
- max=200,
- min=1,
- ),
- ] = 100,
- offset: Annotated[
- int,
- typer.Option(
- "--offset",
- "-o",
- help="Number of movies that should be offsetted.",
- min=0,
- ),
- ] = 0,
-) -> None:
- """
- [blue]List[/blue] movies.
- """
- ioc: HandlerFactory = ctx.obj["ioc"]
- identity_provider: IdentityProvider = ctx.obj["identity_provider"]
-
- with ioc.get_movies(identity_provider) as get_movies_handler:
- get_movies_query = GetMoviesQuery(
- limit=limit,
- offset=offset,
- )
- get_movies_result = get_movies_handler.execute(get_movies_query)
-
- movies_table = rich.table.Table(
- "id",
- "title",
- "release_date",
- "rating",
- "rating_count",
- box=rich.box.ROUNDED,
- )
- for movie in get_movies_result.movies:
- movies_table.add_row(
- str(movie.id),
- movie.title,
- str(movie.release_date),
- str(movie.rating),
- str(movie.rating_count),
- )
-
- rich.print(movies_table)
- rich.print(
- f"Listed movie count: {get_movies_result.movie_count}",
- f"with limit: {limit}",
- f"and offset: {offset}",
- )
-
-
-@movie_commands.command()
-def get(
- ctx: typer.Context,
- movie_id: Annotated[UUID, typer.Argument(help="Movie id.")],
-) -> None:
- """
- [blue]Get[/blue] movie.
- """
- ioc: HandlerFactory = ctx.obj["ioc"]
- identity_provider: IdentityProvider = ctx.obj["identity_provider"]
-
- with ioc.get_movie(identity_provider) as get_movie_handler:
- get_movie_query = GetMovieQuery(
- movie_id=MovieId(movie_id),
- )
- get_movie_result = get_movie_handler.execute(get_movie_query)
-
- movies_table = rich.table.Table(
- "id",
- "title",
- "release_date",
- "rating",
- "rating_count",
- box=rich.box.ROUNDED,
- )
- movies_table.add_row(
- str(movie_id),
- get_movie_result.title,
- str(get_movie_result.release_date),
- str(get_movie_result.rating),
- str(get_movie_result.rating_count),
- )
-
- rich.print(movies_table)
-
-
@movie_commands.command()
def create(
ctx: typer.Context,
diff --git a/src/amdb/presentation/handler_factory.py b/src/amdb/presentation/handler_factory.py
index 930141b..5ad8d87 100644
--- a/src/amdb/presentation/handler_factory.py
+++ b/src/amdb/presentation/handler_factory.py
@@ -1,9 +1,7 @@
from abc import ABC, abstractmethod
from typing import ContextManager
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
+from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.command_handlers.register_user import RegisterUserHandler
from amdb.application.command_handlers.create_movie import CreateMovieHandler
from amdb.application.command_handlers.delete_movie import DeleteMovieHandler
@@ -11,18 +9,13 @@
from amdb.application.command_handlers.unrate_movie import UnrateMovieHandler
from amdb.application.command_handlers.review_movie import ReviewMovieHandler
from amdb.application.query_handlers.login import LoginHandler
-from amdb.application.query_handlers.get_movies import GetMoviesHandler
-from amdb.application.query_handlers.get_movie import GetMovieHandler
-from amdb.application.query_handlers.get_movie_ratings import (
- GetMovieRatingsHandler,
+from amdb.application.query_handlers.detailed_movie import (
+ GetDetailedMovieHandler,
)
-from amdb.application.query_handlers.get_my_ratings import GetMyRatingsHandler
-from amdb.application.query_handlers.get_rating import GetRatingHandler
-from amdb.application.query_handlers.get_movie_reviews import (
- GetMovieReviewsHandler,
+from amdb.application.query_handlers.non_detailed_movies import (
+ GetNonDetailedMoviesHandler,
)
-from amdb.application.query_handlers.get_my_reviews import GetMyReviewsHandler
-from amdb.application.query_handlers.get_review import GetReviewHandler
+from amdb.application.query_handlers.reviews import GetReviewsHandler
class HandlerFactory(ABC):
@@ -35,17 +28,17 @@ def login(self) -> ContextManager[LoginHandler]:
raise NotImplementedError
@abstractmethod
- def get_movies(
+ def get_non_detailed_movies(
self,
identity_provider: IdentityProvider,
- ) -> ContextManager[GetMoviesHandler]:
+ ) -> ContextManager[GetNonDetailedMoviesHandler]:
raise NotImplementedError
@abstractmethod
- def get_movie(
+ def get_detailed_movie(
self,
identity_provider: IdentityProvider,
- ) -> ContextManager[GetMovieHandler]:
+ ) -> ContextManager[GetDetailedMovieHandler]:
raise NotImplementedError
@abstractmethod
@@ -62,27 +55,6 @@ def delete_movie(
) -> ContextManager[DeleteMovieHandler]:
raise NotImplementedError
- @abstractmethod
- def get_movie_ratings(
- self,
- identity_provider: IdentityProvider,
- ) -> ContextManager[GetMovieRatingsHandler]:
- raise NotImplementedError
-
- @abstractmethod
- def get_my_ratings(
- self,
- identity_provider: IdentityProvider,
- ) -> ContextManager[GetMyRatingsHandler]:
- raise NotImplementedError
-
- @abstractmethod
- def get_rating(
- self,
- identity_provider: IdentityProvider,
- ) -> ContextManager[GetRatingHandler]:
- raise NotImplementedError
-
@abstractmethod
def rate_movie(
self,
@@ -98,23 +70,7 @@ def unrate_movie(
raise NotImplementedError
@abstractmethod
- def get_movie_reviews(
- self,
- identity_provider: IdentityProvider,
- ) -> ContextManager[GetMovieReviewsHandler]:
- raise NotImplementedError
-
- def get_my_reviews(
- self,
- identity_provider: IdentityProvider,
- ) -> ContextManager[GetMyReviewsHandler]:
- raise NotImplementedError
-
- @abstractmethod
- def get_review(
- self,
- identity_provider: IdentityProvider,
- ) -> ContextManager[GetReviewHandler]:
+ def get_reviews(self) -> ContextManager[GetReviewsHandler]:
raise NotImplementedError
@abstractmethod
diff --git a/src/amdb/presentation/web_api/dependencies/identity_provider.py b/src/amdb/presentation/web_api/dependencies/identity_provider.py
index 77f8169..3aee77c 100644
--- a/src/amdb/presentation/web_api/dependencies/identity_provider.py
+++ b/src/amdb/presentation/web_api/dependencies/identity_provider.py
@@ -2,34 +2,31 @@
from fastapi import Cookie, Depends
-from amdb.infrastructure.persistence.redis.gateways.session import (
- RedisSessionGateway,
-)
-from amdb.infrastructure.persistence.redis.gateways.permissions import (
- RedisPermissionsGateway,
+from amdb.infrastructure.persistence.redis.mappers.session import SessionMapper
+from amdb.infrastructure.persistence.redis.mappers.permissions import (
+ PermissionsMapper,
)
from amdb.infrastructure.auth.session.identity_provider import (
SessionIdentityProvider,
)
-from amdb.infrastructure.auth.session.model import SessionId
+from amdb.infrastructure.auth.session.session import SessionId
from amdb.presentation.web_api.constants import SESSION_ID_COOKIE
from .depends_stub import Stub
def get_identity_provider(
- session_gateway: Annotated[
- RedisSessionGateway, Depends(Stub(RedisSessionGateway))
- ],
- permissions_gateway: Annotated[
- RedisPermissionsGateway,
- Depends(Stub(RedisPermissionsGateway)),
+ session_mapper: Annotated[SessionMapper, Depends(Stub(SessionMapper))],
+ permissions_mapper: Annotated[
+ PermissionsMapper,
+ Depends(Stub(PermissionsMapper)),
],
session_id: Annotated[
- Optional[str], Cookie(alias=SESSION_ID_COOKIE)
+ Optional[str],
+ Cookie(alias=SESSION_ID_COOKIE),
] = None,
) -> SessionIdentityProvider:
return SessionIdentityProvider(
session_id=SessionId(session_id) if session_id else None,
- session_gateway=session_gateway,
- permissions_gateway=permissions_gateway,
+ session_gateway=session_mapper,
+ permissions_gateway=permissions_mapper,
)
diff --git a/src/amdb/presentation/web_api/exception_handlers.py b/src/amdb/presentation/web_api/exception_handlers.py
index 4408072..b2dcede 100644
--- a/src/amdb/presentation/web_api/exception_handlers.py
+++ b/src/amdb/presentation/web_api/exception_handlers.py
@@ -10,7 +10,8 @@ def setup_exception_handlers(app: FastAPI) -> None:
app.add_exception_handler(DomainError, _domain_error_handler)
app.add_exception_handler(ApplicationError, _application_error_handler)
app.add_exception_handler(
- InfrastructureError, _infrastructure_error_handler
+ InfrastructureError,
+ _infrastructure_error_handler,
)
@@ -23,6 +24,7 @@ def _application_error_handler(_, error: ApplicationError) -> JSONResponse:
def _infrastructure_error_handler(
- _, error: InfrastructureError
+ _,
+ error: InfrastructureError,
) -> JSONResponse:
return JSONResponse(content=None, status_code=500)
diff --git a/src/amdb/presentation/web_api/routers/auth/login.py b/src/amdb/presentation/web_api/routers/auth/login.py
index 8423b92..7b594ea 100644
--- a/src/amdb/presentation/web_api/routers/auth/login.py
+++ b/src/amdb/presentation/web_api/routers/auth/login.py
@@ -5,9 +5,7 @@
from amdb.domain.entities.user import UserId
from amdb.application.queries.login import LoginQuery
from amdb.infrastructure.auth.session.session_processor import SessionProcessor
-from amdb.infrastructure.persistence.redis.gateways.session import (
- RedisSessionGateway,
-)
+from amdb.infrastructure.persistence.redis.mappers.session import SessionMapper
from amdb.presentation.handler_factory import HandlerFactory
from amdb.presentation.web_api.dependencies.depends_stub import Stub
from amdb.presentation.web_api.constants import SESSION_ID_COOKIE
@@ -16,11 +14,10 @@
async def login(
ioc: Annotated[HandlerFactory, Depends()],
session_processor: Annotated[
- SessionProcessor, Depends(Stub(SessionProcessor))
- ],
- session_gateway: Annotated[
- RedisSessionGateway, Depends(Stub(RedisSessionGateway))
+ SessionProcessor,
+ Depends(Stub(SessionProcessor)),
],
+ session_mapper: Annotated[SessionMapper, Depends(Stub(SessionMapper))],
login_query: LoginQuery,
response: Response,
) -> UserId:
@@ -38,7 +35,7 @@ async def login(
user_id = login_handler.execute(login_query)
session = session_processor.create(user_id=user_id)
- session_gateway.save(session)
+ session_mapper.save(session)
response.set_cookie(
key=SESSION_ID_COOKIE,
diff --git a/src/amdb/presentation/web_api/routers/auth/register.py b/src/amdb/presentation/web_api/routers/auth/register.py
index e25e7c9..c0c38fa 100644
--- a/src/amdb/presentation/web_api/routers/auth/register.py
+++ b/src/amdb/presentation/web_api/routers/auth/register.py
@@ -5,9 +5,7 @@
from amdb.domain.entities.user import UserId
from amdb.application.commands.register_user import RegisterUserCommand
from amdb.infrastructure.auth.session.session_processor import SessionProcessor
-from amdb.infrastructure.persistence.redis.gateways.session import (
- RedisSessionGateway,
-)
+from amdb.infrastructure.persistence.redis.mappers.session import SessionMapper
from amdb.presentation.handler_factory import HandlerFactory
from amdb.presentation.web_api.dependencies.depends_stub import Stub
from amdb.presentation.web_api.constants import SESSION_ID_COOKIE
@@ -16,11 +14,10 @@
async def register(
ioc: Annotated[HandlerFactory, Depends()],
session_processor: Annotated[
- SessionProcessor, Depends(Stub(SessionProcessor))
- ],
- session_gateway: Annotated[
- RedisSessionGateway, Depends(Stub(RedisSessionGateway))
+ SessionProcessor,
+ Depends(Stub(SessionProcessor)),
],
+ session_mapper: Annotated[SessionMapper, Depends(Stub(SessionMapper))],
register_user_command: RegisterUserCommand,
response: Response,
) -> UserId:
@@ -36,7 +33,7 @@ async def register(
user_id = register_user_handler.execute(register_user_command)
session = session_processor.create(user_id=user_id)
- session_gateway.save(session)
+ session_mapper.save(session)
response.set_cookie(
key=SESSION_ID_COOKIE,
diff --git a/src/amdb/presentation/web_api/routers/movies/get_movie.py b/src/amdb/presentation/web_api/routers/movies/get_movie.py
deleted file mode 100644
index 8768121..0000000
--- a/src/amdb/presentation/web_api/routers/movies/get_movie.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from typing import Annotated
-
-from fastapi import Depends
-
-from amdb.domain.entities.movie import MovieId
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.queries.get_movie import GetMovieResult, GetMovieQuery
-from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import (
- get_identity_provider,
-)
-
-
-def get_movie(
- ioc: Annotated[HandlerFactory, Depends()],
- identity_provider: Annotated[
- IdentityProvider, Depends(get_identity_provider)
- ],
- movie_id: MovieId,
-) -> GetMovieResult:
- """
- ## Errors: \n
- - When access is denied \n
- - When movie doesn't exist \n
- """
- with ioc.get_movie(identity_provider) as get_movie_handler:
- get_movie_query = GetMovieQuery(
- movie_id=movie_id,
- )
- get_movie_result = get_movie_handler.execute(get_movie_query)
-
- return get_movie_result
diff --git a/src/amdb/presentation/web_api/routers/movies/get_movies.py b/src/amdb/presentation/web_api/routers/movies/get_movies.py
index 3794fdc..aa06916 100644
--- a/src/amdb/presentation/web_api/routers/movies/get_movies.py
+++ b/src/amdb/presentation/web_api/routers/movies/get_movies.py
@@ -2,33 +2,68 @@
from fastapi import Depends
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
+from amdb.domain.entities.movie import MovieId
+from amdb.application.common.identity_provider import IdentityProvider
+from amdb.application.queries.detailed_movie import GetDetailedMovieQuery
+from amdb.application.queries.non_detailed_movies import (
+ GetNonDetailedMoviesQuery,
+)
+from amdb.application.common.view_models.detailed_movie import (
+ DetailedMovieViewModel,
+)
+from amdb.application.common.view_models.non_detailed_movie import (
+ NonDetailedMovieViewModel,
)
from amdb.presentation.handler_factory import HandlerFactory
from amdb.presentation.web_api.dependencies.identity_provider import (
get_identity_provider,
)
-from amdb.application.queries.get_movies import GetMoviesQuery, GetMoviesResult
-async def get_movies(
+async def get_non_detailed_movies(
ioc: Annotated[HandlerFactory, Depends()],
identity_provider: Annotated[
- IdentityProvider, Depends(get_identity_provider)
+ IdentityProvider,
+ Depends(get_identity_provider),
],
limit: int = 100,
offset: int = 0,
-) -> GetMoviesResult:
+) -> list[NonDetailedMovieViewModel]:
"""
## Errors: \n
- When access is denied \n
"""
- with ioc.get_movies(identity_provider) as get_movies_handler:
- get_movies_query = GetMoviesQuery(
+ with ioc.get_non_detailed_movies(
+ identity_provider,
+ ) as get_non_detailed_movies_handler:
+ get_non_detailed_movies_query = GetNonDetailedMoviesQuery(
limit=limit,
offset=offset,
)
- get_movies_result = get_movies_handler.execute(get_movies_query)
+ result = get_non_detailed_movies_handler.execute(
+ get_non_detailed_movies_query,
+ )
+ return result
- return get_movies_result
+
+async def get_detailed_movie(
+ ioc: Annotated[HandlerFactory, Depends()],
+ identity_provider: Annotated[
+ IdentityProvider,
+ Depends(get_identity_provider),
+ ],
+ movie_id: MovieId,
+) -> DetailedMovieViewModel:
+ """
+ ## Errors: \n
+ - When access is denied \n
+ - When movie doesn't exist \n
+ """
+ with ioc.get_detailed_movie(
+ identity_provider,
+ ) as get_detailed_movie_handler:
+ get_detailed_movie_query = GetDetailedMovieQuery(
+ movie_id=movie_id,
+ )
+ result = get_detailed_movie_handler.execute(get_detailed_movie_query)
+ return result
diff --git a/src/amdb/presentation/web_api/routers/movies/router.py b/src/amdb/presentation/web_api/routers/movies/router.py
index 1ccfc4d..890b974 100644
--- a/src/amdb/presentation/web_api/routers/movies/router.py
+++ b/src/amdb/presentation/web_api/routers/movies/router.py
@@ -1,23 +1,22 @@
from fastapi import APIRouter
-from .get_movies import get_movies
-from .get_movie import get_movie
+from .get_movies import get_non_detailed_movies, get_detailed_movie
def create_movies_router() -> APIRouter:
router = APIRouter(
- prefix="/movies",
+ prefix="",
tags=["movies"],
)
router.add_api_route(
- path="",
- endpoint=get_movies,
+ path="/non-detailed-movies",
+ endpoint=get_non_detailed_movies,
methods=["GET"],
)
router.add_api_route(
- path="/{movie_id}",
- endpoint=get_movie,
+ path="/detailed-movies/{movie_id}",
+ endpoint=get_detailed_movie,
methods=["GET"],
)
diff --git a/src/amdb/presentation/web_api/routers/ratings/get_movie_ratings.py b/src/amdb/presentation/web_api/routers/ratings/get_movie_ratings.py
deleted file mode 100644
index 660eec9..0000000
--- a/src/amdb/presentation/web_api/routers/ratings/get_movie_ratings.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from typing import Annotated
-
-from fastapi import Depends
-
-from amdb.domain.entities.movie import MovieId
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.queries.get_movie_ratings import (
- GetMovieRatingsQuery,
- GetMovieRatingsResult,
-)
-from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import (
- get_identity_provider,
-)
-
-
-async def get_movie_ratings(
- ioc: Annotated[HandlerFactory, Depends()],
- identity_provider: Annotated[
- IdentityProvider, Depends(get_identity_provider)
- ],
- movie_id: MovieId,
- limit: int = 100,
- offset: int = 0,
-) -> GetMovieRatingsResult:
- """
- ## Errors: \n
- - When access is denied \n
- - When movie doesn't exist \n
- """
- with ioc.get_movie_ratings(identity_provider) as get_movie_ratings_handler:
- get_movie_ratings_query = GetMovieRatingsQuery(
- movie_id=movie_id,
- limit=limit,
- offset=offset,
- )
- get_movie_ratings_result = get_movie_ratings_handler.execute(
- get_movie_ratings_query
- )
-
- return get_movie_ratings_result
diff --git a/src/amdb/presentation/web_api/routers/ratings/get_my_ratings.py b/src/amdb/presentation/web_api/routers/ratings/get_my_ratings.py
deleted file mode 100644
index f0aa378..0000000
--- a/src/amdb/presentation/web_api/routers/ratings/get_my_ratings.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from typing import Annotated
-
-from fastapi import Depends
-
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.queries.get_my_ratings import (
- GetMyRatingsQuery,
- GetMyRatingsResult,
-)
-from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import (
- get_identity_provider,
-)
-
-
-async def get_my_ratings(
- ioc: Annotated[HandlerFactory, Depends()],
- identity_provider: Annotated[
- IdentityProvider, Depends(get_identity_provider)
- ],
- limit: int = 100,
- offset: int = 0,
-) -> GetMyRatingsResult:
- """
- Errors: \n
- - When access is denied \n
- """
- with ioc.get_my_ratings(identity_provider) as get_my_ratings_handler:
- get_my_ratings_query = GetMyRatingsQuery(
- limit=limit,
- offset=offset,
- )
- get_my_ratings_result = get_my_ratings_handler.execute(
- get_my_ratings_query
- )
-
- return get_my_ratings_result
diff --git a/src/amdb/presentation/web_api/routers/ratings/get_rating.py b/src/amdb/presentation/web_api/routers/ratings/get_rating.py
deleted file mode 100644
index 2cb994e..0000000
--- a/src/amdb/presentation/web_api/routers/ratings/get_rating.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from typing import Annotated
-
-from fastapi import Depends
-
-from amdb.domain.entities.rating import RatingId
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.queries.get_rating import GetRatingQuery, GetRatingResult
-from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import (
- get_identity_provider,
-)
-
-
-async def get_rating(
- ioc: Annotated[HandlerFactory, Depends()],
- identity_provider: Annotated[
- IdentityProvider, Depends(get_identity_provider)
- ],
- rating_id: RatingId,
-) -> GetRatingResult:
- """
- ## Errors: \n
- - When access is denied \n
- - When movie doesn't exist \n
- - When rating doesn't exist \n
- """
- with ioc.get_rating(identity_provider) as get_rating_handler:
- get_rating_query = GetRatingQuery(
- rating_id=rating_id,
- )
- get_rating_result = get_rating_handler.execute(get_rating_query)
-
- return get_rating_result
diff --git a/src/amdb/presentation/web_api/routers/ratings/rate_movie.py b/src/amdb/presentation/web_api/routers/ratings/rate_movie.py
index 6ba1727..585a648 100644
--- a/src/amdb/presentation/web_api/routers/ratings/rate_movie.py
+++ b/src/amdb/presentation/web_api/routers/ratings/rate_movie.py
@@ -3,9 +3,7 @@
from fastapi import Depends
from amdb.domain.entities.rating import RatingId
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
+from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.commands.rate_movie import RateMovieCommand
from amdb.presentation.handler_factory import HandlerFactory
from amdb.presentation.web_api.dependencies.identity_provider import (
@@ -16,7 +14,8 @@
async def rate_movie(
ioc: Annotated[HandlerFactory, Depends(HandlerFactory)],
identity_provider: Annotated[
- IdentityProvider, Depends(get_identity_provider)
+ IdentityProvider,
+ Depends(get_identity_provider),
],
rate_movie_command: RateMovieCommand,
) -> RatingId:
diff --git a/src/amdb/presentation/web_api/routers/ratings/router.py b/src/amdb/presentation/web_api/routers/ratings/router.py
index 224b3ba..26fbd93 100644
--- a/src/amdb/presentation/web_api/routers/ratings/router.py
+++ b/src/amdb/presentation/web_api/routers/ratings/router.py
@@ -1,8 +1,5 @@
from fastapi import APIRouter
-from .get_movie_ratings import get_movie_ratings
-from .get_my_ratings import get_my_ratings
-from .get_rating import get_rating
from .rate_movie import rate_movie
from .unrate_movie import unrate_movie
@@ -13,35 +10,12 @@ def create_ratings_router() -> APIRouter:
tags=["ratings"],
)
- router.add_api_route(
- path="/movies/{movie_id}/ratings",
- endpoint=get_movie_ratings,
- methods=["GET"],
- tags=["movies"],
- )
- router.add_api_route(
- path="/ratings/{rating_id}",
- endpoint=get_rating,
- methods=["GET"],
- )
router.add_api_route(
path="/me/ratings",
endpoint=rate_movie,
methods=["POST"],
tags=["me"],
)
- router.add_api_route(
- path="/me/ratings",
- endpoint=get_my_ratings,
- methods=["GET"],
- tags=["me"],
- )
- router.add_api_route(
- path="/me/ratings/{rating_id}",
- endpoint=get_rating,
- methods=["GET"],
- tags=["me"],
- )
router.add_api_route(
path="/me/ratings/{rating_id}",
endpoint=unrate_movie,
diff --git a/src/amdb/presentation/web_api/routers/ratings/unrate_movie.py b/src/amdb/presentation/web_api/routers/ratings/unrate_movie.py
index 08c30c6..e0b9f4f 100644
--- a/src/amdb/presentation/web_api/routers/ratings/unrate_movie.py
+++ b/src/amdb/presentation/web_api/routers/ratings/unrate_movie.py
@@ -3,9 +3,7 @@
from fastapi import Depends
from amdb.domain.entities.rating import RatingId
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
+from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.commands.unrate_movie import UnrateMovieCommand
from amdb.presentation.handler_factory import HandlerFactory
from amdb.presentation.web_api.dependencies.identity_provider import (
@@ -16,7 +14,8 @@
async def unrate_movie(
ioc: Annotated[HandlerFactory, Depends(HandlerFactory)],
identity_provider: Annotated[
- IdentityProvider, Depends(get_identity_provider)
+ IdentityProvider,
+ Depends(get_identity_provider),
],
rating_id: RatingId,
) -> None:
diff --git a/src/amdb/presentation/web_api/routers/reviews/get_movie_reviews.py b/src/amdb/presentation/web_api/routers/reviews/get_movie_reviews.py
deleted file mode 100644
index e4c6bdc..0000000
--- a/src/amdb/presentation/web_api/routers/reviews/get_movie_reviews.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from typing import Annotated
-
-from fastapi import Depends
-
-from amdb.domain.entities.movie import MovieId
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.queries.get_movie_reviews import (
- GetMovieReviewsQuery,
- GetMovieReviewsResult,
-)
-from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import (
- get_identity_provider,
-)
-
-
-async def get_movie_reviews(
- ioc: Annotated[HandlerFactory, Depends()],
- identity_provider: Annotated[
- IdentityProvider, Depends(get_identity_provider)
- ],
- movie_id: MovieId,
- limit: int = 100,
- offset: int = 0,
-) -> GetMovieReviewsResult:
- """
- ## Errors: \n
- - When access is denied \n
- - When movie doesn't exist \n
- """
- with ioc.get_movie_reviews(identity_provider) as get_movie_reviews_handler:
- get_movie_reviews_query = GetMovieReviewsQuery(
- movie_id=movie_id,
- limit=limit,
- offset=offset,
- )
- get_movie_reviews_result = get_movie_reviews_handler.execute(
- get_movie_reviews_query
- )
-
- return get_movie_reviews_result
diff --git a/src/amdb/presentation/web_api/routers/reviews/get_my_reviews.py b/src/amdb/presentation/web_api/routers/reviews/get_my_reviews.py
deleted file mode 100644
index 60942e5..0000000
--- a/src/amdb/presentation/web_api/routers/reviews/get_my_reviews.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from typing import Annotated
-
-from fastapi import Depends
-
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.queries.get_my_reviews import (
- GetMyReviewsQuery,
- GetMyReviewsResult,
-)
-from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import (
- get_identity_provider,
-)
-
-
-async def get_my_reviews(
- ioc: Annotated[HandlerFactory, Depends()],
- identity_provider: Annotated[
- IdentityProvider, Depends(get_identity_provider)
- ],
- limit: int = 100,
- offset: int = 0,
-) -> GetMyReviewsResult:
- with ioc.get_my_reviews(identity_provider) as get_my_reviews_handler:
- get_my_reviews_query = GetMyReviewsQuery(
- limit=limit,
- offset=offset,
- )
- get_my_reviews_result = get_my_reviews_handler.execute(
- get_my_reviews_query
- )
-
- return get_my_reviews_result
diff --git a/src/amdb/presentation/web_api/routers/reviews/get_review.py b/src/amdb/presentation/web_api/routers/reviews/get_review.py
deleted file mode 100644
index 5c1d7a2..0000000
--- a/src/amdb/presentation/web_api/routers/reviews/get_review.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from typing import Annotated
-
-from fastapi import Depends
-
-from amdb.domain.entities.review import ReviewId
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
-from amdb.application.queries.get_review import GetReviewQuery, GetReviewResult
-from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import (
- get_identity_provider,
-)
-
-
-async def get_review(
- ioc: Annotated[HandlerFactory, Depends()],
- identity_provider: Annotated[
- IdentityProvider, Depends(get_identity_provider)
- ],
- review_id: ReviewId,
-) -> GetReviewResult:
- """
- ## Errors: \n
- - When access is denied \n
- - When review doesn't exist \n
- """
- with ioc.get_review(identity_provider) as get_review_handler:
- get_review_query = GetReviewQuery(
- review_id=review_id,
- )
- get_review_result = get_review_handler.execute(get_review_query)
-
- return get_review_result
diff --git a/src/amdb/presentation/web_api/routers/reviews/get_reviews.py b/src/amdb/presentation/web_api/routers/reviews/get_reviews.py
new file mode 100644
index 0000000..70c5962
--- /dev/null
+++ b/src/amdb/presentation/web_api/routers/reviews/get_reviews.py
@@ -0,0 +1,28 @@
+from typing import Annotated
+
+from fastapi import Depends
+
+from amdb.domain.entities.movie import MovieId
+from amdb.application.common.view_models.review import ReviewViewModel
+from amdb.application.queries.reviews import GetReviewsQuery
+from amdb.presentation.handler_factory import HandlerFactory
+
+
+async def get_reviews(
+ ioc: Annotated[HandlerFactory, Depends()],
+ movie_id: MovieId,
+ limit: int = 100,
+ offset: int = 0,
+) -> list[ReviewViewModel]:
+ """
+ ## Errors: \n
+ - When movie doesn't exist \n
+ """
+ with ioc.get_reviews() as get_reviews_handler:
+ get_reviews_query = GetReviewsQuery(
+ movie_id=movie_id,
+ limit=limit,
+ offset=offset,
+ )
+ result = get_reviews_handler.execute(get_reviews_query)
+ return result
diff --git a/src/amdb/presentation/web_api/routers/reviews/review_movie.py b/src/amdb/presentation/web_api/routers/reviews/review_movie.py
index 2e47a0a..0498eb9 100644
--- a/src/amdb/presentation/web_api/routers/reviews/review_movie.py
+++ b/src/amdb/presentation/web_api/routers/reviews/review_movie.py
@@ -5,9 +5,7 @@
from amdb.domain.entities.movie import MovieId
from amdb.domain.entities.review import ReviewId, ReviewType
-from amdb.application.common.interfaces.identity_provider import (
- IdentityProvider,
-)
+from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.commands.review_movie import ReviewMovieCommand
from amdb.presentation.handler_factory import HandlerFactory
from amdb.presentation.web_api.dependencies.identity_provider import (
@@ -24,7 +22,8 @@ class ReviewMovie(BaseModel):
async def review_movie(
ioc: Annotated[HandlerFactory, Depends()],
identity_provider: Annotated[
- IdentityProvider, Depends(get_identity_provider)
+ IdentityProvider,
+ Depends(get_identity_provider),
],
movie_id: MovieId,
data: ReviewMovie,
diff --git a/src/amdb/presentation/web_api/routers/reviews/router.py b/src/amdb/presentation/web_api/routers/reviews/router.py
index 4e15602..4fb4ce9 100644
--- a/src/amdb/presentation/web_api/routers/reviews/router.py
+++ b/src/amdb/presentation/web_api/routers/reviews/router.py
@@ -1,9 +1,7 @@
from fastapi import APIRouter
-from .get_movie_reviews import get_movie_reviews
-from .get_my_reviews import get_my_reviews
+from .get_reviews import get_reviews
from .review_movie import review_movie
-from .get_review import get_review
def create_reviews_router() -> APIRouter:
@@ -14,13 +12,7 @@ def create_reviews_router() -> APIRouter:
router.add_api_route(
path="/movies/{movie_id}/reviews",
- endpoint=get_movie_reviews,
- methods=["GET"],
- tags=["movies"],
- )
- router.add_api_route(
- path="/reviews/{review_id}",
- endpoint=get_review,
+ endpoint=get_reviews,
methods=["GET"],
)
router.add_api_route(
@@ -29,17 +21,5 @@ def create_reviews_router() -> APIRouter:
methods=["POST"],
tags=["me"],
)
- router.add_api_route(
- path="/me/reviews",
- endpoint=get_my_reviews,
- methods=["GET"],
- tags=["me"],
- )
- router.add_api_route(
- path="/me/reviews/{review_id}",
- endpoint=get_review,
- methods=["GET"],
- tags=["me"],
- )
return router
diff --git a/tests/unit/application/command_handlers/test_create_movie.py b/tests/unit/application/command_handlers/test_create_movie.py
index 3486bd8..5cbbb86 100644
--- a/tests/unit/application/command_handlers/test_create_movie.py
+++ b/tests/unit/application/command_handlers/test_create_movie.py
@@ -24,7 +24,7 @@ def identity_provider_with_correct_permissions(
identity_provider = Mock()
correct_permissions = permissions_gateway.for_create_movie()
- identity_provider.get_permissions = Mock(return_value=correct_permissions)
+ identity_provider.permissions = Mock(return_value=correct_permissions)
return identity_provider
diff --git a/tests/unit/application/command_handlers/test_delete_movie.py b/tests/unit/application/command_handlers/test_delete_movie.py
index be66daa..acad807 100644
--- a/tests/unit/application/command_handlers/test_delete_movie.py
+++ b/tests/unit/application/command_handlers/test_delete_movie.py
@@ -28,7 +28,7 @@ def identity_provider_with_correct_permissions(
identity_provider = Mock()
correct_permissions = permissions_gateway.for_delete_movie()
- identity_provider.get_permissions = Mock(return_value=correct_permissions)
+ identity_provider.permissions = Mock(return_value=correct_permissions)
return identity_provider
diff --git a/tests/unit/application/command_handlers/test_rate_movie.py b/tests/unit/application/command_handlers/test_rate_movie.py
index 559f0ef..e6b35f2 100644
--- a/tests/unit/application/command_handlers/test_rate_movie.py
+++ b/tests/unit/application/command_handlers/test_rate_movie.py
@@ -34,7 +34,7 @@ def identity_provider_with_correct_permissions(
identity_provider = Mock()
correct_permissions = permissions_gateway.for_rate_movie()
- identity_provider.get_permissions = Mock(return_value=correct_permissions)
+ identity_provider.permissions = Mock(return_value=correct_permissions)
return identity_provider
@@ -64,7 +64,7 @@ def test_rate_movie(
unit_of_work.commit()
- identity_provider_with_correct_permissions.get_user_id = Mock(
+ identity_provider_with_correct_permissions.user_id = Mock(
return_value=user.id,
)
@@ -178,7 +178,7 @@ def test_rate_movie_should_raise_error_when_movie_already_rated(
unit_of_work.commit()
- identity_provider_with_correct_permissions.get_user_id = Mock(
+ identity_provider_with_correct_permissions.user_id = Mock(
return_value=user.id,
)
@@ -242,7 +242,7 @@ def test_rate_movie_should_raise_error_when_rating_is_invalid(
unit_of_work.commit()
- identity_provider_with_correct_permissions.get_user_id = Mock(
+ identity_provider_with_correct_permissions.user_id = Mock(
return_value=user.id,
)
diff --git a/tests/unit/application/command_handlers/test_review_movie.py b/tests/unit/application/command_handlers/test_review_movie.py
index fd5982d..ecccbc0 100644
--- a/tests/unit/application/command_handlers/test_review_movie.py
+++ b/tests/unit/application/command_handlers/test_review_movie.py
@@ -32,7 +32,7 @@ def identity_provider_with_correct_permissions(
identity_provider = Mock()
correct_permissions = permissions_gateway.for_review_movie()
- identity_provider.get_permissions = Mock(return_value=correct_permissions)
+ identity_provider.permissions = Mock(return_value=correct_permissions)
return identity_provider
@@ -62,7 +62,7 @@ def test_review_movie(
unit_of_work.commit()
- identity_provider_with_correct_permissions.get_user_id = Mock(
+ identity_provider_with_correct_permissions.user_id = Mock(
return_value=user.id,
)
@@ -184,7 +184,7 @@ def test_review_movie_should_raise_error_when_movie_already_reviewed(
unit_of_work.commit()
- identity_provider_with_correct_permissions.get_user_id = Mock(
+ identity_provider_with_correct_permissions.user_id = Mock(
return_value=user.id,
)
diff --git a/tests/unit/application/command_handlers/test_unrate_movie.py b/tests/unit/application/command_handlers/test_unrate_movie.py
index c3ba201..bc5d2b1 100644
--- a/tests/unit/application/command_handlers/test_unrate_movie.py
+++ b/tests/unit/application/command_handlers/test_unrate_movie.py
@@ -32,7 +32,7 @@ def identity_provider_with_correct_permissions(
identity_provider = Mock()
correct_permissions = permissions_gateway.for_unrate_movie()
- identity_provider.get_permissions = Mock(return_value=correct_permissions)
+ identity_provider.permissions = Mock(return_value=correct_permissions)
return identity_provider
@@ -71,7 +71,7 @@ def test_unrate_movie(
unit_of_work.commit()
- identity_provider_with_correct_permissions.get_user_id = Mock(
+ identity_provider_with_correct_permissions.user_id = Mock(
return_value=user.id,
)
@@ -177,7 +177,7 @@ def test_unrate_movie_should_raise_error_when_user_is_not_rating_owner(
unit_of_work.commit()
- identity_provider_with_correct_permissions.get_user_id = Mock(
+ identity_provider_with_correct_permissions.user_id = Mock(
return_value=UserId(uuid7()),
)
diff --git a/tests/unit/application/conftest.py b/tests/unit/application/conftest.py
index 76a8d77..0af152e 100644
--- a/tests/unit/application/conftest.py
+++ b/tests/unit/application/conftest.py
@@ -2,49 +2,44 @@
from unittest.mock import Mock
import pytest
-from sqlalchemy import Engine, create_engine
-from sqlalchemy.orm import Session
+from sqlalchemy import Connection, Engine, create_engine
from redis.client import Redis
from amdb.infrastructure.persistence.sqlalchemy.models.base import Model
-from amdb.infrastructure.persistence.sqlalchemy.gateways.user import (
- SQLAlchemyUserGateway,
+from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.user import (
+ UserMapper,
)
-from amdb.infrastructure.persistence.sqlalchemy.gateways.movie import (
- SQLAlchemyMovieGateway,
+from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.movie import (
+ MovieMapper,
)
-from amdb.infrastructure.persistence.sqlalchemy.gateways.rating import (
- SQLAlchemyRatingGateway,
+from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.rating import (
+ RatingMapper,
)
-from amdb.infrastructure.persistence.sqlalchemy.gateways.user_password_hash import (
- SQLAlchemyUserPasswordHashGateway,
+from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.review import (
+ ReviewMapper,
)
-from amdb.infrastructure.persistence.sqlalchemy.gateways.review import (
- SQLAlchemyReviewGateway,
+from amdb.infrastructure.persistence.sqlalchemy.mappers.password_hash import (
+ PasswordHashMapper,
)
-from amdb.infrastructure.persistence.redis.gateways.permissions import (
- RedisPermissionsGateway,
+from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.non_detailed_movie import (
+ NonDetailedMovieViewModelMapper,
)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.user import UserMapper
-from amdb.infrastructure.persistence.sqlalchemy.mappers.movie import (
- MovieMapper,
+from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.detailed_movie import (
+ DetailedMovieViewModelMapper,
)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.rating import (
- RatingMapper,
+from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.review import (
+ ReviewViewModelMapper,
)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.user_password_hash import (
- UserPasswordHashMapper,
+from amdb.infrastructure.persistence.redis.mappers.permissions import (
+ PermissionsMapper,
)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.review import (
- ReviewMapper,
-)
-from amdb.infrastructure.security.hasher import Hasher
+from amdb.infrastructure.password_manager.hash_computer import HashComputer
from amdb.infrastructure.password_manager.password_manager import (
HashingPasswordManager,
)
-@pytest.fixture(scope="package")
+@pytest.fixture(scope="session")
def sqlalchemy_engine(postgres_url: str) -> Engine:
return create_engine(url=postgres_url)
@@ -57,61 +52,74 @@ def clear_database(sqlalchemy_engine: Engine) -> Iterator[None]:
@pytest.fixture
-def sqlalchemy_session(sqlalchemy_engine: Engine) -> Iterator[Session]:
+def sqlalchemy_connection(sqlalchemy_engine: Engine) -> Iterator[Connection]:
connection = sqlalchemy_engine.connect()
- session = Session(connection, expire_on_commit=False)
+ yield connection
+ connection.close()
- yield session
- session.close()
- connection.close()
+@pytest.fixture
+def permissions_gateway(redis: Redis) -> PermissionsMapper:
+ return PermissionsMapper(redis)
@pytest.fixture
-def permissions_gateway(redis: Redis) -> RedisPermissionsGateway:
- return RedisPermissionsGateway(redis)
+def user_gateway(sqlalchemy_connection: Connection) -> UserMapper:
+ return UserMapper(sqlalchemy_connection)
@pytest.fixture
-def user_gateway(sqlalchemy_session: Session) -> SQLAlchemyUserGateway:
- return SQLAlchemyUserGateway(sqlalchemy_session, UserMapper())
+def movie_gateway(sqlalchemy_connection: Connection) -> MovieMapper:
+ return MovieMapper(sqlalchemy_connection)
@pytest.fixture
-def movie_gateway(sqlalchemy_session: Session) -> SQLAlchemyMovieGateway:
- return SQLAlchemyMovieGateway(sqlalchemy_session, MovieMapper())
+def rating_gateway(sqlalchemy_connection: Connection) -> RatingMapper:
+ return RatingMapper(sqlalchemy_connection)
@pytest.fixture
-def rating_gateway(sqlalchemy_session: Session) -> SQLAlchemyRatingGateway:
- return SQLAlchemyRatingGateway(sqlalchemy_session, RatingMapper())
+def review_gateway(sqlalchemy_connection: Connection) -> ReviewMapper:
+ return ReviewMapper(sqlalchemy_connection)
@pytest.fixture
-def review_gateway(sqlalchemy_session: Session) -> SQLAlchemyReviewGateway:
- return SQLAlchemyReviewGateway(sqlalchemy_session, ReviewMapper())
+def detailed_movie_reader(
+ sqlalchemy_connection: Connection,
+) -> DetailedMovieViewModelMapper:
+ return DetailedMovieViewModelMapper(sqlalchemy_connection)
@pytest.fixture
-def password_manager(sqlalchemy_session: Session) -> HashingPasswordManager:
- user_password_hash_gateway = SQLAlchemyUserPasswordHashGateway(
- session=sqlalchemy_session,
- mapper=UserPasswordHashMapper(),
- )
+def non_detailed_movie_reader(
+ sqlalchemy_connection: Connection,
+) -> NonDetailedMovieViewModelMapper:
+ return NonDetailedMovieViewModelMapper(sqlalchemy_connection)
+
+
+@pytest.fixture
+def review_reader(sqlalchemy_connection: Connection) -> ReviewViewModelMapper:
+ return ReviewViewModelMapper(sqlalchemy_connection)
+
+
+@pytest.fixture
+def password_manager(
+ sqlalchemy_connection: Connection,
+) -> HashingPasswordManager:
return HashingPasswordManager(
- hasher=Hasher(),
- user_password_hash_gateway=user_password_hash_gateway,
+ hash_computer=HashComputer(),
+ password_hash_gateway=PasswordHashMapper(sqlalchemy_connection),
)
@pytest.fixture
-def unit_of_work(sqlalchemy_session: Session) -> Session:
- return sqlalchemy_session
+def unit_of_work(sqlalchemy_connection: Connection) -> Connection:
+ return sqlalchemy_connection
@pytest.fixture(scope="session")
def identity_provider_with_incorrect_permissions() -> Mock:
identity_provider = Mock()
- identity_provider.get_permissions = Mock(return_value=0)
+ identity_provider.permissions = Mock(return_value=0)
return identity_provider
diff --git a/tests/unit/application/query_handlers/test_detailed_movie.py b/tests/unit/application/query_handlers/test_detailed_movie.py
index 8331c92..c86e5fd 100644
--- a/tests/unit/application/query_handlers/test_detailed_movie.py
+++ b/tests/unit/application/query_handlers/test_detailed_movie.py
@@ -8,14 +8,14 @@
from amdb.domain.entities.movie import MovieId, Movie
from amdb.domain.entities.rating import RatingId, Rating
from amdb.domain.entities.review import ReviewId, ReviewType, Review
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.gateways.permissions import PermissionsGateway
from amdb.application.common.gateways.user import UserGateway
from amdb.application.common.gateways.movie import MovieGateway
from amdb.application.common.gateways.rating import RatingGateway
from amdb.application.common.gateways.review import ReviewGateway
from amdb.application.common.unit_of_work import UnitOfWork
-from amdb.application.common.readers.movie import MovieViewModelReader
+from amdb.application.common.readers.detailed_movie import (
+ DetailedMovieViewModelReader,
+)
from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.common.view_models.detailed_movie import (
UserRating,
@@ -23,36 +23,20 @@
DetailedMovieViewModel,
)
from amdb.application.queries.detailed_movie import GetDetailedMovieQuery
-from amdb.application.query_handlers.detailed_movie import GetDetailedMovieHandler
-from amdb.application.common.constants.exceptions import (
- GET_MOVIE_ACCESS_DENIED,
- MOVIE_DOES_NOT_EXIST,
+from amdb.application.query_handlers.detailed_movie import (
+ GetDetailedMovieHandler,
)
+from amdb.application.common.constants.exceptions import MOVIE_DOES_NOT_EXIST
from amdb.application.common.exception import ApplicationError
-@pytest.fixture
-def identity_provider_with_correct_permissions(
- permissions_gateway: PermissionsGateway,
-) -> IdentityProvider:
- identity_provider = Mock()
-
- correct_permissions = permissions_gateway.for_get_movie()
- identity_provider.get_permissions = Mock(return_value=correct_permissions)
-
- return identity_provider
-
-
-
def test_get_detailed_movie(
user_gateway: UserGateway,
movie_gateway: MovieGateway,
rating_gateway: RatingGateway,
review_gateway: ReviewGateway,
unit_of_work: UnitOfWork,
- permissions_gateway: PermissionsGateway,
- movie_view_model_reader: MovieViewModelReader,
- identity_provider_with_correct_permissions: IdentityProvider,
+ detailed_movie_reader: DetailedMovieViewModelReader,
):
user = User(
id=UserId(uuid7()),
@@ -91,7 +75,8 @@ def test_get_detailed_movie(
unit_of_work.commit()
- identity_provider_with_correct_permissions.get_user_id = Mock(
+ identity_provider: IdentityProvider = Mock()
+ identity_provider.user_id_or_none = Mock(
return_value=user.id,
)
@@ -99,16 +84,15 @@ def test_get_detailed_movie(
movie_id=movie.id,
)
get_detailed_movie_handler = GetDetailedMovieHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- movie_view_model_reader=movie_view_model_reader,
- identity_provider=identity_provider_with_correct_permissions,
+ detailed_movie_reader=detailed_movie_reader,
+ identity_provider=identity_provider,
)
expected_result = DetailedMovieViewModel(
id=movie.id,
title=movie.title,
release_date=movie.release_date,
+ rating=movie.rating,
rating_count=movie.rating_count,
user_rating=UserRating(
id=rating.id,
@@ -120,52 +104,28 @@ def test_get_detailed_movie(
title=review.title,
content=review.content,
type=review.type,
- created_at=review.type,
- )
+ created_at=review.created_at,
+ ),
)
result = get_detailed_movie_handler.execute(get_detailed_movie_query)
assert expected_result == result
-def test_get_detailed_movie_should_raise_error_when_access_is_denied(
- permissions_gateway: PermissionsGateway,
- movie_view_model_reader: MovieViewModelReader,
- identity_provider_with_incorrect_permissions: IdentityProvider,
-):
- get_detailed_movie_query = GetDetailedMovieQuery(
- movie_id=MovieId(uuid7()),
- )
- get_detailed_movie_handler = GetDetailedMovieHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- movie_view_model_reader=movie_view_model_reader,
- identity_provider=identity_provider_with_incorrect_permissions,
- )
-
- with pytest.raises(ApplicationError) as error:
- get_detailed_movie_handler.execute(get_detailed_movie_query)
-
- assert error.value.message == GET_MOVIE_ACCESS_DENIED
-
-
def test_get_detailed_movie_should_raise_error_when_movie_does_not_exist(
- permissions_gateway: PermissionsGateway,
- movie_view_model_reader: MovieViewModelReader,
- identity_provider_with_correct_permissions: IdentityProvider,
+ detailed_movie_reader: DetailedMovieViewModelReader,
):
- identity_provider_with_correct_permissions.get_user_id = Mock(
- return_value=UserId(uuid7()),
+ identity_provider: IdentityProvider = Mock()
+ identity_provider.user_id_or_none = Mock(
+ return_value=None,
)
get_detailed_movie_query = GetDetailedMovieQuery(
movie_id=MovieId(uuid7()),
)
get_detailed_movie_handler = GetDetailedMovieHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- movie_view_model_reader=movie_view_model_reader,
- identity_provider=identity_provider_with_correct_permissions,
+ detailed_movie_reader=detailed_movie_reader,
+ identity_provider=identity_provider,
)
with pytest.raises(ApplicationError) as error:
diff --git a/tests/unit/application/query_handlers/test_get_reviews.py b/tests/unit/application/query_handlers/test_get_reviews.py
new file mode 100644
index 0000000..86b4d8b
--- /dev/null
+++ b/tests/unit/application/query_handlers/test_get_reviews.py
@@ -0,0 +1,121 @@
+from datetime import date, datetime, timezone
+
+import pytest
+from uuid_extensions import uuid7
+
+from amdb.domain.entities.user import UserId, User
+from amdb.domain.entities.movie import MovieId, Movie
+from amdb.domain.entities.rating import RatingId, Rating
+from amdb.domain.entities.review import ReviewId, ReviewType, Review
+from amdb.application.common.gateways.user import UserGateway
+from amdb.application.common.gateways.movie import MovieGateway
+from amdb.application.common.gateways.rating import RatingGateway
+from amdb.application.common.gateways.review import ReviewGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.readers.review import ReviewViewModelReader
+from amdb.application.common.view_models.review import (
+ UserRating,
+ UserReview,
+ ReviewViewModel,
+)
+from amdb.application.queries.reviews import GetReviewsQuery
+from amdb.application.query_handlers.reviews import GetReviewsHandler
+from amdb.application.common.constants.exceptions import MOVIE_DOES_NOT_EXIST
+from amdb.application.common.exception import ApplicationError
+
+
+def test_get_reviews(
+ user_gateway: UserGateway,
+ movie_gateway: MovieGateway,
+ rating_gateway: RatingGateway,
+ review_gateway: ReviewGateway,
+ unit_of_work: UnitOfWork,
+ review_reader: ReviewViewModelReader,
+):
+ user = User(
+ id=UserId(uuid7()),
+ name="John Doe",
+ )
+ user_gateway.save(user)
+
+ movie = Movie(
+ id=MovieId(uuid7()),
+ title="Matrix",
+ release_date=date(1999, 3, 31),
+ rating=8,
+ rating_count=1,
+ )
+ movie_gateway.save(movie)
+
+ rating = Rating(
+ id=RatingId(uuid7()),
+ movie_id=movie.id,
+ user_id=user.id,
+ value=8,
+ created_at=datetime.now(timezone.utc),
+ )
+ rating_gateway.save(rating)
+
+ review = Review(
+ id=ReviewId(uuid7()),
+ user_id=user.id,
+ movie_id=movie.id,
+ title="Not bad",
+ content="Great soundtrack",
+ type=ReviewType.POSITIVE,
+ created_at=datetime.now(timezone.utc),
+ )
+ review_gateway.save(review)
+
+ unit_of_work.commit()
+
+ get_reviews_query = GetReviewsQuery(
+ movie_id=movie.id,
+ limit=10,
+ offset=0,
+ )
+ get_reviews_handler = GetReviewsHandler(
+ movie_gateway=movie_gateway,
+ review_view_model_reader=review_reader,
+ )
+
+ expected_result = [
+ ReviewViewModel(
+ user_id=user.id,
+ user_review=UserReview(
+ id=review.id,
+ title=review.title,
+ content=review.content,
+ type=review.type,
+ created_at=review.created_at,
+ ),
+ user_rating=UserRating(
+ id=rating.id,
+ value=rating.value,
+ created_at=rating.created_at,
+ ),
+ ),
+ ]
+ result = get_reviews_handler.execute(get_reviews_query)
+
+ assert expected_result == result
+
+
+def test_get_reviews_should_raise_error_when_movie_does_not_exist(
+ movie_gateway: MovieGateway,
+ review_reader: ReviewViewModelReader,
+):
+ get_reviews_query = GetReviewsQuery(
+ movie_id=MovieId(uuid7()),
+ limit=10,
+ offset=0,
+ )
+ get_reviews_handler = GetReviewsHandler(
+ movie_gateway=movie_gateway,
+ review_view_model_reader=review_reader,
+ )
+
+ with pytest.raises(ApplicationError) as error:
+ get_reviews_handler.execute(get_reviews_query)
+
+ assert error.value.message == MOVIE_DOES_NOT_EXIST
diff --git a/tests/unit/application/query_handlers/test_non_detailed_movies.py b/tests/unit/application/query_handlers/test_non_detailed_movies.py
index 6b32a6a..633e9d7 100644
--- a/tests/unit/application/query_handlers/test_non_detailed_movies.py
+++ b/tests/unit/application/query_handlers/test_non_detailed_movies.py
@@ -1,40 +1,29 @@
from unittest.mock import Mock
from datetime import date, datetime, timezone
-import pytest
from uuid_extensions import uuid7
from amdb.domain.entities.user import UserId, User
from amdb.domain.entities.movie import MovieId, Movie
from amdb.domain.entities.rating import RatingId, Rating
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.gateways.permissions import PermissionsGateway
from amdb.application.common.gateways.user import UserGateway
from amdb.application.common.gateways.movie import MovieGateway
from amdb.application.common.gateways.rating import RatingGateway
from amdb.application.common.unit_of_work import UnitOfWork
-from amdb.application.common.readers.movie import MovieViewModelReader
+from amdb.application.common.readers.non_detailed_movie import (
+ NonDetailedMovieViewModelReader,
+)
from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.common.view_models.non_detailed_movie import (
UserRating,
NonDetailedMovieViewModel,
)
-from amdb.application.queries.non_detailed_movies import GetNonDetailedMoviesQuery
-from amdb.application.query_handlers.non_detailed_movies import GetNonDetailedMoviesHandler
-from amdb.application.common.constants.exceptions import GET_MOVIE_ACCESS_DENIED
-from amdb.application.common.exception import ApplicationError
-
-
-@pytest.fixture
-def identity_provider_with_correct_permissions(
- permissions_gateway: PermissionsGateway,
-) -> IdentityProvider:
- identity_provider = Mock()
-
- correct_permissions = permissions_gateway.for_get_movie()
- identity_provider.get_permissions = Mock(return_value=correct_permissions)
-
- return identity_provider
+from amdb.application.queries.non_detailed_movies import (
+ GetNonDetailedMoviesQuery,
+)
+from amdb.application.query_handlers.non_detailed_movies import (
+ GetNonDetailedMoviesHandler,
+)
def test_get_non_detailed_movies(
@@ -42,9 +31,7 @@ def test_get_non_detailed_movies(
movie_gateway: MovieGateway,
rating_gateway: RatingGateway,
unit_of_work: UnitOfWork,
- permissions_gateway: PermissionsGateway,
- movie_view_model_reader: MovieViewModelReader,
- identity_provider_with_correct_permissions: IdentityProvider,
+ non_detailed_movie_reader: NonDetailedMovieViewModelReader,
):
user = User(
id=UserId(uuid7()),
@@ -72,7 +59,8 @@ def test_get_non_detailed_movies(
unit_of_work.commit()
- identity_provider_with_correct_permissions.get_user_id = Mock(
+ identity_provider: IdentityProvider = Mock()
+ identity_provider.user_id_or_none = Mock(
return_value=user.id,
)
@@ -81,14 +69,12 @@ def test_get_non_detailed_movies(
offset=0,
)
get_non_detailed_movies_handler = GetNonDetailedMoviesHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- movie_view_model_reader=movie_view_model_reader,
- identity_provider=identity_provider_with_correct_permissions,
+ non_detailed_movie_reader=non_detailed_movie_reader,
+ identity_provider=identity_provider,
)
expected_result = [
- NonDetailedMovieViewModel(
+ NonDetailedMovieViewModel(
id=movie.id,
title=movie.title,
release_date=movie.release_date,
@@ -99,28 +85,8 @@ def test_get_non_detailed_movies(
),
),
]
- result = get_non_detailed_movies_handler.execute(get_non_detailed_movies_query)
-
- assert expected_result == result
-
-
-def test_get_non_detailed_movies_should_raise_error_when_access_is_denied(
- permissions_gateway: PermissionsGateway,
- movie_view_model_reader: MovieViewModelReader,
- identity_provider_with_incorrect_permissions: IdentityProvider,
-):
- get_non_detailed_movies_query = GetNonDetailedMoviesQuery(
- limit=10,
- offset=0,
- )
- get_non_detailed_movies_handler = GetNonDetailedMoviesHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- movie_view_model_reader=movie_view_model_reader,
- identity_provider=identity_provider_with_incorrect_permissions,
+ result = get_non_detailed_movies_handler.execute(
+ get_non_detailed_movies_query,
)
- with pytest.raises(ApplicationError) as error:
- get_non_detailed_movies_handler.execute(get_non_detailed_movies_query)
-
- assert error.value.message == GET_MOVIE_ACCESS_DENIED
+ assert expected_result == result
diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py
index aa4ad28..f2cefb3 100644
--- a/tests/unit/conftest.py
+++ b/tests/unit/conftest.py
@@ -1,49 +1,24 @@
import os
+from typing import cast
import pytest
from redis.client import Redis
from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
+from amdb.infrastructure.persistence.redis.config import RedisConfig
-TEST_POSTGRES_HOST_ENV = "TEST_POSTGRES_HOST"
-TEST_POSTGRES_PORT_ENV = "TEST_POSTGRES_PORT"
-TEST_POSTGRES_NAME_ENV = "TEST_POSTGRES_DB"
-TEST_POSTGRES_USER_ENV = "TEST_POSTGRES_USER"
-TEST_POSTGRES_PASSWORD_ENV = "TEST_POSTGRES_PASSWORD"
-
-TEST_REDIS_HOST_ENV = "TEST_REDIS_HOST"
-TEST_REDIS_PORT_ENV = "TEST_REDIS_PORT"
-TEST_REDIS_DB_ENV = "TEST_REDIS_DB"
-TEST_REDIS_PASSWORD_ENV = "TEST_REDIS_PASSWORD"
-
-
-def _get_env(key: str) -> str:
- value = os.getenv(key)
- if value is None:
- message = f"Env variable {key} is not set"
- raise ValueError(message)
- return value
+CONFIG_PATH = os.getenv("TEST_CONFIG_PATH")
@pytest.fixture(scope="session")
def postgres_url() -> str:
- postgres_config = PostgresConfig(
- host=_get_env(TEST_POSTGRES_HOST_ENV),
- port=_get_env(TEST_POSTGRES_PORT_ENV),
- name=_get_env(TEST_POSTGRES_NAME_ENV),
- user=_get_env(TEST_POSTGRES_USER_ENV),
- password=_get_env(TEST_POSTGRES_PASSWORD_ENV),
- )
- return postgres_config.dsn
+ postgres_config = PostgresConfig.from_toml(CONFIG_PATH)
+ return postgres_config.url
@pytest.fixture(scope="session")
def redis() -> Redis:
- redis = Redis(
- host=_get_env(TEST_REDIS_HOST_ENV),
- port=int(_get_env(TEST_REDIS_PORT_ENV)),
- db=int(_get_env(TEST_REDIS_DB_ENV)),
- password=_get_env(TEST_REDIS_PASSWORD_ENV),
- )
- return redis
+ redis_config = RedisConfig.from_toml(CONFIG_PATH)
+ redis = Redis.from_url(redis_config.url, decode_responses=True)
+ return cast(Redis, redis)
diff --git a/tests/unit/infrastructure/alembic/test_stairway.py b/tests/unit/infrastructure/alembic/test_stairway.py
index b50338f..31a3ad7 100644
--- a/tests/unit/infrastructure/alembic/test_stairway.py
+++ b/tests/unit/infrastructure/alembic/test_stairway.py
@@ -7,6 +7,8 @@
https://github.com/alvassin/alembic-quickstart
"""
+from typing import cast
+
import alembic.config
import alembic.command
import alembic.script
@@ -20,7 +22,7 @@ def get_revisions(
# Get & sort migrations, from first to last
revisions: list[alembic.script.Script] = list(
- revisions_dir.walk_revisions("base", "heads")
+ revisions_dir.walk_revisions("base", "heads"),
)
revisions.reverse()
@@ -33,6 +35,7 @@ def test_migrations_stairway(alembic_config: alembic.config.Config):
# We need -1 for downgrading first migration (its down_revision is None)
alembic.command.downgrade(
- alembic_config, revision.down_revision or "-1"
+ alembic_config,
+ cast(str, revision.down_revision) or "-1",
) # type: ignore
alembic.command.upgrade(alembic_config, revision.revision)
From 28d3f4e37be36e5c937620a7ce13b632e879913e Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Fri, 23 Feb 2024 13:49:44 +0400
Subject: [PATCH 04/39] `UnitOfWork`: Remove rollback method
---
src/amdb/application/common/unit_of_work.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/amdb/application/common/unit_of_work.py b/src/amdb/application/common/unit_of_work.py
index afc756d..c4c6cba 100644
--- a/src/amdb/application/common/unit_of_work.py
+++ b/src/amdb/application/common/unit_of_work.py
@@ -4,6 +4,3 @@
class UnitOfWork(Protocol):
def commit(self) -> None:
raise NotImplementedError
-
- def rollback(self) -> None:
- raise NotImplementedError
From 3d0ad49668176a9aa8bf316e4335d5a4edf049c9 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Fri, 23 Feb 2024 18:30:42 +0400
Subject: [PATCH 05/39] `CreateMovie` and `DeleteMovie` now don't require
permissions
---
.../command_handlers/create_movie.py | 19 ------
.../command_handlers/delete_movie.py | 22 +------
.../common/constants/exceptions.py | 2 -
.../common/gateways/permissions.py | 6 --
.../command_handlers/test_create_movie.py | 52 -----------------
.../command_handlers/test_delete_movie.py | 58 +------------------
6 files changed, 2 insertions(+), 157 deletions(-)
diff --git a/src/amdb/application/command_handlers/create_movie.py b/src/amdb/application/command_handlers/create_movie.py
index 76d503f..402f187 100644
--- a/src/amdb/application/command_handlers/create_movie.py
+++ b/src/amdb/application/command_handlers/create_movie.py
@@ -1,16 +1,10 @@
from uuid_extensions import uuid7
from amdb.domain.entities.movie import MovieId
-from amdb.domain.services.access_concern import AccessConcern
from amdb.domain.services.create_movie import CreateMovie
-from amdb.application.common.gateways.permissions import PermissionsGateway
from amdb.application.common.gateways.movie import MovieGateway
from amdb.application.common.unit_of_work import UnitOfWork
from amdb.application.common.identity_provider import IdentityProvider
-from amdb.application.common.constants.exceptions import (
- CREATE_MOVIE_ACCESS_DENIED,
-)
-from amdb.application.common.exception import ApplicationError
from amdb.application.commands.create_movie import CreateMovieCommand
@@ -18,30 +12,17 @@ class CreateMovieHandler:
def __init__(
self,
*,
- access_concern: AccessConcern,
create_movie: CreateMovie,
- permissions_gateway: PermissionsGateway,
movie_gateway: MovieGateway,
unit_of_work: UnitOfWork,
identity_provider: IdentityProvider,
) -> None:
- self._access_concern = access_concern
self._create_movie = create_movie
- self._permissions_gateway = permissions_gateway
self._movie_gateway = movie_gateway
self._unit_of_work = unit_of_work
self._identity_provider = identity_provider
def execute(self, command: CreateMovieCommand) -> MovieId:
- current_permissions = self._identity_provider.permissions()
- required_permissions = self._permissions_gateway.for_create_movie()
- access = self._access_concern.authorize(
- current_permissions=current_permissions,
- required_permissions=required_permissions,
- )
- if not access:
- raise ApplicationError(CREATE_MOVIE_ACCESS_DENIED)
-
movie = self._create_movie(
id=MovieId(uuid7()),
title=command.title,
diff --git a/src/amdb/application/command_handlers/delete_movie.py b/src/amdb/application/command_handlers/delete_movie.py
index e61e465..6d9502a 100644
--- a/src/amdb/application/command_handlers/delete_movie.py
+++ b/src/amdb/application/command_handlers/delete_movie.py
@@ -1,16 +1,9 @@
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.gateways.permissions import (
- PermissionsGateway,
-)
from amdb.application.common.gateways.movie import MovieGateway
from amdb.application.common.gateways.rating import RatingGateway
from amdb.application.common.gateways.review import ReviewGateway
from amdb.application.common.unit_of_work import UnitOfWork
from amdb.application.common.identity_provider import IdentityProvider
-from amdb.application.common.constants.exceptions import (
- DELETE_MOVIE_ACCESS_DENIED,
- MOVIE_DOES_NOT_EXIST,
-)
+from amdb.application.common.constants.exceptions import MOVIE_DOES_NOT_EXIST
from amdb.application.common.exception import ApplicationError
from amdb.application.commands.delete_movie import DeleteMovieCommand
@@ -19,16 +12,12 @@ class DeleteMovieHandler:
def __init__(
self,
*,
- access_concern: AccessConcern,
- permissions_gateway: PermissionsGateway,
movie_gateway: MovieGateway,
rating_gateway: RatingGateway,
review_gateway: ReviewGateway,
unit_of_work: UnitOfWork,
identity_provider: IdentityProvider,
) -> None:
- self._access_concern = access_concern
- self._permissions_gateway = permissions_gateway
self._movie_gateway = movie_gateway
self._rating_gateway = rating_gateway
self._review_gateway = review_gateway
@@ -36,15 +25,6 @@ def __init__(
self._identity_provider = identity_provider
def execute(self, command: DeleteMovieCommand) -> None:
- current_permissions = self._identity_provider.permissions()
- required_permissions = self._permissions_gateway.for_delete_movie()
- access = self._access_concern.authorize(
- current_permissions=current_permissions,
- required_permissions=required_permissions,
- )
- if not access:
- raise ApplicationError(DELETE_MOVIE_ACCESS_DENIED)
-
movie = self._movie_gateway.with_id(command.movie_id)
if not movie:
raise ApplicationError(MOVIE_DOES_NOT_EXIST)
diff --git a/src/amdb/application/common/constants/exceptions.py b/src/amdb/application/common/constants/exceptions.py
index 68aacdd..db05d96 100644
--- a/src/amdb/application/common/constants/exceptions.py
+++ b/src/amdb/application/common/constants/exceptions.py
@@ -1,6 +1,4 @@
LOGIN_ACCESS_DENIED = "Access to login in denied"
-CREATE_MOVIE_ACCESS_DENIED = "Access to movie creation is denied"
-DELETE_MOVIE_ACCESS_DENIED = "Access to movie deletion is denied"
RATE_MOVIE_ACCESS_DENIED = "Access to movie rating is denied"
UNRATE_MOVIE_ACCESS_DENIED = "Access to movie unrating is denied"
REVIEW_MOVIE_ACCESS_DENIED = "Access to movie reviewing is denied"
diff --git a/src/amdb/application/common/gateways/permissions.py b/src/amdb/application/common/gateways/permissions.py
index 501c912..8b34ac7 100644
--- a/src/amdb/application/common/gateways/permissions.py
+++ b/src/amdb/application/common/gateways/permissions.py
@@ -16,12 +16,6 @@ def for_new_user(self) -> int:
def for_login(self) -> int:
raise NotImplementedError
- def for_create_movie(self) -> int:
- raise NotImplementedError
-
- def for_delete_movie(self) -> int:
- raise NotImplementedError
-
def for_rate_movie(self) -> int:
raise NotImplementedError
diff --git a/tests/unit/application/command_handlers/test_create_movie.py b/tests/unit/application/command_handlers/test_create_movie.py
index 5cbbb86..719cccd 100644
--- a/tests/unit/application/command_handlers/test_create_movie.py
+++ b/tests/unit/application/command_handlers/test_create_movie.py
@@ -1,76 +1,24 @@
-from unittest.mock import Mock
from datetime import date
-import pytest
-
-from amdb.domain.services.access_concern import AccessConcern
from amdb.domain.services.create_movie import CreateMovie
-from amdb.application.common.gateways.permissions import PermissionsGateway
from amdb.application.common.gateways.movie import MovieGateway
from amdb.application.common.unit_of_work import UnitOfWork
-from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.commands.create_movie import CreateMovieCommand
from amdb.application.command_handlers.create_movie import CreateMovieHandler
-from amdb.application.common.constants.exceptions import (
- CREATE_MOVIE_ACCESS_DENIED,
-)
-from amdb.application.common.exception import ApplicationError
-
-
-@pytest.fixture
-def identity_provider_with_correct_permissions(
- permissions_gateway: PermissionsGateway,
-) -> IdentityProvider:
- identity_provider = Mock()
-
- correct_permissions = permissions_gateway.for_create_movie()
- identity_provider.permissions = Mock(return_value=correct_permissions)
-
- return identity_provider
def test_create_movie(
- permissions_gateway: PermissionsGateway,
movie_gateway: MovieGateway,
unit_of_work: UnitOfWork,
- identity_provider_with_correct_permissions: IdentityProvider,
):
create_movie_command = CreateMovieCommand(
title="Matrix",
release_date=date(1999, 3, 31),
)
create_movie_handler = CreateMovieHandler(
- access_concern=AccessConcern(),
create_movie=CreateMovie(),
- permissions_gateway=permissions_gateway,
movie_gateway=movie_gateway,
unit_of_work=unit_of_work,
- identity_provider=identity_provider_with_correct_permissions,
)
create_movie_handler.execute(create_movie_command)
-
-
-def test_create_movie_should_raise_error_when_access_is_denied(
- permissions_gateway: PermissionsGateway,
- movie_gateway: MovieGateway,
- unit_of_work: UnitOfWork,
- identity_provider_with_incorrect_permissions: IdentityProvider,
-):
- create_movie_command = CreateMovieCommand(
- title="Matrix",
- release_date=date(1999, 3, 31),
- )
- create_movie_handler = CreateMovieHandler(
- access_concern=AccessConcern(),
- create_movie=CreateMovie(),
- permissions_gateway=permissions_gateway,
- movie_gateway=movie_gateway,
- unit_of_work=unit_of_work,
- identity_provider=identity_provider_with_incorrect_permissions,
- )
-
- with pytest.raises(ApplicationError) as error:
- create_movie_handler.execute(create_movie_command)
-
- assert error.value.message == CREATE_MOVIE_ACCESS_DENIED
diff --git a/tests/unit/application/command_handlers/test_delete_movie.py b/tests/unit/application/command_handlers/test_delete_movie.py
index acad807..5fd6fdb 100644
--- a/tests/unit/application/command_handlers/test_delete_movie.py
+++ b/tests/unit/application/command_handlers/test_delete_movie.py
@@ -1,45 +1,24 @@
-from unittest.mock import Mock
from datetime import date
import pytest
from uuid_extensions import uuid7
from amdb.domain.entities.movie import MovieId, Movie
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.application.common.gateways.permissions import PermissionsGateway
from amdb.application.common.gateways.movie import MovieGateway
from amdb.application.common.gateways.rating import RatingGateway
from amdb.application.common.gateways.review import ReviewGateway
from amdb.application.common.unit_of_work import UnitOfWork
-from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.commands.delete_movie import DeleteMovieCommand
from amdb.application.command_handlers.delete_movie import DeleteMovieHandler
-from amdb.application.common.constants.exceptions import (
- DELETE_MOVIE_ACCESS_DENIED,
- MOVIE_DOES_NOT_EXIST,
-)
+from amdb.application.common.constants.exceptions import MOVIE_DOES_NOT_EXIST
from amdb.application.common.exception import ApplicationError
-@pytest.fixture
-def identity_provider_with_correct_permissions(
- permissions_gateway: PermissionsGateway,
-) -> IdentityProvider:
- identity_provider = Mock()
-
- correct_permissions = permissions_gateway.for_delete_movie()
- identity_provider.permissions = Mock(return_value=correct_permissions)
-
- return identity_provider
-
-
def test_delete_movie(
- permissions_gateway: PermissionsGateway,
movie_gateway: MovieGateway,
rating_gateway: RatingGateway,
review_gateway: ReviewGateway,
unit_of_work: UnitOfWork,
- identity_provider_with_correct_permissions: IdentityProvider,
):
movie = Movie(
id=MovieId(uuid7()),
@@ -56,64 +35,29 @@ def test_delete_movie(
movie_id=movie.id,
)
delete_movie_handler = DeleteMovieHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
movie_gateway=movie_gateway,
rating_gateway=rating_gateway,
review_gateway=review_gateway,
unit_of_work=unit_of_work,
- identity_provider=identity_provider_with_correct_permissions,
)
delete_movie_handler.execute(delete_movie_command)
-def test_delete_movie_should_raise_error_when_access_is_denied(
- permissions_gateway: PermissionsGateway,
- movie_gateway: MovieGateway,
- rating_gateway: RatingGateway,
- review_gateway: ReviewGateway,
- unit_of_work: UnitOfWork,
- identity_provider_with_incorrect_permissions: IdentityProvider,
-):
- delete_movie_command = DeleteMovieCommand(
- movie_id=MovieId(uuid7()),
- )
- delete_movie_handler = DeleteMovieHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
- movie_gateway=movie_gateway,
- rating_gateway=rating_gateway,
- review_gateway=review_gateway,
- unit_of_work=unit_of_work,
- identity_provider=identity_provider_with_incorrect_permissions,
- )
-
- with pytest.raises(ApplicationError) as error:
- delete_movie_handler.execute(delete_movie_command)
-
- assert error.value.message == DELETE_MOVIE_ACCESS_DENIED
-
-
def test_delete_movie_should_raise_error_when_movie_does_not_exist(
- permissions_gateway: PermissionsGateway,
movie_gateway: MovieGateway,
rating_gateway: RatingGateway,
review_gateway: ReviewGateway,
unit_of_work: UnitOfWork,
- identity_provider_with_correct_permissions: IdentityProvider,
):
delete_movie_command = DeleteMovieCommand(
movie_id=MovieId(uuid7()),
)
delete_movie_handler = DeleteMovieHandler(
- access_concern=AccessConcern(),
- permissions_gateway=permissions_gateway,
movie_gateway=movie_gateway,
rating_gateway=rating_gateway,
review_gateway=review_gateway,
unit_of_work=unit_of_work,
- identity_provider=identity_provider_with_correct_permissions,
)
with pytest.raises(ApplicationError) as error:
From d2fbd05287a21a4ddcbbf3c27518bc21e83a40d8 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Fri, 23 Feb 2024 19:54:04 +0400
Subject: [PATCH 06/39] Grammer, remove unused params
---
src/amdb/application/command_handlers/create_movie.py | 3 ---
src/amdb/application/command_handlers/delete_movie.py | 3 ---
src/amdb/application/common/constants/exceptions.py | 2 +-
3 files changed, 1 insertion(+), 7 deletions(-)
diff --git a/src/amdb/application/command_handlers/create_movie.py b/src/amdb/application/command_handlers/create_movie.py
index 402f187..af21d18 100644
--- a/src/amdb/application/command_handlers/create_movie.py
+++ b/src/amdb/application/command_handlers/create_movie.py
@@ -4,7 +4,6 @@
from amdb.domain.services.create_movie import CreateMovie
from amdb.application.common.gateways.movie import MovieGateway
from amdb.application.common.unit_of_work import UnitOfWork
-from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.commands.create_movie import CreateMovieCommand
@@ -15,12 +14,10 @@ def __init__(
create_movie: CreateMovie,
movie_gateway: MovieGateway,
unit_of_work: UnitOfWork,
- identity_provider: IdentityProvider,
) -> None:
self._create_movie = create_movie
self._movie_gateway = movie_gateway
self._unit_of_work = unit_of_work
- self._identity_provider = identity_provider
def execute(self, command: CreateMovieCommand) -> MovieId:
movie = self._create_movie(
diff --git a/src/amdb/application/command_handlers/delete_movie.py b/src/amdb/application/command_handlers/delete_movie.py
index 6d9502a..cbc9092 100644
--- a/src/amdb/application/command_handlers/delete_movie.py
+++ b/src/amdb/application/command_handlers/delete_movie.py
@@ -2,7 +2,6 @@
from amdb.application.common.gateways.rating import RatingGateway
from amdb.application.common.gateways.review import ReviewGateway
from amdb.application.common.unit_of_work import UnitOfWork
-from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.common.constants.exceptions import MOVIE_DOES_NOT_EXIST
from amdb.application.common.exception import ApplicationError
from amdb.application.commands.delete_movie import DeleteMovieCommand
@@ -16,13 +15,11 @@ def __init__(
rating_gateway: RatingGateway,
review_gateway: ReviewGateway,
unit_of_work: UnitOfWork,
- identity_provider: IdentityProvider,
) -> None:
self._movie_gateway = movie_gateway
self._rating_gateway = rating_gateway
self._review_gateway = review_gateway
self._unit_of_work = unit_of_work
- self._identity_provider = identity_provider
def execute(self, command: DeleteMovieCommand) -> None:
movie = self._movie_gateway.with_id(command.movie_id)
diff --git a/src/amdb/application/common/constants/exceptions.py b/src/amdb/application/common/constants/exceptions.py
index db05d96..dbd3147 100644
--- a/src/amdb/application/common/constants/exceptions.py
+++ b/src/amdb/application/common/constants/exceptions.py
@@ -3,7 +3,7 @@
UNRATE_MOVIE_ACCESS_DENIED = "Access to movie unrating is denied"
REVIEW_MOVIE_ACCESS_DENIED = "Access to movie reviewing is denied"
-USER_IS_NOT_OWNER = "User is not owner"
+USER_IS_NOT_OWNER = "User is not an owner"
USER_NAME_ALREADY_EXISTS = "User name already exists"
USER_DOES_NOT_EXIST = "User doesn't exist"
From 3aafde9512e6f84ac9a8126a4280a88b0a5a181f Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Sat, 24 Feb 2024 00:56:28 +0400
Subject: [PATCH 07/39] Refactor presentation and main, remove cli, add dishka
di
---
pyproject.toml | 7 +-
.../readers/{review.py => detailed_review.py} | 8 +-
.../{review.py => detailed_review.py} | 2 +-
.../{reviews.py => detailed_reviews.py} | 2 +-
.../{reviews.py => detailed_reviews.py} | 23 +-
.../auth/raw/identity_provider.py | 18 -
.../infrastructure/auth/session/config.py | 6 +-
.../raw => persistence/caching}/__init__.py | 0
.../persistence/caching/permissions_mapper.py | 65 ++++
.../persistence/redis/cache}/__init__.py | 0
.../redis/cache/permissions_mapper.py | 26 ++
.../persistence/redis/config.py | 4 +-
.../persistence/redis/mappers/permissions.py | 67 ----
.../persistence/sqlalchemy/config.py | 4 +-
.../sqlalchemy/mappers/permissions.py | 31 ++
.../{review.py => detailed_review.py} | 16 +-
.../sqlalchemy/models/permissions.py | 16 +
src/amdb/main/cli/__main__.py | 25 --
src/amdb/main/cli/app.py | 54 ---
src/amdb/main/ioc.py | 220 -----------
src/amdb/main/providers.py | 358 ++++++++++++++++++
src/amdb/main/web_api/__main__.py | 42 +-
src/amdb/main/web_api/app.py | 64 +++-
src/amdb/main/web_api/config.py | 4 +-
src/amdb/main/web_api/di.py | 50 ---
src/amdb/main/web_api/providers.py | 30 ++
src/amdb/presentation/cli/alembic.py | 17 -
src/amdb/presentation/cli/movie.py | 89 -----
src/amdb/presentation/cli/setup.py | 9 -
src/amdb/presentation/create_handler.py | 16 +
src/amdb/presentation/handler_factory.py | 81 ----
.../{cli => web_api/auth}/__init__.py | 0
src/amdb/presentation/web_api/auth/login.py | 48 +++
.../presentation/web_api/auth/register.py | 46 +++
src/amdb/presentation/web_api/auth/router.py | 22 ++
.../web_api/dependencies/depends_stub.py | 17 -
.../web_api/dependencies/identity_provider.py | 32 --
.../web_api/exception_handlers.py | 5 +-
.../{dependencies => movies}/__init__.py | 0
.../web_api/movies/get_detailed.py | 55 +++
.../web_api/movies/get_non_detailed.py | 57 +++
.../presentation/web_api/movies/router.py | 17 +
.../web_api/{routers => ratings}/__init__.py | 0
.../web_api/ratings/rate_movie.py | 49 +++
.../presentation/web_api/ratings/router.py | 20 +
.../web_api/ratings/unrate_movie.py | 51 +++
.../{routers/auth => reviews}/__init__.py | 0
.../web_api/reviews/get_detailed.py | 35 ++
.../web_api/reviews/review_movie.py | 49 +++
.../presentation/web_api/reviews/router.py | 17 +
src/amdb/presentation/web_api/router.py | 13 +
.../web_api/routers/auth/login.py | 46 ---
.../web_api/routers/auth/register.py | 44 ---
.../web_api/routers/auth/router.py | 24 --
.../web_api/routers/movies/__init__.py | 0
.../web_api/routers/movies/get_movies.py | 69 ----
.../web_api/routers/movies/router.py | 23 --
.../web_api/routers/ratings/__init__.py | 0
.../web_api/routers/ratings/rate_movie.py | 33 --
.../web_api/routers/ratings/router.py | 26 --
.../web_api/routers/ratings/unrate_movie.py | 32 --
.../web_api/routers/reviews/__init__.py | 0
.../web_api/routers/reviews/get_reviews.py | 28 --
.../web_api/routers/reviews/review_movie.py | 46 ---
.../web_api/routers/reviews/router.py | 25 --
.../presentation/web_api/routers/setup.py | 13 -
tests/unit/application/conftest.py | 30 +-
...eviews.py => test_get_detailed_reviews.py} | 40 +-
68 files changed, 1154 insertions(+), 1212 deletions(-)
rename src/amdb/application/common/readers/{review.py => detailed_review.py} (53%)
rename src/amdb/application/common/view_models/{review.py => detailed_review.py} (93%)
rename src/amdb/application/queries/{reviews.py => detailed_reviews.py} (84%)
rename src/amdb/application/query_handlers/{reviews.py => detailed_reviews.py} (51%)
delete mode 100644 src/amdb/infrastructure/auth/raw/identity_provider.py
rename src/amdb/infrastructure/{auth/raw => persistence/caching}/__init__.py (100%)
create mode 100644 src/amdb/infrastructure/persistence/caching/permissions_mapper.py
rename src/amdb/{main/cli => infrastructure/persistence/redis/cache}/__init__.py (100%)
create mode 100644 src/amdb/infrastructure/persistence/redis/cache/permissions_mapper.py
delete mode 100644 src/amdb/infrastructure/persistence/redis/mappers/permissions.py
create mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/mappers/permissions.py
rename src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/{review.py => detailed_review.py} (89%)
create mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/models/permissions.py
delete mode 100644 src/amdb/main/cli/__main__.py
delete mode 100644 src/amdb/main/cli/app.py
delete mode 100644 src/amdb/main/ioc.py
create mode 100644 src/amdb/main/providers.py
delete mode 100644 src/amdb/main/web_api/di.py
create mode 100644 src/amdb/main/web_api/providers.py
delete mode 100644 src/amdb/presentation/cli/alembic.py
delete mode 100644 src/amdb/presentation/cli/movie.py
delete mode 100644 src/amdb/presentation/cli/setup.py
create mode 100644 src/amdb/presentation/create_handler.py
delete mode 100644 src/amdb/presentation/handler_factory.py
rename src/amdb/presentation/{cli => web_api/auth}/__init__.py (100%)
create mode 100644 src/amdb/presentation/web_api/auth/login.py
create mode 100644 src/amdb/presentation/web_api/auth/register.py
create mode 100644 src/amdb/presentation/web_api/auth/router.py
delete mode 100644 src/amdb/presentation/web_api/dependencies/depends_stub.py
delete mode 100644 src/amdb/presentation/web_api/dependencies/identity_provider.py
rename src/amdb/presentation/web_api/{dependencies => movies}/__init__.py (100%)
create mode 100644 src/amdb/presentation/web_api/movies/get_detailed.py
create mode 100644 src/amdb/presentation/web_api/movies/get_non_detailed.py
create mode 100644 src/amdb/presentation/web_api/movies/router.py
rename src/amdb/presentation/web_api/{routers => ratings}/__init__.py (100%)
create mode 100644 src/amdb/presentation/web_api/ratings/rate_movie.py
create mode 100644 src/amdb/presentation/web_api/ratings/router.py
create mode 100644 src/amdb/presentation/web_api/ratings/unrate_movie.py
rename src/amdb/presentation/web_api/{routers/auth => reviews}/__init__.py (100%)
create mode 100644 src/amdb/presentation/web_api/reviews/get_detailed.py
create mode 100644 src/amdb/presentation/web_api/reviews/review_movie.py
create mode 100644 src/amdb/presentation/web_api/reviews/router.py
create mode 100644 src/amdb/presentation/web_api/router.py
delete mode 100644 src/amdb/presentation/web_api/routers/auth/login.py
delete mode 100644 src/amdb/presentation/web_api/routers/auth/register.py
delete mode 100644 src/amdb/presentation/web_api/routers/auth/router.py
delete mode 100644 src/amdb/presentation/web_api/routers/movies/__init__.py
delete mode 100644 src/amdb/presentation/web_api/routers/movies/get_movies.py
delete mode 100644 src/amdb/presentation/web_api/routers/movies/router.py
delete mode 100644 src/amdb/presentation/web_api/routers/ratings/__init__.py
delete mode 100644 src/amdb/presentation/web_api/routers/ratings/rate_movie.py
delete mode 100644 src/amdb/presentation/web_api/routers/ratings/router.py
delete mode 100644 src/amdb/presentation/web_api/routers/ratings/unrate_movie.py
delete mode 100644 src/amdb/presentation/web_api/routers/reviews/__init__.py
delete mode 100644 src/amdb/presentation/web_api/routers/reviews/get_reviews.py
delete mode 100644 src/amdb/presentation/web_api/routers/reviews/review_movie.py
delete mode 100644 src/amdb/presentation/web_api/routers/reviews/router.py
delete mode 100644 src/amdb/presentation/web_api/routers/setup.py
rename tests/unit/application/query_handlers/{test_get_reviews.py => test_get_detailed_reviews.py} (70%)
diff --git a/pyproject.toml b/pyproject.toml
index 8167880..1adc221 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -24,8 +24,9 @@ maintainers = [
{ name = "madnoberson", email = "baseddepartmentzx77@gmail.com" },
]
dependencies = [
- "uuid7",
+ "uuid7==0.1.*",
"toml==0.10.*",
+ "dishka==0.4.*",
"sqlalchemy==2.0.*",
"psycopg2-binary==2.9.*",
"alembic==1.13.*",
@@ -37,9 +38,6 @@ web_api = [
"fastapi==0.103.*",
"uvicorn==0.22.*",
]
-cli = [
- "typer[all]==0.9.*",
-]
dev = [
"mypy==1.8.*",
"ruff==0.1.*",
@@ -53,5 +51,4 @@ coverage = [
]
[project.scripts]
-amdb-cli = "amdb.main.cli.__main__:main"
amdb-web_api = "amdb.main.web_api.__main__:main"
diff --git a/src/amdb/application/common/readers/review.py b/src/amdb/application/common/readers/detailed_review.py
similarity index 53%
rename from src/amdb/application/common/readers/review.py
rename to src/amdb/application/common/readers/detailed_review.py
index f04f6f1..2cdf4a4 100644
--- a/src/amdb/application/common/readers/review.py
+++ b/src/amdb/application/common/readers/detailed_review.py
@@ -1,14 +1,16 @@
from typing import Protocol
from amdb.domain.entities.movie import MovieId
-from amdb.application.common.view_models.review import ReviewViewModel
+from amdb.application.common.view_models.detailed_review import (
+ DetailedReviewViewModel,
+)
-class ReviewViewModelReader(Protocol):
+class DetailedReviewViewModelReader(Protocol):
def list(
self,
movie_id: MovieId,
limit: int,
offset: int,
- ) -> list[ReviewViewModel]:
+ ) -> list[DetailedReviewViewModel]:
raise NotImplementedError
diff --git a/src/amdb/application/common/view_models/review.py b/src/amdb/application/common/view_models/detailed_review.py
similarity index 93%
rename from src/amdb/application/common/view_models/review.py
rename to src/amdb/application/common/view_models/detailed_review.py
index 8091a8b..da2f376 100644
--- a/src/amdb/application/common/view_models/review.py
+++ b/src/amdb/application/common/view_models/detailed_review.py
@@ -22,7 +22,7 @@ class UserReview(TypedDict):
created_at: datetime
-class ReviewViewModel(TypedDict):
+class DetailedReviewViewModel(TypedDict):
user_id: UserId
user_review: UserReview
user_rating: Optional[UserRating]
diff --git a/src/amdb/application/queries/reviews.py b/src/amdb/application/queries/detailed_reviews.py
similarity index 84%
rename from src/amdb/application/queries/reviews.py
rename to src/amdb/application/queries/detailed_reviews.py
index a3577b0..c8c7525 100644
--- a/src/amdb/application/queries/reviews.py
+++ b/src/amdb/application/queries/detailed_reviews.py
@@ -4,7 +4,7 @@
@dataclass(frozen=True, slots=True)
-class GetReviewsQuery:
+class GetDetailedReviewsQuery:
movie_id: MovieId
limit: int
offset: int
diff --git a/src/amdb/application/query_handlers/reviews.py b/src/amdb/application/query_handlers/detailed_reviews.py
similarity index 51%
rename from src/amdb/application/query_handlers/reviews.py
rename to src/amdb/application/query_handlers/detailed_reviews.py
index 736add7..c60fa6a 100644
--- a/src/amdb/application/query_handlers/reviews.py
+++ b/src/amdb/application/query_handlers/detailed_reviews.py
@@ -1,27 +1,34 @@
from amdb.application.common.gateways.movie import MovieGateway
-from amdb.application.common.readers.review import ReviewViewModelReader
-from amdb.application.common.view_models.review import ReviewViewModel
+from amdb.application.common.readers.detailed_review import (
+ DetailedReviewViewModelReader,
+)
+from amdb.application.common.view_models.detailed_review import (
+ DetailedReviewViewModel,
+)
from amdb.application.common.constants.exceptions import MOVIE_DOES_NOT_EXIST
from amdb.application.common.exception import ApplicationError
-from amdb.application.queries.reviews import GetReviewsQuery
+from amdb.application.queries.detailed_reviews import GetDetailedReviewsQuery
-class GetReviewsHandler:
+class GetDetailedReviewsHandler:
def __init__(
self,
*,
movie_gateway: MovieGateway,
- review_view_model_reader: ReviewViewModelReader,
+ detailed_review_reader: DetailedReviewViewModelReader,
) -> None:
self._movie_gateway = movie_gateway
- self._review_view_model_reader = review_view_model_reader
+ self._detailed_review_reader = detailed_review_reader
- def execute(self, query: GetReviewsQuery) -> list[ReviewViewModel]:
+ def execute(
+ self,
+ query: GetDetailedReviewsQuery,
+ ) -> list[DetailedReviewViewModel]:
movie = self._movie_gateway.with_id(query.movie_id)
if not movie:
raise ApplicationError(MOVIE_DOES_NOT_EXIST)
- review_view_models = self._review_view_model_reader.list(
+ review_view_models = self._detailed_review_reader.list(
movie_id=query.movie_id,
limit=query.limit,
offset=query.offset,
diff --git a/src/amdb/infrastructure/auth/raw/identity_provider.py b/src/amdb/infrastructure/auth/raw/identity_provider.py
deleted file mode 100644
index ec0556d..0000000
--- a/src/amdb/infrastructure/auth/raw/identity_provider.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from typing import Optional
-
-from amdb.domain.entities.user import UserId
-
-
-class RawIdentityProvider:
- def __init__(self, user_id: UserId, permissions: int) -> None:
- self._user_id = user_id
- self._permissions = permissions
-
- def user_id(self) -> UserId:
- return self._user_id
-
- def user_id_or_none(self) -> Optional[UserId]:
- return self._user_id
-
- def permissions(self) -> int:
- return self._permissions
diff --git a/src/amdb/infrastructure/auth/session/config.py b/src/amdb/infrastructure/auth/session/config.py
index 8100d97..9093717 100644
--- a/src/amdb/infrastructure/auth/session/config.py
+++ b/src/amdb/infrastructure/auth/session/config.py
@@ -1,7 +1,5 @@
from dataclasses import dataclass
from datetime import timedelta
-from typing import Union
-from os import PathLike
import toml
@@ -11,9 +9,9 @@ class SessionConfig:
lifetime: timedelta
@classmethod
- def from_toml(cls, path: Union[PathLike, str]) -> "SessionConfig":
+ def from_toml(cls, path: str) -> "SessionConfig":
toml_as_dict = toml.load(path)
session_section_as_dict = toml_as_dict["auth-session"]
return SessionConfig(
- lifetime=session_section_as_dict["lifetime"],
+ lifetime=timedelta(minutes=session_section_as_dict["lifetime"]),
)
diff --git a/src/amdb/infrastructure/auth/raw/__init__.py b/src/amdb/infrastructure/persistence/caching/__init__.py
similarity index 100%
rename from src/amdb/infrastructure/auth/raw/__init__.py
rename to src/amdb/infrastructure/persistence/caching/__init__.py
diff --git a/src/amdb/infrastructure/persistence/caching/permissions_mapper.py b/src/amdb/infrastructure/persistence/caching/permissions_mapper.py
new file mode 100644
index 0000000..90f587f
--- /dev/null
+++ b/src/amdb/infrastructure/persistence/caching/permissions_mapper.py
@@ -0,0 +1,65 @@
+from typing import Optional
+
+from amdb.domain.entities.user import UserId
+from amdb.infrastructure.persistence.sqlalchemy.mappers.permissions import (
+ PermissionsMapper,
+)
+from amdb.infrastructure.persistence.redis.cache.permissions_mapper import (
+ PermissionsMapperCacheProvider,
+)
+
+
+class CachingPermissionsMapper:
+ def __init__(
+ self,
+ permissions_mapper: PermissionsMapper,
+ cache_provider: PermissionsMapperCacheProvider,
+ ) -> None:
+ self._permissions_mapper = permissions_mapper
+ self._cache_provider = cache_provider
+
+ def with_user_id(self, user_id: UserId) -> Optional[int]:
+ permissions_from_cache = self._cache_provider.with_user_id(user_id)
+ if permissions_from_cache:
+ return permissions_from_cache
+
+ permissions_from_database = self._permissions_mapper.with_user_id(
+ user_id,
+ )
+ if permissions_from_database:
+ self._cache_provider.set(
+ user_id=user_id,
+ permissions=permissions_from_database,
+ )
+
+ return permissions_from_database
+
+ def set(self, user_id: UserId, permissions: int) -> None:
+ self._permissions_mapper.set(
+ user_id=user_id,
+ permissions=permissions,
+ )
+ self._cache_provider.set(
+ user_id=user_id,
+ permissions=permissions,
+ )
+
+ def for_new_user(self) -> int:
+ return (
+ self.for_login()
+ + self.for_rate_movie()
+ + self.for_unrate_movie()
+ + self.for_review_movie()
+ )
+
+ def for_login(self) -> int:
+ return 2
+
+ def for_rate_movie(self) -> int:
+ return 4
+
+ def for_unrate_movie(self) -> int:
+ return 8
+
+ def for_review_movie(self) -> int:
+ return 16
diff --git a/src/amdb/main/cli/__init__.py b/src/amdb/infrastructure/persistence/redis/cache/__init__.py
similarity index 100%
rename from src/amdb/main/cli/__init__.py
rename to src/amdb/infrastructure/persistence/redis/cache/__init__.py
diff --git a/src/amdb/infrastructure/persistence/redis/cache/permissions_mapper.py b/src/amdb/infrastructure/persistence/redis/cache/permissions_mapper.py
new file mode 100644
index 0000000..fe3760b
--- /dev/null
+++ b/src/amdb/infrastructure/persistence/redis/cache/permissions_mapper.py
@@ -0,0 +1,26 @@
+from datetime import timedelta
+from typing import Optional, cast
+
+from redis import Redis
+
+from amdb.domain.entities.user import UserId
+
+
+class PermissionsMapperCacheProvider:
+ _CACHE_TIME = timedelta(hours=24)
+
+ def __init__(self, redis: Redis) -> None:
+ self._redis = redis
+
+ def with_user_id(self, user_id: UserId) -> Optional[int]:
+ permissions = self._redis.get(f"permissions:user_id:{user_id.hex}")
+ if permissions:
+ return int(cast(str, permissions))
+ return None
+
+ def set(self, user_id: UserId, permissions: int) -> None:
+ self._redis.set(
+ name=f"permissions:user_id:{user_id.hex}",
+ value=permissions,
+ ex=self._CACHE_TIME,
+ )
diff --git a/src/amdb/infrastructure/persistence/redis/config.py b/src/amdb/infrastructure/persistence/redis/config.py
index 3a4abc8..06cdffd 100644
--- a/src/amdb/infrastructure/persistence/redis/config.py
+++ b/src/amdb/infrastructure/persistence/redis/config.py
@@ -1,6 +1,4 @@
from dataclasses import dataclass
-from typing import Union
-from os import PathLike
import toml
@@ -10,7 +8,7 @@ class RedisConfig:
url: str
@classmethod
- def from_toml(cls, path: Union[PathLike, str]) -> "RedisConfig":
+ def from_toml(cls, path: str) -> "RedisConfig":
toml_as_dict = toml.load(path)
redis_section_as_dict = toml_as_dict["redis"]
return RedisConfig(url=redis_section_as_dict["url"])
diff --git a/src/amdb/infrastructure/persistence/redis/mappers/permissions.py b/src/amdb/infrastructure/persistence/redis/mappers/permissions.py
deleted file mode 100644
index e5554bd..0000000
--- a/src/amdb/infrastructure/persistence/redis/mappers/permissions.py
+++ /dev/null
@@ -1,67 +0,0 @@
-from typing import Optional, cast
-
-from redis.client import Redis
-
-from amdb.domain.entities.user import UserId
-
-
-class PermissionsMapper:
- def __init__(self, redis: Redis) -> None:
- self._redis = redis
-
- def with_user_id(self, user_id: UserId) -> Optional[int]:
- permissions = self._redis.get(f"permissions:user_id:{user_id.hex}")
- if permissions:
- return int(cast(str, permissions))
- return None
-
- def set(self, user_id: UserId, permissions: int) -> None:
- self._redis.set(
- name=f"permissions:user_id:{user_id.hex}",
- value=permissions,
- )
-
- def for_login(self) -> int:
- return 2
-
- def for_new_user(self) -> int:
- return 6
-
- def for_get_movies(self) -> int:
- return 4
-
- def for_get_movie(self) -> int:
- return 4
-
- def for_create_movie(self) -> int:
- return 8
-
- def for_delete_movie(self) -> int:
- return 8
-
- def for_get_movie_ratings(self) -> int:
- return 4
-
- def for_get_my_ratings(self) -> int:
- return 4
-
- def for_get_rating(self) -> int:
- return 4
-
- def for_rate_movie(self) -> int:
- return 4
-
- def for_unrate_movie(self) -> int:
- return 4
-
- def for_get_reviews(self) -> int:
- return 4
-
- def for_get_my_reviews(self) -> int:
- return 4
-
- def for_get_review(self) -> int:
- return 4
-
- def for_review_movie(self) -> int:
- return 4
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/config.py b/src/amdb/infrastructure/persistence/sqlalchemy/config.py
index 5a44813..96951e9 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/config.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/config.py
@@ -1,6 +1,4 @@
from dataclasses import dataclass
-from typing import Union
-from os import PathLike
import toml
@@ -10,7 +8,7 @@ class PostgresConfig:
url: str
@classmethod
- def from_toml(cls, path: Union[PathLike, str]) -> "PostgresConfig":
+ def from_toml(cls, path: str) -> "PostgresConfig":
toml_as_dict = toml.load(path)
postgres_section_as_dict = toml_as_dict["postgres"]
return PostgresConfig(url=postgres_section_as_dict["url"])
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/permissions.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/permissions.py
new file mode 100644
index 0000000..b3392d0
--- /dev/null
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/permissions.py
@@ -0,0 +1,31 @@
+from typing import Optional
+
+from sqlalchemy import Connection, Row, select, insert
+
+from amdb.domain.entities.user import UserId
+from amdb.infrastructure.persistence.sqlalchemy.models.permissions import (
+ PermissionsModel,
+)
+
+
+class PermissionsMapper:
+ def __init__(self, connection: Connection) -> None:
+ self._connection = connection
+
+ def with_user_id(self, user_id: UserId) -> Optional[int]:
+ statement = select(PermissionsModel).where(
+ PermissionsModel.user_id == user_id,
+ )
+ row: Optional[Row[tuple[PermissionsModel]]] = self._connection.execute(
+ statement,
+ ).one_or_none()
+ if row is not None:
+ return row.value
+ return None
+
+ def set(self, user_id: UserId, permissions: int) -> None:
+ statement = insert(PermissionsModel).values(
+ user_id=user_id,
+ value=permissions,
+ )
+ self._connection.execute(statement)
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/review.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/detailed_review.py
similarity index 89%
rename from src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/review.py
rename to src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/detailed_review.py
index a3443a1..3145c32 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/review.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/detailed_review.py
@@ -1,4 +1,4 @@
-__all__ = ("ReviewViewModelMapper",)
+__all__ = ("DetailedReviewViewModelMapper",)
from typing import Optional, TypedDict
from datetime import datetime
@@ -10,10 +10,10 @@
from amdb.domain.entities.movie import MovieId
from amdb.domain.entities.rating import RatingId
from amdb.domain.entities.review import ReviewId, ReviewType
-from amdb.application.common.view_models.review import (
+from amdb.application.common.view_models.detailed_review import (
UserRating,
UserReview,
- ReviewViewModel,
+ DetailedReviewViewModel,
)
@@ -33,7 +33,7 @@ def from_row(cls, row: Row) -> "RowAsDict":
return RowAsDict(row._mapping) # noqa: SLF001
-class ReviewViewModelMapper:
+class DetailedReviewViewModelMapper:
def __init__(self, connection: Connection) -> None:
self._connection = connection
@@ -42,7 +42,7 @@ def list(
movie_id: MovieId,
limit: int,
offset: int,
- ) -> list[ReviewViewModel]:
+ ) -> list[DetailedReviewViewModel]:
statement = text(
"""
SELECT
@@ -83,7 +83,7 @@ def list(
def _to_view_model(
self,
row_as_dict: RowAsDict,
- ) -> ReviewViewModel:
+ ) -> DetailedReviewViewModel:
user_review = UserReview(
id=ReviewId(row_as_dict["user_review_id"]),
title=row_as_dict["user_review_title"],
@@ -101,9 +101,9 @@ def _to_view_model(
else:
user_rating = None
- review_view_model = ReviewViewModel(
+ detailed_review_view_model = DetailedReviewViewModel(
user_id=UserId(row_as_dict["user_id"]),
user_review=user_review,
user_rating=user_rating,
)
- return review_view_model
+ return detailed_review_view_model
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/models/permissions.py b/src/amdb/infrastructure/persistence/sqlalchemy/models/permissions.py
new file mode 100644
index 0000000..f0d6b4a
--- /dev/null
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/models/permissions.py
@@ -0,0 +1,16 @@
+from uuid import UUID
+
+from sqlalchemy import ForeignKey
+from sqlalchemy.orm import Mapped, mapped_column
+
+from .base import Model
+
+
+class PermissionsModel(Model):
+ __tablename__ = "permissions"
+
+ user_id: Mapped[UUID] = mapped_column(
+ ForeignKey("users.id", ondelete="CASCADE"),
+ primary_key=True,
+ )
+ value: Mapped[int]
diff --git a/src/amdb/main/cli/__main__.py b/src/amdb/main/cli/__main__.py
deleted file mode 100644
index e9d6f6f..0000000
--- a/src/amdb/main/cli/__main__.py
+++ /dev/null
@@ -1,25 +0,0 @@
-import os
-
-from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
-from amdb.infrastructure.persistence.redis.config import RedisConfig
-from .app import create_app
-
-
-def main() -> None:
- path_to_config = os.getenv("CONFIG_PATH")
- if not path_to_config:
- message = "Path to config env var is not set"
- raise ValueError(message)
-
- postgres_config = PostgresConfig.from_toml(path_to_config)
- redis_config = RedisConfig.from_toml(path_to_config)
-
- app = create_app(
- postgres_config=postgres_config,
- redis_config=redis_config,
- )
-
- app()
-
-
-main()
diff --git a/src/amdb/main/cli/app.py b/src/amdb/main/cli/app.py
deleted file mode 100644
index abdc0b8..0000000
--- a/src/amdb/main/cli/app.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from typing import cast
-from uuid import UUID
-
-import typer
-from sqlalchemy import create_engine
-from redis import Redis
-
-from amdb.domain.entities.user import UserId
-from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
-from amdb.infrastructure.persistence.redis.config import RedisConfig
-from amdb.infrastructure.persistence.redis.mappers.permissions import (
- PermissionsMapper,
-)
-from amdb.infrastructure.password_manager.hash_computer import HashComputer
-from amdb.infrastructure.auth.raw.identity_provider import RawIdentityProvider
-from amdb.presentation.cli.setup import setup_typer_command_handlers
-from amdb.main.ioc import IoC
-
-
-IDENTITY_PROVIDER_USER_ID = UserId(
- UUID("00000000-0000-0000-0000-000000000000"),
-)
-IDENTITY_PROVIDER_PERMISSIONS = 12
-
-
-def create_app(
- postgres_config: PostgresConfig,
- redis_config: RedisConfig,
-) -> typer.Typer:
- sqlalchemy_engine = create_engine(postgres_config.url)
- redis = Redis.from_url(redis_config.url, decode_responses=True)
- permissions_mapper = PermissionsMapper(cast(Redis, redis))
-
- ioc = IoC(
- sqlalchemy_engine=sqlalchemy_engine,
- permissions_mapper=permissions_mapper,
- hash_computer=HashComputer(),
- )
- raw_identity_provider = RawIdentityProvider(
- user_id=IDENTITY_PROVIDER_USER_ID,
- permissions=IDENTITY_PROVIDER_PERMISSIONS,
- )
- dependencies = {
- "ioc": ioc,
- "identity_provider": raw_identity_provider,
- }
-
- app = typer.Typer(
- rich_markup_mode="rich",
- context_settings={"obj": dependencies},
- )
- setup_typer_command_handlers(app)
-
- return app
diff --git a/src/amdb/main/ioc.py b/src/amdb/main/ioc.py
deleted file mode 100644
index 53b769e..0000000
--- a/src/amdb/main/ioc.py
+++ /dev/null
@@ -1,220 +0,0 @@
-from contextlib import contextmanager
-from typing import Iterator
-
-from sqlalchemy import Engine
-
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.domain.services.create_user import CreateUser
-from amdb.domain.services.create_movie import CreateMovie
-from amdb.domain.services.rate_movie import RateMovie
-from amdb.domain.services.unrate_movie import UnrateMovie
-from amdb.domain.services.review_movie import ReviewMovie
-from amdb.application.common.identity_provider import IdentityProvider
-from amdb.application.command_handlers.register_user import RegisterUserHandler
-from amdb.application.command_handlers.create_movie import CreateMovieHandler
-from amdb.application.command_handlers.delete_movie import DeleteMovieHandler
-from amdb.application.command_handlers.rate_movie import RateMovieHandler
-from amdb.application.command_handlers.unrate_movie import UnrateMovieHandler
-from amdb.application.command_handlers.review_movie import ReviewMovieHandler
-from amdb.application.query_handlers.login import LoginHandler
-from amdb.application.query_handlers.detailed_movie import (
- GetDetailedMovieHandler,
-)
-from amdb.application.query_handlers.non_detailed_movies import (
- GetNonDetailedMoviesHandler,
-)
-from amdb.application.query_handlers.reviews import GetReviewsHandler
-from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.user import (
- UserMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.movie import (
- MovieMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.rating import (
- RatingMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.review import (
- ReviewMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.non_detailed_movie import (
- NonDetailedMovieViewModelMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.detailed_movie import (
- DetailedMovieViewModelMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.review import (
- ReviewViewModelMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.password_hash import (
- PasswordHashMapper,
-)
-from amdb.infrastructure.persistence.redis.mappers.permissions import (
- PermissionsMapper,
-)
-from amdb.infrastructure.password_manager.hash_computer import HashComputer
-from amdb.infrastructure.password_manager.password_manager import (
- HashingPasswordManager,
-)
-from amdb.presentation.handler_factory import HandlerFactory
-
-
-class IoC(HandlerFactory):
- def __init__(
- self,
- sqlalchemy_engine: Engine,
- permissions_mapper: PermissionsMapper,
- hash_computer: HashComputer,
- ) -> None:
- self._sqlalchemy_engine = sqlalchemy_engine
- self._permissions_mapper = permissions_mapper
- self._hash_computer = hash_computer
-
- @contextmanager
- def register_user(self) -> Iterator[RegisterUserHandler]:
- with self._sqlalchemy_engine.connect() as sqlalchemy_connection:
- password_manager = HashingPasswordManager(
- hash_computer=self._hash_computer,
- password_hash_gateway=PasswordHashMapper(
- sqlalchemy_connection,
- ),
- )
- yield RegisterUserHandler(
- create_user=CreateUser(),
- user_gateway=UserMapper(sqlalchemy_connection),
- permissions_gateway=self._permissions_mapper,
- unit_of_work=sqlalchemy_connection,
- password_manager=password_manager,
- )
-
- @contextmanager
- def login(self) -> Iterator[LoginHandler]:
- with self._sqlalchemy_engine.connect() as sqlalchemy_connection:
- password_manager = HashingPasswordManager(
- hash_computer=self._hash_computer,
- password_hash_gateway=PasswordHashMapper(
- sqlalchemy_connection,
- ),
- )
- yield LoginHandler(
- access_concern=AccessConcern(),
- user_gateway=UserMapper(sqlalchemy_connection),
- permissions_gateway=self._permissions_mapper,
- password_manager=password_manager,
- )
-
- @contextmanager
- def get_non_detailed_movies(
- self,
- identity_provider: IdentityProvider,
- ) -> Iterator[GetNonDetailedMoviesHandler]:
- with self._sqlalchemy_engine.connect() as sqlalchemy_connection:
- yield GetNonDetailedMoviesHandler(
- non_detailed_movie_reader=NonDetailedMovieViewModelMapper(
- sqlalchemy_connection,
- ),
- identity_provider=identity_provider,
- )
-
- @contextmanager
- def get_detailed_movie(
- self,
- identity_provider: IdentityProvider,
- ) -> Iterator[GetDetailedMovieHandler]:
- with self._sqlalchemy_engine.connect() as sqlalchemy_connection:
- yield GetDetailedMovieHandler(
- detailed_movie_reader=DetailedMovieViewModelMapper(
- sqlalchemy_connection,
- ),
- identity_provider=identity_provider,
- )
-
- @contextmanager
- def create_movie(
- self,
- identity_provider: IdentityProvider,
- ) -> Iterator[CreateMovieHandler]:
- with self._sqlalchemy_engine.connect() as sqlalchemy_connection:
- yield CreateMovieHandler(
- access_concern=AccessConcern(),
- create_movie=CreateMovie(),
- permissions_gateway=self._permissions_mapper,
- movie_gateway=MovieMapper(sqlalchemy_connection),
- unit_of_work=sqlalchemy_connection,
- identity_provider=identity_provider,
- )
-
- @contextmanager
- def delete_movie(
- self,
- identity_provider: IdentityProvider,
- ) -> Iterator[DeleteMovieHandler]:
- with self._sqlalchemy_engine.connect() as sqlalchemy_connection:
- yield DeleteMovieHandler(
- access_concern=AccessConcern(),
- permissions_gateway=self._permissions_mapper,
- movie_gateway=MovieMapper(sqlalchemy_connection),
- rating_gateway=RatingMapper(sqlalchemy_connection),
- review_gateway=ReviewMapper(sqlalchemy_connection),
- unit_of_work=sqlalchemy_connection,
- identity_provider=identity_provider,
- )
-
- @contextmanager
- def rate_movie(
- self,
- identity_provider: IdentityProvider,
- ) -> Iterator[RateMovieHandler]:
- with self._sqlalchemy_engine.connect() as sqlalchemy_connection:
- yield RateMovieHandler(
- access_concern=AccessConcern(),
- rate_movie=RateMovie(),
- permissions_gateway=self._permissions_mapper,
- user_gateway=UserMapper(sqlalchemy_connection),
- movie_gateway=MovieMapper(sqlalchemy_connection),
- rating_gateway=RatingMapper(sqlalchemy_connection),
- unit_of_work=sqlalchemy_connection,
- identity_provider=identity_provider,
- )
-
- @contextmanager
- def unrate_movie(
- self,
- identity_provider: IdentityProvider,
- ) -> Iterator[UnrateMovieHandler]:
- with self._sqlalchemy_engine.connect() as sqlalchemy_connection:
- yield UnrateMovieHandler(
- access_concern=AccessConcern(),
- unrate_movie=UnrateMovie(),
- permissions_gateway=self._permissions_mapper,
- movie_gateway=MovieMapper(sqlalchemy_connection),
- rating_gateway=RatingMapper(sqlalchemy_connection),
- unit_of_work=sqlalchemy_connection,
- identity_provider=identity_provider,
- )
-
- @contextmanager
- def get_reviews(self) -> Iterator[GetReviewsHandler]:
- with self._sqlalchemy_engine.connect() as sqlalchemy_connection:
- yield GetReviewsHandler(
- movie_gateway=MovieMapper(sqlalchemy_connection),
- review_view_model_reader=ReviewViewModelMapper(
- sqlalchemy_connection,
- ),
- )
-
- @contextmanager
- def review_movie(
- self,
- identity_provider: IdentityProvider,
- ) -> Iterator[ReviewMovieHandler]:
- with self._sqlalchemy_engine.connect() as sqlalchemy_connection:
- yield ReviewMovieHandler(
- access_concern=AccessConcern(),
- review_movie=ReviewMovie(),
- permissions_gateway=self._permissions_mapper,
- user_gateway=UserMapper(sqlalchemy_connection),
- movie_gateway=MovieMapper(sqlalchemy_connection),
- review_gateway=ReviewMapper(sqlalchemy_connection),
- unit_of_work=sqlalchemy_connection,
- identity_provider=identity_provider,
- )
diff --git a/src/amdb/main/providers.py b/src/amdb/main/providers.py
new file mode 100644
index 0000000..1f082c2
--- /dev/null
+++ b/src/amdb/main/providers.py
@@ -0,0 +1,358 @@
+from typing import Iterable, cast
+
+from dishka import Provider, Scope, provide, alias
+from sqlalchemy import Connection, Engine, create_engine
+from redis import Redis
+
+from amdb.domain.services.access_concern import AccessConcern
+from amdb.domain.services.create_user import CreateUser
+from amdb.domain.services.create_movie import CreateMovie
+from amdb.domain.services.rate_movie import RateMovie
+from amdb.domain.services.unrate_movie import UnrateMovie
+from amdb.domain.services.review_movie import ReviewMovie
+from amdb.application.common.gateways.user import UserGateway
+from amdb.application.common.gateways.movie import MovieGateway
+from amdb.application.common.gateways.rating import RatingGateway
+from amdb.application.common.gateways.review import ReviewGateway
+from amdb.application.common.gateways.permissions import PermissionsGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.readers.detailed_movie import (
+ DetailedMovieViewModelReader,
+)
+from amdb.application.common.readers.non_detailed_movie import (
+ NonDetailedMovieViewModelReader,
+)
+from amdb.application.common.readers.detailed_review import (
+ DetailedReviewViewModelReader,
+)
+from amdb.application.common.password_manager import PasswordManager
+from amdb.application.common.identity_provider import IdentityProvider
+from amdb.application.command_handlers.register_user import RegisterUserHandler
+from amdb.application.command_handlers.create_movie import CreateMovieHandler
+from amdb.application.command_handlers.delete_movie import DeleteMovieHandler
+from amdb.application.command_handlers.rate_movie import RateMovieHandler
+from amdb.application.command_handlers.unrate_movie import UnrateMovieHandler
+from amdb.application.command_handlers.review_movie import ReviewMovieHandler
+from amdb.application.query_handlers.login import LoginHandler
+from amdb.application.query_handlers.detailed_movie import (
+ GetDetailedMovieHandler,
+)
+from amdb.application.query_handlers.non_detailed_movies import (
+ GetNonDetailedMoviesHandler,
+)
+from amdb.application.query_handlers.detailed_reviews import (
+ GetDetailedReviewsHandler,
+)
+from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
+from amdb.infrastructure.persistence.redis.config import RedisConfig
+from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.user import (
+ UserMapper,
+)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.movie import (
+ MovieMapper,
+)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.rating import (
+ RatingMapper,
+)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.review import (
+ ReviewMapper,
+)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.permissions import (
+ PermissionsMapper,
+)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.password_hash import (
+ PasswordHashMapper,
+)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.detailed_movie import (
+ DetailedMovieViewModelMapper,
+)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.non_detailed_movie import (
+ NonDetailedMovieViewModelMapper,
+)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.detailed_review import (
+ DetailedReviewViewModelMapper,
+)
+from amdb.infrastructure.persistence.redis.cache.permissions_mapper import (
+ PermissionsMapperCacheProvider,
+)
+from amdb.infrastructure.persistence.caching.permissions_mapper import (
+ CachingPermissionsMapper,
+)
+from amdb.infrastructure.password_manager.hash_computer import HashComputer
+from amdb.infrastructure.password_manager.password_manager import (
+ HashingPasswordManager,
+)
+from amdb.presentation.create_handler import CreateHandler
+
+
+class ConnectionsProvider(Provider):
+ scope = Scope.APP
+
+ def __init__(
+ self,
+ *,
+ postgres_config: PostgresConfig,
+ redis_config: RedisConfig,
+ ) -> None:
+ super().__init__()
+ self._postgsres_config = postgres_config
+ self._redis_config = redis_config
+
+ @provide
+ def sqlaclhemy_engine(self) -> Engine:
+ return create_engine(self._postgsres_config.url)
+
+ @provide
+ def redis(self) -> Redis:
+ redis = Redis.from_url(
+ url=self._redis_config.url,
+ decode_responses=True,
+ )
+ return cast(Redis, redis)
+
+ @provide(scope=Scope.REQUEST)
+ def sqlalchemy_connection(
+ self,
+ sqlalchemy_engine: Engine,
+ ) -> Iterable[Connection]:
+ with sqlalchemy_engine.connect() as sqlalchemy_connection:
+ yield sqlalchemy_connection
+
+
+class AdaptersProvider(Provider):
+ scope = Scope.REQUEST
+
+ user_gateway = provide(source=UserMapper, provides=UserGateway)
+ movie_gateway = provide(source=MovieMapper, provides=MovieGateway)
+ rating_gateway = provide(source=RatingMapper, provides=RatingGateway)
+ review_gateway = provide(source=ReviewMapper, provides=ReviewGateway)
+ detailed_movie_reader = provide(
+ source=DetailedMovieViewModelMapper,
+ provides=DetailedMovieViewModelReader,
+ )
+ non_detailed_movie_reader = provide(
+ source=NonDetailedMovieViewModelMapper,
+ provides=NonDetailedMovieViewModelReader,
+ )
+ detailed_review_reader = provide(
+ source=DetailedReviewViewModelMapper,
+ provides=DetailedReviewViewModelReader,
+ )
+
+ unit_of_work = alias(source=Connection, provides=UnitOfWork)
+
+ @provide
+ def permissions_mapper(
+ self,
+ sqlalchemy_connection: Connection,
+ redis: Redis,
+ ) -> PermissionsMapper:
+ permissions_mapper = PermissionsMapper(sqlalchemy_connection)
+ cache_provider = PermissionsMapperCacheProvider(redis)
+ return CachingPermissionsMapper( # type: ignore
+ permissions_mapper=permissions_mapper,
+ cache_provider=cache_provider,
+ )
+
+ @provide
+ def permissions_gateway(
+ self,
+ sqlalchemy_connection: Connection,
+ redis: Redis,
+ ) -> PermissionsGateway:
+ permissions_mapper = PermissionsMapper(sqlalchemy_connection)
+ cache_provider = PermissionsMapperCacheProvider(redis)
+ return CachingPermissionsMapper(
+ permissions_mapper=permissions_mapper,
+ cache_provider=cache_provider,
+ )
+
+ @provide
+ def password_manager(
+ self,
+ sqlalchemy_connection: Connection,
+ ) -> PasswordManager:
+ password_hash_mapper = PasswordHashMapper(sqlalchemy_connection)
+ return HashingPasswordManager(
+ hash_computer=HashComputer(),
+ password_hash_gateway=password_hash_mapper,
+ )
+
+
+class HandlersProvider(Provider):
+ scope = Scope.REQUEST
+
+ @provide
+ def register_user_handler(
+ self,
+ user_gateway: UserGateway,
+ permissions_gateway: PermissionsGateway,
+ unit_of_work: UnitOfWork,
+ password_manager: PasswordManager,
+ ) -> RegisterUserHandler:
+ return RegisterUserHandler(
+ create_user=CreateUser(),
+ user_gateway=user_gateway,
+ permissions_gateway=permissions_gateway,
+ unit_of_work=unit_of_work,
+ password_manager=password_manager,
+ )
+
+ @provide
+ def login_handler(
+ self,
+ user_gateway: UserGateway,
+ permissions_gateway: PermissionsGateway,
+ password_manager: PasswordManager,
+ ) -> LoginHandler:
+ return LoginHandler(
+ access_concern=AccessConcern(),
+ user_gateway=user_gateway,
+ permissions_gateway=permissions_gateway,
+ password_manager=password_manager,
+ )
+
+ @provide
+ def create_movie_handler(
+ self,
+ movie_gateway: MovieGateway,
+ unit_of_work: UnitOfWork,
+ ) -> CreateMovieHandler:
+ return CreateMovieHandler(
+ create_movie=CreateMovie(),
+ movie_gateway=movie_gateway,
+ unit_of_work=unit_of_work,
+ )
+
+ @provide
+ def delete_movie_handler(
+ self,
+ movie_gateway: MovieGateway,
+ rating_gateway: RatingGateway,
+ review_gateway: ReviewGateway,
+ unit_of_work: UnitOfWork,
+ ) -> DeleteMovieHandler:
+ return DeleteMovieHandler(
+ movie_gateway=movie_gateway,
+ rating_gateway=rating_gateway,
+ review_gateway=review_gateway,
+ unit_of_work=unit_of_work,
+ )
+
+ @provide
+ def get_detailed_reviews_handler(
+ self,
+ movie_gateway: MovieGateway,
+ detailed_review_reader: DetailedReviewViewModelReader,
+ ) -> GetDetailedReviewsHandler:
+ return GetDetailedReviewsHandler(
+ movie_gateway=movie_gateway,
+ detailed_review_reader=detailed_review_reader,
+ )
+
+
+class HandlerCreatorsProvider(Provider):
+ scope = Scope.REQUEST
+
+ @provide
+ def get_detailed_movie_handler(
+ self,
+ detailed_movie_reader: DetailedMovieViewModelReader,
+ ) -> CreateHandler[GetDetailedMovieHandler]:
+ def create_handler(
+ identity_provider: IdentityProvider,
+ ) -> GetDetailedMovieHandler:
+ return GetDetailedMovieHandler(
+ detailed_movie_reader=detailed_movie_reader,
+ identity_provider=identity_provider,
+ )
+
+ return create_handler
+
+ @provide
+ def get_non_detailed_movies_handler(
+ self,
+ non_detailed_movie_reader: NonDetailedMovieViewModelReader,
+ ) -> CreateHandler[GetNonDetailedMoviesHandler]:
+ def create_handler(
+ identity_provider: IdentityProvider,
+ ) -> GetNonDetailedMoviesHandler:
+ return GetNonDetailedMoviesHandler(
+ non_detailed_movie_reader=non_detailed_movie_reader,
+ identity_provider=identity_provider,
+ )
+
+ return create_handler
+
+ @provide
+ def rate_movie_handler(
+ self,
+ permissions_gateway: PermissionsGateway,
+ user_gateway: UserGateway,
+ movie_gateway: MovieGateway,
+ rating_gateway: RatingGateway,
+ unit_of_work: UnitOfWork,
+ ) -> CreateHandler[RateMovieHandler]:
+ def create_handler(
+ identity_provider: IdentityProvider,
+ ) -> RateMovieHandler:
+ return RateMovieHandler(
+ access_concern=AccessConcern(),
+ rate_movie=RateMovie(),
+ permissions_gateway=permissions_gateway,
+ user_gateway=user_gateway,
+ movie_gateway=movie_gateway,
+ rating_gateway=rating_gateway,
+ unit_of_work=unit_of_work,
+ identity_provider=identity_provider,
+ )
+
+ return create_handler
+
+ @provide
+ def unrate_movie_handler(
+ self,
+ permissions_gateway: PermissionsGateway,
+ movie_gateway: MovieGateway,
+ rating_gateway: RatingGateway,
+ unit_of_work: UnitOfWork,
+ ) -> CreateHandler[UnrateMovieHandler]:
+ def create_handler(
+ identity_provider: IdentityProvider,
+ ) -> UnrateMovieHandler:
+ return UnrateMovieHandler(
+ access_concern=AccessConcern(),
+ unrate_movie=UnrateMovie(),
+ permissions_gateway=permissions_gateway,
+ movie_gateway=movie_gateway,
+ rating_gateway=rating_gateway,
+ unit_of_work=unit_of_work,
+ identity_provider=identity_provider,
+ )
+
+ return create_handler
+
+ @provide
+ def review_movie_handler(
+ self,
+ permissions_gateway: PermissionsGateway,
+ user_gateway: UserGateway,
+ movie_gateway: MovieGateway,
+ review_gateway: ReviewGateway,
+ unit_of_work: UnitOfWork,
+ ) -> CreateHandler[ReviewMovieHandler]:
+ def create_handler(
+ identity_provider: IdentityProvider,
+ ) -> ReviewMovieHandler:
+ return ReviewMovieHandler(
+ access_concern=AccessConcern(),
+ review_movie=ReviewMovie(),
+ permissions_gateway=permissions_gateway,
+ user_gateway=user_gateway,
+ movie_gateway=movie_gateway,
+ review_gateway=review_gateway,
+ unit_of_work=unit_of_work,
+ identity_provider=identity_provider,
+ )
+
+ return create_handler
diff --git a/src/amdb/main/web_api/__main__.py b/src/amdb/main/web_api/__main__.py
index f321b67..44388f6 100644
--- a/src/amdb/main/web_api/__main__.py
+++ b/src/amdb/main/web_api/__main__.py
@@ -1,41 +1,5 @@
-import asyncio
-import os
+from .app import run_web_api
-from uvicorn import Server, Config
-from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
-from amdb.infrastructure.persistence.redis.config import RedisConfig
-from amdb.infrastructure.auth.session.config import SessionConfig
-from .config import WebAPIConfig
-from .app import create_app
-
-
-async def main() -> None:
- path_to_config = os.getenv("CONFIG_PATH")
- if not path_to_config:
- message = "Path to config env var is not set"
- raise ValueError(message)
-
- web_api_config = WebAPIConfig.from_toml(path_to_config)
- postgres_config = PostgresConfig.from_toml(path_to_config)
- redis_config = RedisConfig.from_toml(path_to_config)
- session_config = SessionConfig.from_toml(path_to_config)
-
- app = create_app(
- web_api_config=web_api_config,
- postgres_config=postgres_config,
- redis_config=redis_config,
- session_config=session_config,
- )
- server = Server(
- Config(
- app=app,
- host=web_api_config.host,
- port=web_api_config.port,
- ),
- )
-
- await server.serve()
-
-
-asyncio.run(main())
+def main() -> None:
+ run_web_api()
diff --git a/src/amdb/main/web_api/app.py b/src/amdb/main/web_api/app.py
index dfa1555..1902b52 100644
--- a/src/amdb/main/web_api/app.py
+++ b/src/amdb/main/web_api/app.py
@@ -1,30 +1,62 @@
+import os
+
+import uvicorn
from fastapi import FastAPI
+from dishka import make_async_container
+from dishka.integrations.fastapi import setup_dishka
from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
from amdb.infrastructure.persistence.redis.config import RedisConfig
from amdb.infrastructure.auth.session.config import SessionConfig
+from amdb.presentation.web_api.router import router
from amdb.presentation.web_api.exception_handlers import (
setup_exception_handlers,
)
-from amdb.presentation.web_api.routers.setup import setup_routers
-from .di import setup_dependecies
+from amdb.main.providers import (
+ ConnectionsProvider,
+ AdaptersProvider,
+ HandlersProvider,
+ HandlerCreatorsProvider,
+)
+from .providers import SessionAdaptersProvider
from .config import WebAPIConfig
-def create_app(
- web_api_config: WebAPIConfig,
- postgres_config: PostgresConfig,
- redis_config: RedisConfig,
- session_config: SessionConfig,
-) -> FastAPI:
- app = FastAPI(version=web_api_config.version)
- setup_dependecies(
- app=app,
- session_config=session_config,
- postgres_config=postgres_config,
- redis_config=redis_config,
+def run_web_api() -> None:
+ path_to_config = os.getenv("CONFIG_PATH")
+ if not path_to_config:
+ message = "Path to config env var is not set"
+ raise ValueError(message)
+
+ web_api_config = WebAPIConfig.from_toml(path_to_config)
+ postgres_config = PostgresConfig.from_toml(path_to_config)
+ redis_config = RedisConfig.from_toml(path_to_config)
+ session_config = SessionConfig.from_toml(path_to_config)
+
+ app = FastAPI(
+ title="Awesome Movie Database",
+ version=web_api_config.version,
+ swagger_ui_parameters={"defaultModelsExpandDepth": -1},
)
+ app.include_router(router)
setup_exception_handlers(app)
- setup_routers(app)
- return app
+ container = make_async_container(
+ ConnectionsProvider(
+ postgres_config=postgres_config,
+ redis_config=redis_config,
+ ),
+ AdaptersProvider(),
+ SessionAdaptersProvider(
+ session_config=session_config,
+ ),
+ HandlersProvider(),
+ HandlerCreatorsProvider(),
+ )
+ setup_dishka(container, app)
+
+ uvicorn.run(
+ app=app,
+ host=web_api_config.host,
+ port=web_api_config.port,
+ )
diff --git a/src/amdb/main/web_api/config.py b/src/amdb/main/web_api/config.py
index 60a0108..050409c 100644
--- a/src/amdb/main/web_api/config.py
+++ b/src/amdb/main/web_api/config.py
@@ -1,6 +1,4 @@
from dataclasses import dataclass
-from typing import Union
-from os import PathLike
import toml
@@ -12,7 +10,7 @@ class WebAPIConfig:
port: int
@classmethod
- def from_toml(cls, path: Union[PathLike, str]) -> "WebAPIConfig":
+ def from_toml(cls, path: str) -> "WebAPIConfig":
toml_as_dict = toml.load(path)
web_api_section_as_dict = toml_as_dict["web-api"]
return WebAPIConfig(
diff --git a/src/amdb/main/web_api/di.py b/src/amdb/main/web_api/di.py
deleted file mode 100644
index 2a03b52..0000000
--- a/src/amdb/main/web_api/di.py
+++ /dev/null
@@ -1,50 +0,0 @@
-from typing import cast
-
-from fastapi import FastAPI
-from sqlalchemy import create_engine
-from redis.client import Redis
-
-from amdb.infrastructure.auth.session.session_processor import SessionProcessor
-from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
-from amdb.infrastructure.persistence.redis.config import RedisConfig
-from amdb.infrastructure.auth.session.config import SessionConfig
-from amdb.infrastructure.persistence.redis.mappers.session import SessionMapper
-from amdb.infrastructure.persistence.redis.mappers.permissions import (
- PermissionsMapper,
-)
-from amdb.infrastructure.password_manager.hash_computer import HashComputer
-from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.depends_stub import Stub
-from amdb.main.ioc import IoC
-
-
-def setup_dependecies(
- app: FastAPI,
- session_config: SessionConfig,
- postgres_config: PostgresConfig,
- redis_config: RedisConfig,
-) -> None:
- redis = Redis.from_url(redis_config.url, decode_responses=True)
- session_mapper = SessionMapper(
- redis=cast(Redis, redis),
- session_lifetime=session_config.lifetime,
- )
- app.dependency_overrides[Stub(SessionMapper)] = lambda: session_mapper # type: ignore
-
- permissions_mapper = PermissionsMapper(cast(Redis, redis))
- app.dependency_overrides[Stub(PermissionsMapper)] = (
- lambda: permissions_mapper
- ) # type: ignore
-
- sqlalchemy_engine = create_engine(postgres_config.url)
- ioc = IoC(
- sqlalchemy_engine=sqlalchemy_engine,
- permissions_mapper=permissions_mapper,
- hash_computer=HashComputer(),
- )
- app.dependency_overrides[HandlerFactory] = lambda: ioc # type: ignore
-
- session_processor = SessionProcessor()
- app.dependency_overrides[Stub(SessionProcessor)] = (
- lambda: session_processor
- ) # type: ignore
diff --git a/src/amdb/main/web_api/providers.py b/src/amdb/main/web_api/providers.py
new file mode 100644
index 0000000..a1af227
--- /dev/null
+++ b/src/amdb/main/web_api/providers.py
@@ -0,0 +1,30 @@
+from dishka import Provider, Scope, provide
+from redis import Redis
+
+from amdb.infrastructure.auth.session.config import SessionConfig
+from amdb.infrastructure.auth.session.session_processor import SessionProcessor
+from amdb.infrastructure.auth.session.session_gateway import SessionGateway
+from amdb.infrastructure.persistence.redis.mappers.session import SessionMapper
+
+
+class SessionAdaptersProvider(Provider):
+ scope = Scope.APP
+
+ def __init__(self, session_config: SessionConfig) -> None:
+ super().__init__()
+ self._session_config = session_config
+
+ @provide
+ def session_config(self) -> SessionConfig:
+ return self._session_config
+
+ @provide
+ def session_processor(self) -> SessionProcessor:
+ return SessionProcessor()
+
+ @provide
+ def session_gateway(self, redis: Redis) -> SessionGateway:
+ return SessionMapper(
+ redis=redis,
+ session_lifetime=self._session_config.lifetime,
+ )
diff --git a/src/amdb/presentation/cli/alembic.py b/src/amdb/presentation/cli/alembic.py
deleted file mode 100644
index 37372c6..0000000
--- a/src/amdb/presentation/cli/alembic.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from typing import Annotated
-
-import typer
-from alembic import config
-
-from amdb.infrastructure.persistence.alembic.config import ALEMBIC_CONFIG
-
-
-migration_commands = typer.Typer(name="migration")
-
-
-@migration_commands.command()
-def alembic(commands: Annotated[list[str], typer.Argument()]) -> None:
- """
- [green]Run[/green] alembic.
- """
- config.main(["-c", ALEMBIC_CONFIG, *commands])
diff --git a/src/amdb/presentation/cli/movie.py b/src/amdb/presentation/cli/movie.py
deleted file mode 100644
index a430a1d..0000000
--- a/src/amdb/presentation/cli/movie.py
+++ /dev/null
@@ -1,89 +0,0 @@
-from datetime import datetime
-from typing import Annotated
-from uuid import UUID
-
-import typer
-import rich
-import rich.box
-import rich.table
-
-from amdb.domain.entities.movie import MovieId
-from amdb.application.common.identity_provider import IdentityProvider
-from amdb.application.commands.create_movie import CreateMovieCommand
-from amdb.application.commands.delete_movie import DeleteMovieCommand
-from amdb.presentation.handler_factory import HandlerFactory
-
-
-movie_commands = typer.Typer(name="movie")
-
-
-@movie_commands.command()
-def create(
- ctx: typer.Context,
- title: Annotated[str, typer.Option("--title", "-t", help="Movie title.")],
- release_date: Annotated[
- datetime,
- typer.Option("--release_date", "-rd", help="Movie release date."),
- ],
- silently: Annotated[
- bool,
- typer.Option("--silently", "-s", help="Do not print movie id."),
- ] = False,
-) -> None:
- """
- [green]Create[/green] movie.
-
- If --silently is not used, will print movie id.
- """
- ioc: HandlerFactory = ctx.obj["ioc"]
- identity_provider: IdentityProvider = ctx.obj["identity_provider"]
-
- with ioc.create_movie(identity_provider) as create_movie_handler:
- create_movie_command = CreateMovieCommand(
- title=title,
- release_date=release_date.date(),
- )
- movie_id = create_movie_handler.execute(create_movie_command)
-
- if not silently:
- rich.print(movie_id)
-
-
-@movie_commands.command()
-def delete(
- ctx: typer.Context,
- movie_id: Annotated[UUID, typer.Argument(help="Movie id.")],
- force: Annotated[
- bool,
- typer.Option("--force", "-f", help="Do not ask for confirmation."),
- ] = False,
- silently: Annotated[
- bool,
- typer.Option("--silently", "-s", help="Do not print movie id"),
- ] = False,
-) -> None:
- """
- [red]Delete[/red] movie. Also [red]deletes[/red] ratings and
- reviews related to movie.
-
- If --force is not used, will ask for confirmation.
- If --silently is not used, will print movie id.
- """
- if not force:
- typer.confirm(
- text="Are you sure you want to delete movie?",
- default=True,
- abort=True,
- )
-
- ioc: HandlerFactory = ctx.obj["ioc"]
- identity_provider: IdentityProvider = ctx.obj["identity_provider"]
-
- with ioc.delete_movie(identity_provider) as delete_movie_handler:
- delete_movie_command = DeleteMovieCommand(
- movie_id=MovieId(movie_id),
- )
- delete_movie_handler.execute(delete_movie_command)
-
- if not silently:
- rich.print(movie_id)
diff --git a/src/amdb/presentation/cli/setup.py b/src/amdb/presentation/cli/setup.py
deleted file mode 100644
index 7b948f5..0000000
--- a/src/amdb/presentation/cli/setup.py
+++ /dev/null
@@ -1,9 +0,0 @@
-import typer
-
-from .movie import movie_commands
-from .alembic import migration_commands
-
-
-def setup_typer_command_handlers(app: typer.Typer) -> None:
- app.add_typer(movie_commands)
- app.add_typer(migration_commands)
diff --git a/src/amdb/presentation/create_handler.py b/src/amdb/presentation/create_handler.py
new file mode 100644
index 0000000..a408fdc
--- /dev/null
+++ b/src/amdb/presentation/create_handler.py
@@ -0,0 +1,16 @@
+__all__ = ("CreateHandler",)
+
+from typing import TypeVar, Protocol
+
+from amdb.application.common.identity_provider import IdentityProvider
+
+
+H = TypeVar("H", covariant=True)
+
+
+class CreateHandler(Protocol[H]):
+ def __call__(
+ self,
+ identity_provider: IdentityProvider,
+ ) -> H:
+ raise NotImplementedError
diff --git a/src/amdb/presentation/handler_factory.py b/src/amdb/presentation/handler_factory.py
deleted file mode 100644
index 5ad8d87..0000000
--- a/src/amdb/presentation/handler_factory.py
+++ /dev/null
@@ -1,81 +0,0 @@
-from abc import ABC, abstractmethod
-from typing import ContextManager
-
-from amdb.application.common.identity_provider import IdentityProvider
-from amdb.application.command_handlers.register_user import RegisterUserHandler
-from amdb.application.command_handlers.create_movie import CreateMovieHandler
-from amdb.application.command_handlers.delete_movie import DeleteMovieHandler
-from amdb.application.command_handlers.rate_movie import RateMovieHandler
-from amdb.application.command_handlers.unrate_movie import UnrateMovieHandler
-from amdb.application.command_handlers.review_movie import ReviewMovieHandler
-from amdb.application.query_handlers.login import LoginHandler
-from amdb.application.query_handlers.detailed_movie import (
- GetDetailedMovieHandler,
-)
-from amdb.application.query_handlers.non_detailed_movies import (
- GetNonDetailedMoviesHandler,
-)
-from amdb.application.query_handlers.reviews import GetReviewsHandler
-
-
-class HandlerFactory(ABC):
- @abstractmethod
- def register_user(self) -> ContextManager[RegisterUserHandler]:
- raise NotImplementedError
-
- @abstractmethod
- def login(self) -> ContextManager[LoginHandler]:
- raise NotImplementedError
-
- @abstractmethod
- def get_non_detailed_movies(
- self,
- identity_provider: IdentityProvider,
- ) -> ContextManager[GetNonDetailedMoviesHandler]:
- raise NotImplementedError
-
- @abstractmethod
- def get_detailed_movie(
- self,
- identity_provider: IdentityProvider,
- ) -> ContextManager[GetDetailedMovieHandler]:
- raise NotImplementedError
-
- @abstractmethod
- def create_movie(
- self,
- identity_provider: IdentityProvider,
- ) -> ContextManager[CreateMovieHandler]:
- raise NotImplementedError
-
- @abstractmethod
- def delete_movie(
- self,
- identity_provider: IdentityProvider,
- ) -> ContextManager[DeleteMovieHandler]:
- raise NotImplementedError
-
- @abstractmethod
- def rate_movie(
- self,
- identity_provider: IdentityProvider,
- ) -> ContextManager[RateMovieHandler]:
- raise NotImplementedError
-
- @abstractmethod
- def unrate_movie(
- self,
- identity_provider: IdentityProvider,
- ) -> ContextManager[UnrateMovieHandler]:
- raise NotImplementedError
-
- @abstractmethod
- def get_reviews(self) -> ContextManager[GetReviewsHandler]:
- raise NotImplementedError
-
- @abstractmethod
- def review_movie(
- self,
- identity_provider: IdentityProvider,
- ) -> ContextManager[ReviewMovieHandler]:
- raise NotImplementedError
diff --git a/src/amdb/presentation/cli/__init__.py b/src/amdb/presentation/web_api/auth/__init__.py
similarity index 100%
rename from src/amdb/presentation/cli/__init__.py
rename to src/amdb/presentation/web_api/auth/__init__.py
diff --git a/src/amdb/presentation/web_api/auth/login.py b/src/amdb/presentation/web_api/auth/login.py
new file mode 100644
index 0000000..e3f8fa8
--- /dev/null
+++ b/src/amdb/presentation/web_api/auth/login.py
@@ -0,0 +1,48 @@
+from typing import Annotated
+from datetime import datetime, timezone
+
+from fastapi import Response
+from dishka.integrations.fastapi import Depends, inject
+
+from amdb.domain.entities.user import UserId
+from amdb.application.queries.login import LoginQuery
+from amdb.application.query_handlers.login import LoginHandler
+from amdb.infrastructure.auth.session.config import SessionConfig
+from amdb.infrastructure.auth.session.session_processor import SessionProcessor
+from amdb.infrastructure.persistence.redis.mappers.session import SessionMapper
+from amdb.presentation.web_api.constants import SESSION_ID_COOKIE
+
+
+@inject
+async def login(
+ *,
+ handler: Annotated[LoginHandler, Depends()],
+ session_processor: Annotated[SessionProcessor, Depends()],
+ session_mapper: Annotated[SessionMapper, Depends()],
+ session_config: Annotated[SessionConfig, Depends()],
+ query: LoginQuery,
+ response: Response,
+) -> UserId:
+ """
+ Logins, returns user id, creates new authentication session
+ and sets cookie with its id. \n\n
+
+ #### Returns 400: \n
+ * When user doesn't exist \n
+ * When password is incorrect \n
+ * When access is denied \n
+ """
+ user_id = handler.execute(query)
+
+ session = session_processor.create(user_id)
+ session_id = session_mapper.save(session)
+ session_expires_at = datetime.now(timezone.utc) + session_config.lifetime
+
+ response.set_cookie(
+ key=SESSION_ID_COOKIE,
+ value=session_id,
+ expires=session_expires_at,
+ httponly=True,
+ )
+
+ return user_id
diff --git a/src/amdb/presentation/web_api/auth/register.py b/src/amdb/presentation/web_api/auth/register.py
new file mode 100644
index 0000000..5f78e0c
--- /dev/null
+++ b/src/amdb/presentation/web_api/auth/register.py
@@ -0,0 +1,46 @@
+from typing import Annotated
+from datetime import datetime, timezone
+
+from fastapi import Response
+from dishka.integrations.fastapi import Depends, inject
+
+from amdb.domain.entities.user import UserId
+from amdb.application.commands.register_user import RegisterUserCommand
+from amdb.application.command_handlers.register_user import RegisterUserHandler
+from amdb.infrastructure.auth.session.config import SessionConfig
+from amdb.infrastructure.auth.session.session_processor import SessionProcessor
+from amdb.infrastructure.persistence.redis.mappers.session import SessionMapper
+from amdb.presentation.web_api.constants import SESSION_ID_COOKIE
+
+
+@inject
+async def register(
+ *,
+ handler: Annotated[RegisterUserHandler, Depends()],
+ session_processor: Annotated[SessionProcessor, Depends()],
+ session_mapper: Annotated[SessionMapper, Depends()],
+ session_config: Annotated[SessionConfig, Depends()],
+ command: RegisterUserCommand,
+ response: Response,
+) -> UserId:
+ """
+ Registers user, returns his id, creates new
+ authentication session and sets cookie with its id. \n\n
+
+ #### Returns 400: \n
+ * When name is already taken
+ """
+ user_id = handler.execute(command)
+
+ session = session_processor.create(user_id)
+ session_id = session_mapper.save(session)
+ session_expires_at = datetime.now(timezone.utc) + session_config.lifetime
+
+ response.set_cookie(
+ key=SESSION_ID_COOKIE,
+ value=session_id,
+ expires=session_expires_at,
+ httponly=True,
+ )
+
+ return user_id
diff --git a/src/amdb/presentation/web_api/auth/router.py b/src/amdb/presentation/web_api/auth/router.py
new file mode 100644
index 0000000..f3b4f6d
--- /dev/null
+++ b/src/amdb/presentation/web_api/auth/router.py
@@ -0,0 +1,22 @@
+__all__ = ("auth_router",)
+
+from fastapi import APIRouter
+
+from .register import register
+from .login import login
+
+
+auth_router = APIRouter(
+ prefix="/auth",
+ tags=["auth"],
+)
+auth_router.add_api_route(
+ path="/register",
+ endpoint=register,
+ methods=["POST"],
+)
+auth_router.add_api_route(
+ path="/login",
+ endpoint=login,
+ methods=["POST"],
+)
diff --git a/src/amdb/presentation/web_api/dependencies/depends_stub.py b/src/amdb/presentation/web_api/dependencies/depends_stub.py
deleted file mode 100644
index 7a5f2e1..0000000
--- a/src/amdb/presentation/web_api/dependencies/depends_stub.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from typing import Callable
-
-
-class Stub:
- def __init__(self, dependency: Callable) -> None:
- self.dependency = dependency
-
- def __call__(self) -> None:
- raise NotImplementedError
-
- def __eq__(self, value: object) -> bool:
- if isinstance(value, Stub):
- return self.dependency == value.dependency
- return False
-
- def __hash__(self) -> int:
- return hash(self.dependency)
diff --git a/src/amdb/presentation/web_api/dependencies/identity_provider.py b/src/amdb/presentation/web_api/dependencies/identity_provider.py
deleted file mode 100644
index 3aee77c..0000000
--- a/src/amdb/presentation/web_api/dependencies/identity_provider.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from typing import Annotated, Optional
-
-from fastapi import Cookie, Depends
-
-from amdb.infrastructure.persistence.redis.mappers.session import SessionMapper
-from amdb.infrastructure.persistence.redis.mappers.permissions import (
- PermissionsMapper,
-)
-from amdb.infrastructure.auth.session.identity_provider import (
- SessionIdentityProvider,
-)
-from amdb.infrastructure.auth.session.session import SessionId
-from amdb.presentation.web_api.constants import SESSION_ID_COOKIE
-from .depends_stub import Stub
-
-
-def get_identity_provider(
- session_mapper: Annotated[SessionMapper, Depends(Stub(SessionMapper))],
- permissions_mapper: Annotated[
- PermissionsMapper,
- Depends(Stub(PermissionsMapper)),
- ],
- session_id: Annotated[
- Optional[str],
- Cookie(alias=SESSION_ID_COOKIE),
- ] = None,
-) -> SessionIdentityProvider:
- return SessionIdentityProvider(
- session_id=SessionId(session_id) if session_id else None,
- session_gateway=session_mapper,
- permissions_gateway=permissions_mapper,
- )
diff --git a/src/amdb/presentation/web_api/exception_handlers.py b/src/amdb/presentation/web_api/exception_handlers.py
index b2dcede..1ded5b6 100644
--- a/src/amdb/presentation/web_api/exception_handlers.py
+++ b/src/amdb/presentation/web_api/exception_handlers.py
@@ -23,8 +23,5 @@ def _application_error_handler(_, error: ApplicationError) -> JSONResponse:
return JSONResponse(content={"message": error.message}, status_code=400)
-def _infrastructure_error_handler(
- _,
- error: InfrastructureError,
-) -> JSONResponse:
+def _infrastructure_error_handler(_, _2: InfrastructureError) -> JSONResponse:
return JSONResponse(content=None, status_code=500)
diff --git a/src/amdb/presentation/web_api/dependencies/__init__.py b/src/amdb/presentation/web_api/movies/__init__.py
similarity index 100%
rename from src/amdb/presentation/web_api/dependencies/__init__.py
rename to src/amdb/presentation/web_api/movies/__init__.py
diff --git a/src/amdb/presentation/web_api/movies/get_detailed.py b/src/amdb/presentation/web_api/movies/get_detailed.py
new file mode 100644
index 0000000..ec2153a
--- /dev/null
+++ b/src/amdb/presentation/web_api/movies/get_detailed.py
@@ -0,0 +1,55 @@
+from typing import Annotated, Optional
+
+from fastapi import Cookie
+from dishka.integrations.fastapi import Depends, inject
+
+from amdb.domain.entities.movie import MovieId
+from amdb.application.common.view_models.detailed_movie import (
+ DetailedMovieViewModel,
+)
+from amdb.application.queries.detailed_movie import GetDetailedMovieQuery
+from amdb.application.query_handlers.detailed_movie import (
+ GetDetailedMovieHandler,
+)
+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
+
+
+HandlerCreator = CreateHandler[GetDetailedMovieHandler]
+
+
+@inject
+async def get_detailed_movie(
+ *,
+ create_handler: Annotated[HandlerCreator, Depends()],
+ session_gateway: Annotated[SessionGateway, Depends()],
+ permissions_gateway: Annotated[PermissionsGateway, Depends()],
+ session_id: Annotated[
+ Optional[str],
+ Cookie(alias=SESSION_ID_COOKIE),
+ ] = None,
+ movie_id: MovieId,
+) -> DetailedMovieViewModel:
+ """
+ Returns detailed movie information, detailed current user rating
+ and review on it. \n\n
+
+ #### Returns 400: \n
+ * When movie doesn't exist
+ """
+ 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 = GetDetailedMovieQuery(movie_id=movie_id)
+
+ return handler.execute(query)
diff --git a/src/amdb/presentation/web_api/movies/get_non_detailed.py b/src/amdb/presentation/web_api/movies/get_non_detailed.py
new file mode 100644
index 0000000..65596f3
--- /dev/null
+++ b/src/amdb/presentation/web_api/movies/get_non_detailed.py
@@ -0,0 +1,57 @@
+from typing import Annotated, Optional
+
+from fastapi import Cookie
+from dishka.integrations.fastapi import Depends, inject
+
+from amdb.application.common.view_models.non_detailed_movie import (
+ NonDetailedMovieViewModel,
+)
+from amdb.application.queries.non_detailed_movies import (
+ GetNonDetailedMoviesQuery,
+)
+from amdb.application.query_handlers.non_detailed_movies import (
+ GetNonDetailedMoviesHandler,
+)
+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
+
+
+HandlerCreator = CreateHandler[GetNonDetailedMoviesHandler]
+
+
+@inject
+async def get_non_detailed_movies(
+ *,
+ create_handler: Annotated[HandlerCreator, Depends()],
+ session_gateway: Annotated[SessionGateway, Depends()],
+ permissions_gateway: Annotated[PermissionsGateway, Depends()],
+ session_id: Annotated[
+ Optional[str],
+ Cookie(alias=SESSION_ID_COOKIE),
+ ] = None,
+ limit: int = 100,
+ offset: int = 0,
+) -> list[NonDetailedMovieViewModel]:
+ """
+ Returns list of non detailed movies and non detailed current
+ user rating. \n\n
+ """
+ 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 = GetNonDetailedMoviesQuery(
+ limit=limit,
+ offset=offset,
+ )
+
+ return handler.execute(query)
diff --git a/src/amdb/presentation/web_api/movies/router.py b/src/amdb/presentation/web_api/movies/router.py
new file mode 100644
index 0000000..e476763
--- /dev/null
+++ b/src/amdb/presentation/web_api/movies/router.py
@@ -0,0 +1,17 @@
+from fastapi import APIRouter
+
+from .get_non_detailed import get_non_detailed_movies
+from .get_detailed import get_detailed_movie
+
+
+movies_router = APIRouter(tags=["movies"])
+movies_router.add_api_route(
+ path="/non-detailed-movies",
+ endpoint=get_non_detailed_movies,
+ methods=["GET"],
+)
+movies_router.add_api_route(
+ path="/detailed-movies/{movie_id}",
+ endpoint=get_detailed_movie,
+ methods=["GET"],
+)
diff --git a/src/amdb/presentation/web_api/routers/__init__.py b/src/amdb/presentation/web_api/ratings/__init__.py
similarity index 100%
rename from src/amdb/presentation/web_api/routers/__init__.py
rename to src/amdb/presentation/web_api/ratings/__init__.py
diff --git a/src/amdb/presentation/web_api/ratings/rate_movie.py b/src/amdb/presentation/web_api/ratings/rate_movie.py
new file mode 100644
index 0000000..f824a98
--- /dev/null
+++ b/src/amdb/presentation/web_api/ratings/rate_movie.py
@@ -0,0 +1,49 @@
+from typing import Annotated, Optional
+
+from fastapi import Cookie
+from dishka.integrations.fastapi import Depends, inject
+
+from amdb.domain.entities.rating import RatingId
+from amdb.application.commands.rate_movie import RateMovieCommand
+from amdb.application.command_handlers.rate_movie import RateMovieHandler
+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
+
+
+HandlerCreator = CreateHandler[RateMovieHandler]
+
+
+@inject
+async def rate_movie(
+ *,
+ create_handler: Annotated[HandlerCreator, Depends()],
+ session_gateway: Annotated[SessionGateway, Depends()],
+ permissions_gateway: Annotated[PermissionsGateway, Depends()],
+ session_id: Annotated[
+ Optional[str],
+ Cookie(alias=SESSION_ID_COOKIE),
+ ] = None,
+ command: RateMovieCommand,
+) -> RatingId:
+ """
+ Create movie rating and returns its id. \n\n
+
+ #### Returns 400:
+ * When access is denied
+ * When movie doesn't exist
+ * When rating already exists
+ """
+ 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)
+
+ return handler.execute(command)
diff --git a/src/amdb/presentation/web_api/ratings/router.py b/src/amdb/presentation/web_api/ratings/router.py
new file mode 100644
index 0000000..410a184
--- /dev/null
+++ b/src/amdb/presentation/web_api/ratings/router.py
@@ -0,0 +1,20 @@
+from fastapi import APIRouter
+
+from .rate_movie import rate_movie
+from .unrate_movie import unrate_movie
+
+
+ratings_router = APIRouter(
+ prefix="/ratings",
+ tags=["ratings"],
+)
+ratings_router.add_api_route(
+ path="",
+ endpoint=rate_movie,
+ methods=["POST"],
+)
+ratings_router.add_api_route(
+ path="/{rating_id}",
+ endpoint=unrate_movie,
+ methods=["DELETE"],
+)
diff --git a/src/amdb/presentation/web_api/ratings/unrate_movie.py b/src/amdb/presentation/web_api/ratings/unrate_movie.py
new file mode 100644
index 0000000..44fbb37
--- /dev/null
+++ b/src/amdb/presentation/web_api/ratings/unrate_movie.py
@@ -0,0 +1,51 @@
+from typing import Annotated, Optional
+
+from fastapi import Cookie
+from dishka.integrations.fastapi import Depends, inject
+
+from amdb.domain.entities.rating import RatingId
+from amdb.application.commands.unrate_movie import UnrateMovieCommand
+from amdb.application.command_handlers.unrate_movie import UnrateMovieHandler
+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
+
+
+HandlerCreator = CreateHandler[UnrateMovieHandler]
+
+
+@inject
+async def unrate_movie(
+ *,
+ create_handler: Annotated[HandlerCreator, Depends()],
+ session_gateway: Annotated[SessionGateway, Depends()],
+ permissions_gateway: Annotated[PermissionsGateway, Depends()],
+ session_id: Annotated[
+ Optional[str],
+ Cookie(alias=SESSION_ID_COOKIE),
+ ] = None,
+ rating_id: RatingId,
+) -> None:
+ """
+ Removes movie rating. \n\n
+
+ #### Returns 400:
+ * When access is denied
+ * When rating doesn't exist
+ * When user is not a rating 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 = UnrateMovieCommand(rating_id=rating_id)
+
+ handler.execute(command)
diff --git a/src/amdb/presentation/web_api/routers/auth/__init__.py b/src/amdb/presentation/web_api/reviews/__init__.py
similarity index 100%
rename from src/amdb/presentation/web_api/routers/auth/__init__.py
rename to src/amdb/presentation/web_api/reviews/__init__.py
diff --git a/src/amdb/presentation/web_api/reviews/get_detailed.py b/src/amdb/presentation/web_api/reviews/get_detailed.py
new file mode 100644
index 0000000..454ba64
--- /dev/null
+++ b/src/amdb/presentation/web_api/reviews/get_detailed.py
@@ -0,0 +1,35 @@
+from typing import Annotated
+
+from dishka.integrations.fastapi import Depends, inject
+
+from amdb.domain.entities.movie import MovieId
+from amdb.application.common.view_models.detailed_review import (
+ DetailedReviewViewModel,
+)
+from amdb.application.queries.detailed_reviews import GetDetailedReviewsQuery
+from amdb.application.query_handlers.detailed_reviews import (
+ GetDetailedReviewsHandler,
+)
+
+
+@inject
+async def get_detailed_reviews(
+ *,
+ handler: Annotated[GetDetailedReviewsHandler, Depends()],
+ movie_id: MovieId,
+ limit: int = 100,
+ offset: int = 0,
+) -> list[DetailedReviewViewModel]:
+ """
+ Returns detailed movie reviews with ratings.\n\n
+
+ #### Returns 400:
+ * When movie doesn't exist
+ """
+ query = GetDetailedReviewsQuery(
+ movie_id=movie_id,
+ limit=limit,
+ offset=offset,
+ )
+
+ return handler.execute(query)
diff --git a/src/amdb/presentation/web_api/reviews/review_movie.py b/src/amdb/presentation/web_api/reviews/review_movie.py
new file mode 100644
index 0000000..e28f471
--- /dev/null
+++ b/src/amdb/presentation/web_api/reviews/review_movie.py
@@ -0,0 +1,49 @@
+from typing import Annotated, Optional
+
+from fastapi import Cookie
+from dishka.integrations.fastapi import Depends, inject
+
+from amdb.domain.entities.review import ReviewId
+from amdb.application.commands.review_movie import ReviewMovieCommand
+from amdb.application.command_handlers.review_movie import ReviewMovieHandler
+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
+
+
+HandlerCreator = CreateHandler[ReviewMovieHandler]
+
+
+@inject
+async def review_movie(
+ *,
+ create_handler: Annotated[HandlerCreator, Depends()],
+ session_gateway: Annotated[SessionGateway, Depends()],
+ permissions_gateway: Annotated[PermissionsGateway, Depends()],
+ session_id: Annotated[
+ Optional[str],
+ Cookie(alias=SESSION_ID_COOKIE),
+ ] = None,
+ command: ReviewMovieCommand,
+) -> ReviewId:
+ """
+ Create movie review and returns its id.\n\n
+
+ #### Returns 400:
+ * When access is denied
+ * When movie doesn't exist
+ * When review already exists
+ """
+ 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)
+
+ return handler.execute(command)
diff --git a/src/amdb/presentation/web_api/reviews/router.py b/src/amdb/presentation/web_api/reviews/router.py
new file mode 100644
index 0000000..32aebc1
--- /dev/null
+++ b/src/amdb/presentation/web_api/reviews/router.py
@@ -0,0 +1,17 @@
+from fastapi import APIRouter
+
+from .get_detailed import get_detailed_reviews
+from .review_movie import review_movie
+
+
+reviews_router = APIRouter(tags=["reviews"])
+reviews_router.add_api_route(
+ path="/movies/{movie_id}/detailed-reviews",
+ endpoint=get_detailed_reviews,
+ methods=["GET"],
+)
+reviews_router.add_api_route(
+ path="/reviews",
+ endpoint=review_movie,
+ methods=["POST"],
+)
diff --git a/src/amdb/presentation/web_api/router.py b/src/amdb/presentation/web_api/router.py
new file mode 100644
index 0000000..a24f55a
--- /dev/null
+++ b/src/amdb/presentation/web_api/router.py
@@ -0,0 +1,13 @@
+from fastapi import APIRouter
+
+from .auth.router import auth_router
+from .movies.router import movies_router
+from .ratings.router import ratings_router
+from .reviews.router import reviews_router
+
+
+router = APIRouter(prefix="/v1")
+router.include_router(auth_router)
+router.include_router(movies_router)
+router.include_router(ratings_router)
+router.include_router(reviews_router)
diff --git a/src/amdb/presentation/web_api/routers/auth/login.py b/src/amdb/presentation/web_api/routers/auth/login.py
deleted file mode 100644
index 7b594ea..0000000
--- a/src/amdb/presentation/web_api/routers/auth/login.py
+++ /dev/null
@@ -1,46 +0,0 @@
-from typing import Annotated
-
-from fastapi import Response, Depends
-
-from amdb.domain.entities.user import UserId
-from amdb.application.queries.login import LoginQuery
-from amdb.infrastructure.auth.session.session_processor import SessionProcessor
-from amdb.infrastructure.persistence.redis.mappers.session import SessionMapper
-from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.depends_stub import Stub
-from amdb.presentation.web_api.constants import SESSION_ID_COOKIE
-
-
-async def login(
- ioc: Annotated[HandlerFactory, Depends()],
- session_processor: Annotated[
- SessionProcessor,
- Depends(Stub(SessionProcessor)),
- ],
- session_mapper: Annotated[SessionMapper, Depends(Stub(SessionMapper))],
- login_query: LoginQuery,
- response: Response,
-) -> UserId:
- """
- ## Returns: \n
- - user id \n
- - session id in cookies \n
-
- ## Errors: \n
- - When access is denied \n
- - When user name doesn't exist \n
- - When password is incorrect \n
- """
- with ioc.login() as login_handler:
- user_id = login_handler.execute(login_query)
-
- session = session_processor.create(user_id=user_id)
- session_mapper.save(session)
-
- response.set_cookie(
- key=SESSION_ID_COOKIE,
- value=session.id,
- httponly=True,
- )
-
- return user_id
diff --git a/src/amdb/presentation/web_api/routers/auth/register.py b/src/amdb/presentation/web_api/routers/auth/register.py
deleted file mode 100644
index c0c38fa..0000000
--- a/src/amdb/presentation/web_api/routers/auth/register.py
+++ /dev/null
@@ -1,44 +0,0 @@
-from typing import Annotated
-
-from fastapi import Response, Depends
-
-from amdb.domain.entities.user import UserId
-from amdb.application.commands.register_user import RegisterUserCommand
-from amdb.infrastructure.auth.session.session_processor import SessionProcessor
-from amdb.infrastructure.persistence.redis.mappers.session import SessionMapper
-from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.depends_stub import Stub
-from amdb.presentation.web_api.constants import SESSION_ID_COOKIE
-
-
-async def register(
- ioc: Annotated[HandlerFactory, Depends()],
- session_processor: Annotated[
- SessionProcessor,
- Depends(Stub(SessionProcessor)),
- ],
- session_mapper: Annotated[SessionMapper, Depends(Stub(SessionMapper))],
- register_user_command: RegisterUserCommand,
- response: Response,
-) -> UserId:
- """
- ## Returns: \n
- - user id \n
- - session id in cookies \n
-
- ## Errors: \n
- - When user name already exists \n
- """
- with ioc.register_user() as register_user_handler:
- user_id = register_user_handler.execute(register_user_command)
-
- session = session_processor.create(user_id=user_id)
- session_mapper.save(session)
-
- response.set_cookie(
- key=SESSION_ID_COOKIE,
- value=session.id,
- httponly=True,
- )
-
- return user_id
diff --git a/src/amdb/presentation/web_api/routers/auth/router.py b/src/amdb/presentation/web_api/routers/auth/router.py
deleted file mode 100644
index ef97d8b..0000000
--- a/src/amdb/presentation/web_api/routers/auth/router.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from fastapi import APIRouter
-
-from .register import register
-from .login import login
-
-
-def create_auth_router() -> APIRouter:
- router = APIRouter(
- prefix="/auth",
- tags=["auth"],
- )
-
- router.add_api_route(
- path="/register",
- endpoint=register,
- methods=["POST"],
- )
- router.add_api_route(
- path="/login",
- endpoint=login,
- methods=["POST"],
- )
-
- return router
diff --git a/src/amdb/presentation/web_api/routers/movies/__init__.py b/src/amdb/presentation/web_api/routers/movies/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/src/amdb/presentation/web_api/routers/movies/get_movies.py b/src/amdb/presentation/web_api/routers/movies/get_movies.py
deleted file mode 100644
index aa06916..0000000
--- a/src/amdb/presentation/web_api/routers/movies/get_movies.py
+++ /dev/null
@@ -1,69 +0,0 @@
-from typing import Annotated
-
-from fastapi import Depends
-
-from amdb.domain.entities.movie import MovieId
-from amdb.application.common.identity_provider import IdentityProvider
-from amdb.application.queries.detailed_movie import GetDetailedMovieQuery
-from amdb.application.queries.non_detailed_movies import (
- GetNonDetailedMoviesQuery,
-)
-from amdb.application.common.view_models.detailed_movie import (
- DetailedMovieViewModel,
-)
-from amdb.application.common.view_models.non_detailed_movie import (
- NonDetailedMovieViewModel,
-)
-from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import (
- get_identity_provider,
-)
-
-
-async def get_non_detailed_movies(
- ioc: Annotated[HandlerFactory, Depends()],
- identity_provider: Annotated[
- IdentityProvider,
- Depends(get_identity_provider),
- ],
- limit: int = 100,
- offset: int = 0,
-) -> list[NonDetailedMovieViewModel]:
- """
- ## Errors: \n
- - When access is denied \n
- """
- with ioc.get_non_detailed_movies(
- identity_provider,
- ) as get_non_detailed_movies_handler:
- get_non_detailed_movies_query = GetNonDetailedMoviesQuery(
- limit=limit,
- offset=offset,
- )
- result = get_non_detailed_movies_handler.execute(
- get_non_detailed_movies_query,
- )
- return result
-
-
-async def get_detailed_movie(
- ioc: Annotated[HandlerFactory, Depends()],
- identity_provider: Annotated[
- IdentityProvider,
- Depends(get_identity_provider),
- ],
- movie_id: MovieId,
-) -> DetailedMovieViewModel:
- """
- ## Errors: \n
- - When access is denied \n
- - When movie doesn't exist \n
- """
- with ioc.get_detailed_movie(
- identity_provider,
- ) as get_detailed_movie_handler:
- get_detailed_movie_query = GetDetailedMovieQuery(
- movie_id=movie_id,
- )
- result = get_detailed_movie_handler.execute(get_detailed_movie_query)
- return result
diff --git a/src/amdb/presentation/web_api/routers/movies/router.py b/src/amdb/presentation/web_api/routers/movies/router.py
deleted file mode 100644
index 890b974..0000000
--- a/src/amdb/presentation/web_api/routers/movies/router.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from fastapi import APIRouter
-
-from .get_movies import get_non_detailed_movies, get_detailed_movie
-
-
-def create_movies_router() -> APIRouter:
- router = APIRouter(
- prefix="",
- tags=["movies"],
- )
-
- router.add_api_route(
- path="/non-detailed-movies",
- endpoint=get_non_detailed_movies,
- methods=["GET"],
- )
- router.add_api_route(
- path="/detailed-movies/{movie_id}",
- endpoint=get_detailed_movie,
- methods=["GET"],
- )
-
- return router
diff --git a/src/amdb/presentation/web_api/routers/ratings/__init__.py b/src/amdb/presentation/web_api/routers/ratings/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/src/amdb/presentation/web_api/routers/ratings/rate_movie.py b/src/amdb/presentation/web_api/routers/ratings/rate_movie.py
deleted file mode 100644
index 585a648..0000000
--- a/src/amdb/presentation/web_api/routers/ratings/rate_movie.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from typing import Annotated
-
-from fastapi import Depends
-
-from amdb.domain.entities.rating import RatingId
-from amdb.application.common.identity_provider import IdentityProvider
-from amdb.application.commands.rate_movie import RateMovieCommand
-from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import (
- get_identity_provider,
-)
-
-
-async def rate_movie(
- ioc: Annotated[HandlerFactory, Depends(HandlerFactory)],
- identity_provider: Annotated[
- IdentityProvider,
- Depends(get_identity_provider),
- ],
- rate_movie_command: RateMovieCommand,
-) -> RatingId:
- """
- ## Returns: \n
- - Rating id
- ## Errors: \n
- - When access is denied \n
- - When movie doesn't exist \n
- - When movie is already rated \n
- """
- with ioc.rate_movie(identity_provider) as rate_movie_handler:
- rating_id = rate_movie_handler.execute(rate_movie_command)
-
- return rating_id
diff --git a/src/amdb/presentation/web_api/routers/ratings/router.py b/src/amdb/presentation/web_api/routers/ratings/router.py
deleted file mode 100644
index 26fbd93..0000000
--- a/src/amdb/presentation/web_api/routers/ratings/router.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from fastapi import APIRouter
-
-from .rate_movie import rate_movie
-from .unrate_movie import unrate_movie
-
-
-def create_ratings_router() -> APIRouter:
- router = APIRouter(
- prefix="",
- tags=["ratings"],
- )
-
- router.add_api_route(
- path="/me/ratings",
- endpoint=rate_movie,
- methods=["POST"],
- tags=["me"],
- )
- router.add_api_route(
- path="/me/ratings/{rating_id}",
- endpoint=unrate_movie,
- methods=["DELETE"],
- tags=["me"],
- )
-
- return router
diff --git a/src/amdb/presentation/web_api/routers/ratings/unrate_movie.py b/src/amdb/presentation/web_api/routers/ratings/unrate_movie.py
deleted file mode 100644
index e0b9f4f..0000000
--- a/src/amdb/presentation/web_api/routers/ratings/unrate_movie.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from typing import Annotated
-
-from fastapi import Depends
-
-from amdb.domain.entities.rating import RatingId
-from amdb.application.common.identity_provider import IdentityProvider
-from amdb.application.commands.unrate_movie import UnrateMovieCommand
-from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import (
- get_identity_provider,
-)
-
-
-async def unrate_movie(
- ioc: Annotated[HandlerFactory, Depends(HandlerFactory)],
- identity_provider: Annotated[
- IdentityProvider,
- Depends(get_identity_provider),
- ],
- rating_id: RatingId,
-) -> None:
- """
- ## Errors: \n
- - When access is denied \n
- - When user is not an owner of rating \n
- - When movie is already rated \n
- """
- with ioc.unrate_movie(identity_provider) as unrate_movie_handler:
- unrate_movie_command = UnrateMovieCommand(
- rating_id=rating_id,
- )
- unrate_movie_handler.execute(unrate_movie_command)
diff --git a/src/amdb/presentation/web_api/routers/reviews/__init__.py b/src/amdb/presentation/web_api/routers/reviews/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/src/amdb/presentation/web_api/routers/reviews/get_reviews.py b/src/amdb/presentation/web_api/routers/reviews/get_reviews.py
deleted file mode 100644
index 70c5962..0000000
--- a/src/amdb/presentation/web_api/routers/reviews/get_reviews.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from typing import Annotated
-
-from fastapi import Depends
-
-from amdb.domain.entities.movie import MovieId
-from amdb.application.common.view_models.review import ReviewViewModel
-from amdb.application.queries.reviews import GetReviewsQuery
-from amdb.presentation.handler_factory import HandlerFactory
-
-
-async def get_reviews(
- ioc: Annotated[HandlerFactory, Depends()],
- movie_id: MovieId,
- limit: int = 100,
- offset: int = 0,
-) -> list[ReviewViewModel]:
- """
- ## Errors: \n
- - When movie doesn't exist \n
- """
- with ioc.get_reviews() as get_reviews_handler:
- get_reviews_query = GetReviewsQuery(
- movie_id=movie_id,
- limit=limit,
- offset=offset,
- )
- result = get_reviews_handler.execute(get_reviews_query)
- return result
diff --git a/src/amdb/presentation/web_api/routers/reviews/review_movie.py b/src/amdb/presentation/web_api/routers/reviews/review_movie.py
deleted file mode 100644
index 0498eb9..0000000
--- a/src/amdb/presentation/web_api/routers/reviews/review_movie.py
+++ /dev/null
@@ -1,46 +0,0 @@
-from typing import Annotated
-
-from fastapi import Depends
-from pydantic import BaseModel
-
-from amdb.domain.entities.movie import MovieId
-from amdb.domain.entities.review import ReviewId, ReviewType
-from amdb.application.common.identity_provider import IdentityProvider
-from amdb.application.commands.review_movie import ReviewMovieCommand
-from amdb.presentation.handler_factory import HandlerFactory
-from amdb.presentation.web_api.dependencies.identity_provider import (
- get_identity_provider,
-)
-
-
-class ReviewMovie(BaseModel):
- title: str
- content: str
- type: ReviewType
-
-
-async def review_movie(
- ioc: Annotated[HandlerFactory, Depends()],
- identity_provider: Annotated[
- IdentityProvider,
- Depends(get_identity_provider),
- ],
- movie_id: MovieId,
- data: ReviewMovie,
-) -> ReviewId:
- """
- ## Errors: \n
- - When access is denied \n
- - When movie doesn't exist \n
- - When movie is already reviewd \n
- """
- with ioc.review_movie(identity_provider) as review_movie_handler:
- review_movie_command = ReviewMovieCommand(
- movie_id=movie_id,
- title=data.title,
- content=data.content,
- type=data.type,
- )
- review_id = review_movie_handler.execute(review_movie_command)
-
- return review_id
diff --git a/src/amdb/presentation/web_api/routers/reviews/router.py b/src/amdb/presentation/web_api/routers/reviews/router.py
deleted file mode 100644
index 4fb4ce9..0000000
--- a/src/amdb/presentation/web_api/routers/reviews/router.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from fastapi import APIRouter
-
-from .get_reviews import get_reviews
-from .review_movie import review_movie
-
-
-def create_reviews_router() -> APIRouter:
- router = APIRouter(
- prefix="",
- tags=["reviews"],
- )
-
- router.add_api_route(
- path="/movies/{movie_id}/reviews",
- endpoint=get_reviews,
- methods=["GET"],
- )
- router.add_api_route(
- path="/me/reviews",
- endpoint=review_movie,
- methods=["POST"],
- tags=["me"],
- )
-
- return router
diff --git a/src/amdb/presentation/web_api/routers/setup.py b/src/amdb/presentation/web_api/routers/setup.py
deleted file mode 100644
index 616b55b..0000000
--- a/src/amdb/presentation/web_api/routers/setup.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from fastapi import FastAPI
-
-from .auth.router import create_auth_router
-from .movies.router import create_movies_router
-from .ratings.router import create_ratings_router
-from .reviews.router import create_reviews_router
-
-
-def setup_routers(app: FastAPI) -> None:
- app.include_router(create_auth_router())
- app.include_router(create_movies_router())
- app.include_router(create_ratings_router())
- app.include_router(create_reviews_router())
diff --git a/tests/unit/application/conftest.py b/tests/unit/application/conftest.py
index 0af152e..5baac92 100644
--- a/tests/unit/application/conftest.py
+++ b/tests/unit/application/conftest.py
@@ -21,17 +21,23 @@
from amdb.infrastructure.persistence.sqlalchemy.mappers.password_hash import (
PasswordHashMapper,
)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.permissions import (
+ PermissionsMapper,
+)
from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.non_detailed_movie import (
NonDetailedMovieViewModelMapper,
)
from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.detailed_movie import (
DetailedMovieViewModelMapper,
)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.review import (
- ReviewViewModelMapper,
+from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.detailed_review import (
+ DetailedReviewViewModelMapper,
)
-from amdb.infrastructure.persistence.redis.mappers.permissions import (
- PermissionsMapper,
+from amdb.infrastructure.persistence.redis.cache.permissions_mapper import (
+ PermissionsMapperCacheProvider,
+)
+from amdb.infrastructure.persistence.caching.permissions_mapper import (
+ CachingPermissionsMapper,
)
from amdb.infrastructure.password_manager.hash_computer import HashComputer
from amdb.infrastructure.password_manager.password_manager import (
@@ -59,8 +65,14 @@ def sqlalchemy_connection(sqlalchemy_engine: Engine) -> Iterator[Connection]:
@pytest.fixture
-def permissions_gateway(redis: Redis) -> PermissionsMapper:
- return PermissionsMapper(redis)
+def permissions_gateway(
+ redis: Redis,
+ sqlalchemy_connection: Connection,
+) -> CachingPermissionsMapper:
+ return CachingPermissionsMapper(
+ permissions_mapper=PermissionsMapper(sqlalchemy_connection),
+ cache_provider=PermissionsMapperCacheProvider(redis),
+ )
@pytest.fixture
@@ -98,8 +110,10 @@ def non_detailed_movie_reader(
@pytest.fixture
-def review_reader(sqlalchemy_connection: Connection) -> ReviewViewModelMapper:
- return ReviewViewModelMapper(sqlalchemy_connection)
+def detailed_review_reader(
+ sqlalchemy_connection: Connection,
+) -> DetailedMovieViewModelMapper:
+ return DetailedReviewViewModelMapper(sqlalchemy_connection)
@pytest.fixture
diff --git a/tests/unit/application/query_handlers/test_get_reviews.py b/tests/unit/application/query_handlers/test_get_detailed_reviews.py
similarity index 70%
rename from tests/unit/application/query_handlers/test_get_reviews.py
rename to tests/unit/application/query_handlers/test_get_detailed_reviews.py
index 86b4d8b..b879fc8 100644
--- a/tests/unit/application/query_handlers/test_get_reviews.py
+++ b/tests/unit/application/query_handlers/test_get_detailed_reviews.py
@@ -12,25 +12,29 @@
from amdb.application.common.gateways.rating import RatingGateway
from amdb.application.common.gateways.review import ReviewGateway
from amdb.application.common.unit_of_work import UnitOfWork
-from amdb.application.common.readers.review import ReviewViewModelReader
-from amdb.application.common.view_models.review import (
+from amdb.application.common.readers.detailed_review import (
+ DetailedReviewViewModelReader,
+)
+from amdb.application.common.view_models.detailed_review import (
UserRating,
UserReview,
- ReviewViewModel,
+ DetailedReviewViewModel,
+)
+from amdb.application.queries.detailed_reviews import GetDetailedReviewsQuery
+from amdb.application.query_handlers.detailed_reviews import (
+ GetDetailedReviewsHandler,
)
-from amdb.application.queries.reviews import GetReviewsQuery
-from amdb.application.query_handlers.reviews import GetReviewsHandler
from amdb.application.common.constants.exceptions import MOVIE_DOES_NOT_EXIST
from amdb.application.common.exception import ApplicationError
-def test_get_reviews(
+def test_get_detailed_reviews(
user_gateway: UserGateway,
movie_gateway: MovieGateway,
rating_gateway: RatingGateway,
review_gateway: ReviewGateway,
unit_of_work: UnitOfWork,
- review_reader: ReviewViewModelReader,
+ detailed_review_reader: DetailedReviewViewModelReader,
):
user = User(
id=UserId(uuid7()),
@@ -69,18 +73,18 @@ def test_get_reviews(
unit_of_work.commit()
- get_reviews_query = GetReviewsQuery(
+ get_detailed_reviews_query = GetDetailedReviewsQuery(
movie_id=movie.id,
limit=10,
offset=0,
)
- get_reviews_handler = GetReviewsHandler(
+ get_detailed_reviews_handler = GetDetailedReviewsHandler(
movie_gateway=movie_gateway,
- review_view_model_reader=review_reader,
+ detailed_review_reader=detailed_review_reader,
)
expected_result = [
- ReviewViewModel(
+ DetailedReviewViewModel(
user_id=user.id,
user_review=UserReview(
id=review.id,
@@ -96,26 +100,26 @@ def test_get_reviews(
),
),
]
- result = get_reviews_handler.execute(get_reviews_query)
+ result = get_detailed_reviews_handler.execute(get_detailed_reviews_query)
assert expected_result == result
-def test_get_reviews_should_raise_error_when_movie_does_not_exist(
+def test_get_detailed_reviews_should_raise_error_when_movie_does_not_exist(
movie_gateway: MovieGateway,
- review_reader: ReviewViewModelReader,
+ detailed_review_reader: DetailedReviewViewModelReader,
):
- get_reviews_query = GetReviewsQuery(
+ get_detailed_reviews_query = GetDetailedReviewsQuery(
movie_id=MovieId(uuid7()),
limit=10,
offset=0,
)
- get_reviews_handler = GetReviewsHandler(
+ get_detailed_reviews_handler = GetDetailedReviewsHandler(
movie_gateway=movie_gateway,
- review_view_model_reader=review_reader,
+ detailed_review_reader=detailed_review_reader,
)
with pytest.raises(ApplicationError) as error:
- get_reviews_handler.execute(get_reviews_query)
+ get_detailed_reviews_handler.execute(get_detailed_reviews_query)
assert error.value.message == MOVIE_DOES_NOT_EXIST
From 0c32872631da46e9eb38d93e2b458d3a4daa4ea7 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Sat, 24 Feb 2024 01:05:27 +0400
Subject: [PATCH 08/39] Add `create reviews table` migration
---
.../migrations/versions/a2f7c2383ba8_.py | 37 +++++++++++++++++++
1 file changed, 37 insertions(+)
create mode 100644 src/amdb/infrastructure/persistence/alembic/migrations/versions/a2f7c2383ba8_.py
diff --git a/src/amdb/infrastructure/persistence/alembic/migrations/versions/a2f7c2383ba8_.py b/src/amdb/infrastructure/persistence/alembic/migrations/versions/a2f7c2383ba8_.py
new file mode 100644
index 0000000..2a7b54c
--- /dev/null
+++ b/src/amdb/infrastructure/persistence/alembic/migrations/versions/a2f7c2383ba8_.py
@@ -0,0 +1,37 @@
+"""
+Add permissions table
+
+Revision ID: a2f7c2383ba8
+Revises: 65f8840f4494
+Create Date: 2024-02-24 00:59:16.630215
+
+"""
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision: str = "a2f7c2383ba8"
+down_revision: Union[str, None] = "65f8840f4494"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ op.create_table(
+ "permissions",
+ sa.Column("user_id", sa.Uuid(), nullable=False),
+ sa.Column("value", sa.Integer(), nullable=False),
+ sa.PrimaryKeyConstraint("user_id"),
+ sa.ForeignKeyConstraint(
+ ["user_id"],
+ ["users.id"],
+ ondelete="CASCADE",
+ ),
+ )
+
+
+def downgrade() -> None:
+ op.drop_table("permissions")
From ac44d71f95e6f692d7abb61440fedbb8420c3cc4 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Sat, 24 Feb 2024 12:22:28 +0400
Subject: [PATCH 09/39] Make review type string
---
src/amdb/domain/entities/review.py | 10 +--
.../persistence/alembic/migrations/env.py | 28 +++------
.../migrations/versions/a2f7c2383ba8_.py | 61 ++++++++++++++++++-
.../persistence/sqlalchemy/models/review.py | 2 +-
src/amdb/main/web_api/providers.py | 7 +++
5 files changed, 81 insertions(+), 27 deletions(-)
diff --git a/src/amdb/domain/entities/review.py b/src/amdb/domain/entities/review.py
index 31f8e39..1b1a22c 100644
--- a/src/amdb/domain/entities/review.py
+++ b/src/amdb/domain/entities/review.py
@@ -1,7 +1,7 @@
from dataclasses import dataclass
from datetime import datetime
from typing import NewType
-from enum import IntEnum
+from enum import Enum
from uuid import UUID
from .user import UserId
@@ -11,10 +11,10 @@
ReviewId = NewType("ReviewId", UUID)
-class ReviewType(IntEnum):
- NEUTRAL = 0
- POSITIVE = 1
- NEGATIVE = 2
+class ReviewType(Enum):
+ NEUTRAL = "neutral"
+ POSITIVE = "positive"
+ NEGATIVE = "negative"
@dataclass(frozen=True, slots=True)
diff --git a/src/amdb/infrastructure/persistence/alembic/migrations/env.py b/src/amdb/infrastructure/persistence/alembic/migrations/env.py
index 00a7fa0..8186e3d 100644
--- a/src/amdb/infrastructure/persistence/alembic/migrations/env.py
+++ b/src/amdb/infrastructure/persistence/alembic/migrations/env.py
@@ -6,6 +6,7 @@
from alembic import context
from amdb.infrastructure.persistence.sqlalchemy.models.base import Model
+from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
@@ -27,33 +28,20 @@
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
-POSTGRES_HOST_ENV = "POSTGRES_HOST"
-POSTGRES_PORT_ENV = "POSTGRES_PORT"
-POSTGRES_NAME_ENV = "POSTGRES_DB"
-POSTGRES_USER_ENV = "POSTGRES_USER"
-POSTGRES_PASSWORD_ENV = "POSTGRES_PASSWORD"
-
-
-def get_env(key: str) -> str:
- value = os.getenv(key)
- if value is None:
- message = f"Env variable {key} is not set"
- raise ValueError(message)
- return value
-
def get_sqlalchemy_url() -> str:
sqlalchemy_url = config.get_main_option("sqlalchemy.url")
if sqlalchemy_url is not None:
return sqlalchemy_url
else:
- host = get_env(POSTGRES_HOST_ENV)
- port = get_env(POSTGRES_PORT_ENV)
- name = get_env(POSTGRES_NAME_ENV)
- user = get_env(POSTGRES_USER_ENV)
- password = get_env(POSTGRES_PASSWORD_ENV)
+ path_to_config = os.getenv("CONFIG_PATH")
+ if not path_to_config:
+ message = "Path to config env var is not set"
+ raise ValueError(message)
+
+ postgres_config = PostgresConfig.from_toml(path_to_config)
- return f"postgresql://{user}:{password}@{host}:{port}/{name}"
+ return postgres_config.url
def run_migrations_offline() -> None:
diff --git a/src/amdb/infrastructure/persistence/alembic/migrations/versions/a2f7c2383ba8_.py b/src/amdb/infrastructure/persistence/alembic/migrations/versions/a2f7c2383ba8_.py
index 2a7b54c..061ebe4 100644
--- a/src/amdb/infrastructure/persistence/alembic/migrations/versions/a2f7c2383ba8_.py
+++ b/src/amdb/infrastructure/persistence/alembic/migrations/versions/a2f7c2383ba8_.py
@@ -1,5 +1,6 @@
"""
-Add permissions table
+Add permissions table,
+Make type column string in review table
Revision ID: a2f7c2383ba8
Revises: 65f8840f4494
@@ -31,7 +32,65 @@ def upgrade() -> None:
ondelete="CASCADE",
),
)
+ with op.batch_alter_table("reviews") as batch_op:
+ batch_op.alter_column(
+ "type",
+ new_column_name="old_type",
+ )
+ batch_op.add_column(
+ sa.Column("type", sa.String(), nullable=True),
+ )
+ op.execute(
+ """
+ UPDATE reviews
+ SET type =
+ (
+ SELECT
+ CASE
+ WHEN r.old_type = 0 THEN 'neutral'
+ WHEN r.old_type = 1 THEN 'positive'
+ WHEN r.old_type = 2 THEN 'negative'
+ END
+ FROM reviews r
+ )
+ """,
+ )
+ with op.batch_alter_table("reviews") as batch_op:
+ batch_op.drop_column("old_type")
+ batch_op.alter_column(
+ "type",
+ nullable=False,
+ )
def downgrade() -> None:
op.drop_table("permissions")
+ with op.batch_alter_table("reviews") as batch_op:
+ batch_op.alter_column(
+ "type",
+ new_column_name="old_type",
+ )
+ batch_op.add_column(
+ sa.Column("type", sa.Integer(), nullable=True),
+ )
+ op.execute(
+ """
+ UPDATE reviews
+ SET type =
+ (
+ SELECT
+ CASE
+ WHEN r.old_type = 'neutral' THEN 0
+ WHEN r.old_type = 'positive' THEN 1
+ WHEN r.old_type = 'negative' THEN 2
+ END
+ FROM reviews r
+ )
+ """,
+ )
+ with op.batch_alter_table("reviews") as batch_op:
+ batch_op.drop_column("old_type")
+ batch_op.alter_column(
+ "type",
+ nullable=False,
+ )
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/models/review.py b/src/amdb/infrastructure/persistence/sqlalchemy/models/review.py
index 90c4f6f..37d3221 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/models/review.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/models/review.py
@@ -23,7 +23,7 @@ class ReviewModel(Model):
)
title: Mapped[str]
content: Mapped[str]
- type: Mapped[int]
+ type: Mapped[str]
created_at: Mapped[datetime]
user: Mapped[UserModel] = relationship()
diff --git a/src/amdb/main/web_api/providers.py b/src/amdb/main/web_api/providers.py
index a1af227..8c37894 100644
--- a/src/amdb/main/web_api/providers.py
+++ b/src/amdb/main/web_api/providers.py
@@ -22,6 +22,13 @@ def session_config(self) -> SessionConfig:
def session_processor(self) -> SessionProcessor:
return SessionProcessor()
+ @provide
+ def session_mapper(self, redis: Redis) -> SessionMapper:
+ return SessionMapper(
+ redis=redis,
+ session_lifetime=self._session_config.lifetime,
+ )
+
@provide
def session_gateway(self, redis: Redis) -> SessionGateway:
return SessionMapper(
From 1db6f89ee335facd42822680e9120c71820c3447 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Sat, 24 Feb 2024 17:54:42 +0400
Subject: [PATCH 10/39] Add doc strings to `IdentityProvider` protocol
---
src/amdb/application/common/identity_provider.py | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/src/amdb/application/common/identity_provider.py b/src/amdb/application/common/identity_provider.py
index 769cd06..b992b4a 100644
--- a/src/amdb/application/common/identity_provider.py
+++ b/src/amdb/application/common/identity_provider.py
@@ -5,10 +5,22 @@
class IdentityProvider(Protocol):
def user_id(self) -> UserId:
+ """
+ Returns current user id if authenticated,
+ otherwise raises error
+ """
raise NotImplementedError
def user_id_or_none(self) -> Optional[UserId]:
+ """
+ Returns current user id if authenticated,
+ otherwise returns None
+ """
raise NotImplementedError
def permissions(self) -> int:
+ """
+ Returns current user permissions if authenticated,
+ otherwise raises error
+ """
raise NotImplementedError
From 8b43cdfd8b14f39f8359eb40f89ecd469020adbd Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Sat, 24 Feb 2024 19:50:20 +0400
Subject: [PATCH 11/39] Refactor tests
---
.../command_handlers/test_create_movie.py | 6 ++--
.../command_handlers/test_delete_movie.py | 6 ++--
.../command_handlers/test_rate_movie.py | 30 +++++++++----------
.../command_handlers/test_register_user.py | 12 ++++----
.../command_handlers/test_review_movie.py | 24 +++++++--------
.../command_handlers/test_unrate_movie.py | 24 +++++++--------
.../query_handlers/test_detailed_movie.py | 12 ++++----
.../test_get_detailed_reviews.py | 12 ++++----
.../application/query_handlers/test_login.py | 12 ++++----
.../test_non_detailed_movies.py | 8 ++---
10 files changed, 72 insertions(+), 74 deletions(-)
diff --git a/tests/unit/application/command_handlers/test_create_movie.py b/tests/unit/application/command_handlers/test_create_movie.py
index 719cccd..1a73f7f 100644
--- a/tests/unit/application/command_handlers/test_create_movie.py
+++ b/tests/unit/application/command_handlers/test_create_movie.py
@@ -11,14 +11,14 @@ def test_create_movie(
movie_gateway: MovieGateway,
unit_of_work: UnitOfWork,
):
- create_movie_command = CreateMovieCommand(
+ command = CreateMovieCommand(
title="Matrix",
release_date=date(1999, 3, 31),
)
- create_movie_handler = CreateMovieHandler(
+ handler = CreateMovieHandler(
create_movie=CreateMovie(),
movie_gateway=movie_gateway,
unit_of_work=unit_of_work,
)
- create_movie_handler.execute(create_movie_command)
+ handler.execute(command)
diff --git a/tests/unit/application/command_handlers/test_delete_movie.py b/tests/unit/application/command_handlers/test_delete_movie.py
index 5fd6fdb..9e01fa0 100644
--- a/tests/unit/application/command_handlers/test_delete_movie.py
+++ b/tests/unit/application/command_handlers/test_delete_movie.py
@@ -50,10 +50,10 @@ def test_delete_movie_should_raise_error_when_movie_does_not_exist(
review_gateway: ReviewGateway,
unit_of_work: UnitOfWork,
):
- delete_movie_command = DeleteMovieCommand(
+ command = DeleteMovieCommand(
movie_id=MovieId(uuid7()),
)
- delete_movie_handler = DeleteMovieHandler(
+ handler = DeleteMovieHandler(
movie_gateway=movie_gateway,
rating_gateway=rating_gateway,
review_gateway=review_gateway,
@@ -61,6 +61,6 @@ def test_delete_movie_should_raise_error_when_movie_does_not_exist(
)
with pytest.raises(ApplicationError) as error:
- delete_movie_handler.execute(delete_movie_command)
+ handler.execute(command)
assert error.value.message == MOVIE_DOES_NOT_EXIST
diff --git a/tests/unit/application/command_handlers/test_rate_movie.py b/tests/unit/application/command_handlers/test_rate_movie.py
index e6b35f2..c7cb76f 100644
--- a/tests/unit/application/command_handlers/test_rate_movie.py
+++ b/tests/unit/application/command_handlers/test_rate_movie.py
@@ -68,11 +68,11 @@ def test_rate_movie(
return_value=user.id,
)
- rate_movie_command = RateMovieCommand(
+ command = RateMovieCommand(
movie_id=movie.id,
rating=9,
)
- rate_movie_handler = RateMovieHandler(
+ handler = RateMovieHandler(
access_concern=AccessConcern(),
rate_movie=RateMovie(),
permissions_gateway=permissions_gateway,
@@ -83,7 +83,7 @@ def test_rate_movie(
identity_provider=identity_provider_with_correct_permissions,
)
- rate_movie_handler.execute(rate_movie_command)
+ handler.execute(command)
def test_rate_movie_should_raise_error_when_access_is_denied(
@@ -94,11 +94,11 @@ def test_rate_movie_should_raise_error_when_access_is_denied(
unit_of_work: UnitOfWork,
identity_provider_with_incorrect_permissions: IdentityProvider,
):
- rate_movie_command = RateMovieCommand(
+ command = RateMovieCommand(
movie_id=MovieId(uuid7()),
rating=9,
)
- rate_movie_handler = RateMovieHandler(
+ handler = RateMovieHandler(
access_concern=AccessConcern(),
rate_movie=RateMovie(),
permissions_gateway=permissions_gateway,
@@ -110,7 +110,7 @@ def test_rate_movie_should_raise_error_when_access_is_denied(
)
with pytest.raises(ApplicationError) as error:
- rate_movie_handler.execute(rate_movie_command)
+ handler.execute(command)
assert error.value.message == RATE_MOVIE_ACCESS_DENIED
@@ -123,11 +123,11 @@ def test_rate_movie_should_raise_error_when_movie_does_not_exist(
unit_of_work: UnitOfWork,
identity_provider_with_correct_permissions: IdentityProvider,
):
- rate_movie_command = RateMovieCommand(
+ command = RateMovieCommand(
movie_id=MovieId(uuid7()),
rating=9,
)
- rate_movie_handler = RateMovieHandler(
+ handler = RateMovieHandler(
access_concern=AccessConcern(),
rate_movie=RateMovie(),
permissions_gateway=permissions_gateway,
@@ -139,7 +139,7 @@ def test_rate_movie_should_raise_error_when_movie_does_not_exist(
)
with pytest.raises(ApplicationError) as error:
- rate_movie_handler.execute(rate_movie_command)
+ handler.execute(command)
assert error.value.message == MOVIE_DOES_NOT_EXIST
@@ -182,11 +182,11 @@ def test_rate_movie_should_raise_error_when_movie_already_rated(
return_value=user.id,
)
- rate_movie_command = RateMovieCommand(
+ command = RateMovieCommand(
movie_id=movie.id,
rating=9,
)
- rate_movie_handler = RateMovieHandler(
+ handler = RateMovieHandler(
access_concern=AccessConcern(),
rate_movie=RateMovie(),
permissions_gateway=permissions_gateway,
@@ -198,7 +198,7 @@ def test_rate_movie_should_raise_error_when_movie_already_rated(
)
with pytest.raises(ApplicationError) as error:
- rate_movie_handler.execute(rate_movie_command)
+ handler.execute(command)
assert error.value.message == MOVIE_ALREADY_RATED
@@ -246,11 +246,11 @@ def test_rate_movie_should_raise_error_when_rating_is_invalid(
return_value=user.id,
)
- rate_movie_command = RateMovieCommand(
+ command = RateMovieCommand(
movie_id=movie.id,
rating=rating_value,
)
- rate_movie_handler = RateMovieHandler(
+ handler = RateMovieHandler(
access_concern=AccessConcern(),
rate_movie=RateMovie(),
permissions_gateway=permissions_gateway,
@@ -262,6 +262,6 @@ def test_rate_movie_should_raise_error_when_rating_is_invalid(
)
with pytest.raises(DomainError) as error:
- rate_movie_handler.execute(rate_movie_command)
+ handler.execute(command)
assert error.value.message == INVALID_RATING_VALUE
diff --git a/tests/unit/application/command_handlers/test_register_user.py b/tests/unit/application/command_handlers/test_register_user.py
index 7c3dda9..281c5b4 100644
--- a/tests/unit/application/command_handlers/test_register_user.py
+++ b/tests/unit/application/command_handlers/test_register_user.py
@@ -21,11 +21,11 @@ def test_register_user(
unit_of_work: UnitOfWork,
password_manager: PasswordManager,
):
- register_user_command = RegisterUserCommand(
+ command = RegisterUserCommand(
name="John Doe",
password="Secret",
)
- register_user_handler = RegisterUserHandler(
+ handler = RegisterUserHandler(
create_user=CreateUser(),
user_gateway=user_gateway,
permissions_gateway=permissions_gateway,
@@ -33,7 +33,7 @@ def test_register_user(
password_manager=password_manager,
)
- register_user_handler.execute(register_user_command)
+ handler.execute(command)
def test_create_user_should_raise_error_when_user_name_already_exists(
@@ -51,11 +51,11 @@ def test_create_user_should_raise_error_when_user_name_already_exists(
user_gateway.save(user)
unit_of_work.commit()
- register_user_command = RegisterUserCommand(
+ command = RegisterUserCommand(
name=user_name,
password="Secret",
)
- register_user_handler = RegisterUserHandler(
+ handler = RegisterUserHandler(
create_user=CreateUser(),
user_gateway=user_gateway,
permissions_gateway=permissions_gateway,
@@ -64,6 +64,6 @@ def test_create_user_should_raise_error_when_user_name_already_exists(
)
with pytest.raises(ApplicationError) as error:
- register_user_handler.execute(register_user_command)
+ handler.execute(command)
assert error.value.message == USER_NAME_ALREADY_EXISTS
diff --git a/tests/unit/application/command_handlers/test_review_movie.py b/tests/unit/application/command_handlers/test_review_movie.py
index ecccbc0..54b47d7 100644
--- a/tests/unit/application/command_handlers/test_review_movie.py
+++ b/tests/unit/application/command_handlers/test_review_movie.py
@@ -66,13 +66,13 @@ def test_review_movie(
return_value=user.id,
)
- review_movie_command = ReviewMovieCommand(
+ command = ReviewMovieCommand(
movie_id=movie.id,
title="Not bad",
content="Great soundtrack",
type=ReviewType.POSITIVE,
)
- review_movie_handler = ReviewMovieHandler(
+ handler = ReviewMovieHandler(
access_concern=AccessConcern(),
review_movie=ReviewMovie(),
permissions_gateway=permissions_gateway,
@@ -83,7 +83,7 @@ def test_review_movie(
identity_provider=identity_provider_with_correct_permissions,
)
- review_movie_handler.execute(review_movie_command)
+ handler.execute(command)
def test_review_movie_should_raise_error_when_access_is_denied(
@@ -94,13 +94,13 @@ def test_review_movie_should_raise_error_when_access_is_denied(
unit_of_work: UnitOfWork,
identity_provider_with_incorrect_permissions: IdentityProvider,
):
- review_movie_command = ReviewMovieCommand(
+ command = ReviewMovieCommand(
movie_id=MovieId(uuid7()),
title="Mid",
content="So-so",
type=ReviewType.NEUTRAL,
)
- review_movie_handler = ReviewMovieHandler(
+ handler = ReviewMovieHandler(
access_concern=AccessConcern(),
review_movie=ReviewMovie(),
permissions_gateway=permissions_gateway,
@@ -112,7 +112,7 @@ def test_review_movie_should_raise_error_when_access_is_denied(
)
with pytest.raises(ApplicationError) as error:
- review_movie_handler.execute(review_movie_command)
+ handler.execute(command)
assert error.value.message == REVIEW_MOVIE_ACCESS_DENIED
@@ -125,13 +125,13 @@ def test_review_movie_should_raise_error_when_movie_does_not_exist(
unit_of_work: UnitOfWork,
identity_provider_with_correct_permissions: IdentityProvider,
):
- review_movie_command = ReviewMovieCommand(
+ command = ReviewMovieCommand(
movie_id=MovieId(uuid7()),
title="Fantastic",
content="Awesome!!",
type=ReviewType.POSITIVE,
)
- review_movie_handler = ReviewMovieHandler(
+ handler = ReviewMovieHandler(
access_concern=AccessConcern(),
review_movie=ReviewMovie(),
permissions_gateway=permissions_gateway,
@@ -143,7 +143,7 @@ def test_review_movie_should_raise_error_when_movie_does_not_exist(
)
with pytest.raises(ApplicationError) as error:
- review_movie_handler.execute(review_movie_command)
+ handler.execute(command)
assert error.value.message == MOVIE_DOES_NOT_EXIST
@@ -188,13 +188,13 @@ def test_review_movie_should_raise_error_when_movie_already_reviewed(
return_value=user.id,
)
- review_movie_command = ReviewMovieCommand(
+ command = ReviewMovieCommand(
movie_id=movie.id,
title="Masterpice",
content="Extremely underrated",
type=ReviewType.POSITIVE,
)
- review_movie_handler = ReviewMovieHandler(
+ handler = ReviewMovieHandler(
access_concern=AccessConcern(),
review_movie=ReviewMovie(),
permissions_gateway=permissions_gateway,
@@ -206,6 +206,6 @@ def test_review_movie_should_raise_error_when_movie_already_reviewed(
)
with pytest.raises(ApplicationError) as error:
- review_movie_handler.execute(review_movie_command)
+ handler.execute(command)
assert error.value.message == MOVIE_ALREADY_REVIEWED
diff --git a/tests/unit/application/command_handlers/test_unrate_movie.py b/tests/unit/application/command_handlers/test_unrate_movie.py
index bc5d2b1..34385f9 100644
--- a/tests/unit/application/command_handlers/test_unrate_movie.py
+++ b/tests/unit/application/command_handlers/test_unrate_movie.py
@@ -75,10 +75,10 @@ def test_unrate_movie(
return_value=user.id,
)
- unrate_movie_command = UnrateMovieCommand(
+ command = UnrateMovieCommand(
rating_id=rating.id,
)
- unrate_movie_handler = UnrateMovieHandler(
+ handler = UnrateMovieHandler(
access_concern=AccessConcern(),
unrate_movie=UnrateMovie(),
permissions_gateway=permissions_gateway,
@@ -88,7 +88,7 @@ def test_unrate_movie(
identity_provider=identity_provider_with_correct_permissions,
)
- unrate_movie_handler.execute(unrate_movie_command)
+ handler.execute(command)
def test_unrate_movie_should_raise_error_when_access_is_denied(
@@ -98,10 +98,10 @@ def test_unrate_movie_should_raise_error_when_access_is_denied(
unit_of_work: UnitOfWork,
identity_provider_with_incorrect_permissions: IdentityProvider,
):
- unrate_movie_command = UnrateMovieCommand(
+ command = UnrateMovieCommand(
rating_id=RatingId(uuid7()),
)
- unrate_movie_handler = UnrateMovieHandler(
+ handler = UnrateMovieHandler(
access_concern=AccessConcern(),
unrate_movie=UnrateMovie(),
permissions_gateway=permissions_gateway,
@@ -112,7 +112,7 @@ def test_unrate_movie_should_raise_error_when_access_is_denied(
)
with pytest.raises(ApplicationError) as error:
- unrate_movie_handler.execute(unrate_movie_command)
+ handler.execute(command)
assert error.value.message == UNRATE_MOVIE_ACCESS_DENIED
@@ -124,10 +124,10 @@ def test_unrate_movie_should_raise_error_when_rating_does_not_exist(
unit_of_work: UnitOfWork,
identity_provider_with_correct_permissions: IdentityProvider,
):
- unrate_movie_command = UnrateMovieCommand(
+ command = UnrateMovieCommand(
rating_id=RatingId(uuid7()),
)
- unrate_movie_handler = UnrateMovieHandler(
+ handler = UnrateMovieHandler(
access_concern=AccessConcern(),
unrate_movie=UnrateMovie(),
permissions_gateway=permissions_gateway,
@@ -138,7 +138,7 @@ def test_unrate_movie_should_raise_error_when_rating_does_not_exist(
)
with pytest.raises(ApplicationError) as error:
- unrate_movie_handler.execute(unrate_movie_command)
+ handler.execute(command)
assert error.value.message == RATING_DOES_NOT_EXIST
@@ -181,10 +181,10 @@ def test_unrate_movie_should_raise_error_when_user_is_not_rating_owner(
return_value=UserId(uuid7()),
)
- unrate_movie_command = UnrateMovieCommand(
+ command = UnrateMovieCommand(
rating_id=rating.id,
)
- unrate_movie_handler = UnrateMovieHandler(
+ handler = UnrateMovieHandler(
access_concern=AccessConcern(),
unrate_movie=UnrateMovie(),
permissions_gateway=permissions_gateway,
@@ -195,6 +195,6 @@ def test_unrate_movie_should_raise_error_when_user_is_not_rating_owner(
)
with pytest.raises(ApplicationError) as error:
- unrate_movie_handler.execute(unrate_movie_command)
+ handler.execute(command)
assert error.value.message == USER_IS_NOT_OWNER
diff --git a/tests/unit/application/query_handlers/test_detailed_movie.py b/tests/unit/application/query_handlers/test_detailed_movie.py
index c86e5fd..43af222 100644
--- a/tests/unit/application/query_handlers/test_detailed_movie.py
+++ b/tests/unit/application/query_handlers/test_detailed_movie.py
@@ -80,10 +80,10 @@ def test_get_detailed_movie(
return_value=user.id,
)
- get_detailed_movie_query = GetDetailedMovieQuery(
+ query = GetDetailedMovieQuery(
movie_id=movie.id,
)
- get_detailed_movie_handler = GetDetailedMovieHandler(
+ handler = GetDetailedMovieHandler(
detailed_movie_reader=detailed_movie_reader,
identity_provider=identity_provider,
)
@@ -107,7 +107,7 @@ def test_get_detailed_movie(
created_at=review.created_at,
),
)
- result = get_detailed_movie_handler.execute(get_detailed_movie_query)
+ result = handler.execute(query)
assert expected_result == result
@@ -120,15 +120,15 @@ def test_get_detailed_movie_should_raise_error_when_movie_does_not_exist(
return_value=None,
)
- get_detailed_movie_query = GetDetailedMovieQuery(
+ query = GetDetailedMovieQuery(
movie_id=MovieId(uuid7()),
)
- get_detailed_movie_handler = GetDetailedMovieHandler(
+ handler = GetDetailedMovieHandler(
detailed_movie_reader=detailed_movie_reader,
identity_provider=identity_provider,
)
with pytest.raises(ApplicationError) as error:
- get_detailed_movie_handler.execute(get_detailed_movie_query)
+ handler.execute(query)
assert error.value.message == MOVIE_DOES_NOT_EXIST
diff --git a/tests/unit/application/query_handlers/test_get_detailed_reviews.py b/tests/unit/application/query_handlers/test_get_detailed_reviews.py
index b879fc8..f4e4511 100644
--- a/tests/unit/application/query_handlers/test_get_detailed_reviews.py
+++ b/tests/unit/application/query_handlers/test_get_detailed_reviews.py
@@ -73,12 +73,12 @@ def test_get_detailed_reviews(
unit_of_work.commit()
- get_detailed_reviews_query = GetDetailedReviewsQuery(
+ query = GetDetailedReviewsQuery(
movie_id=movie.id,
limit=10,
offset=0,
)
- get_detailed_reviews_handler = GetDetailedReviewsHandler(
+ handler = GetDetailedReviewsHandler(
movie_gateway=movie_gateway,
detailed_review_reader=detailed_review_reader,
)
@@ -100,7 +100,7 @@ def test_get_detailed_reviews(
),
),
]
- result = get_detailed_reviews_handler.execute(get_detailed_reviews_query)
+ result = handler.execute(query)
assert expected_result == result
@@ -109,17 +109,17 @@ def test_get_detailed_reviews_should_raise_error_when_movie_does_not_exist(
movie_gateway: MovieGateway,
detailed_review_reader: DetailedReviewViewModelReader,
):
- get_detailed_reviews_query = GetDetailedReviewsQuery(
+ query = GetDetailedReviewsQuery(
movie_id=MovieId(uuid7()),
limit=10,
offset=0,
)
- get_detailed_reviews_handler = GetDetailedReviewsHandler(
+ handler = GetDetailedReviewsHandler(
movie_gateway=movie_gateway,
detailed_review_reader=detailed_review_reader,
)
with pytest.raises(ApplicationError) as error:
- get_detailed_reviews_handler.execute(get_detailed_reviews_query)
+ handler.execute(query)
assert error.value.message == MOVIE_DOES_NOT_EXIST
diff --git a/tests/unit/application/query_handlers/test_login.py b/tests/unit/application/query_handlers/test_login.py
index 91cc151..9e6a943 100644
--- a/tests/unit/application/query_handlers/test_login.py
+++ b/tests/unit/application/query_handlers/test_login.py
@@ -101,11 +101,11 @@ def test_login_should_raise_error_when_password_is_incorrect(
unit_of_work.commit()
- login_query = LoginQuery(
+ query = LoginQuery(
name=user.name,
password="invalid_password",
)
- login_handler = LoginHandler(
+ handler = LoginHandler(
access_concern=AccessConcern(),
user_gateway=user_gateway,
permissions_gateway=permissions_gateway,
@@ -113,7 +113,7 @@ def test_login_should_raise_error_when_password_is_incorrect(
)
with pytest.raises(ApplicationError) as error:
- login_handler.execute(login_query)
+ handler.execute(query)
assert error.value.message == INCORRECT_PASSWORD
@@ -145,11 +145,11 @@ def test_login_should_raise_error_when_access_is_denied(
unit_of_work.commit()
- login_query = LoginQuery(
+ query = LoginQuery(
name=user.name,
password=user_password,
)
- login_handler = LoginHandler(
+ handler = LoginHandler(
access_concern=AccessConcern(),
user_gateway=user_gateway,
permissions_gateway=permissions_gateway,
@@ -157,6 +157,6 @@ def test_login_should_raise_error_when_access_is_denied(
)
with pytest.raises(ApplicationError) as error:
- login_handler.execute(login_query)
+ handler.execute(query)
assert error.value.message == LOGIN_ACCESS_DENIED
diff --git a/tests/unit/application/query_handlers/test_non_detailed_movies.py b/tests/unit/application/query_handlers/test_non_detailed_movies.py
index 633e9d7..d92fea9 100644
--- a/tests/unit/application/query_handlers/test_non_detailed_movies.py
+++ b/tests/unit/application/query_handlers/test_non_detailed_movies.py
@@ -64,11 +64,11 @@ def test_get_non_detailed_movies(
return_value=user.id,
)
- get_non_detailed_movies_query = GetNonDetailedMoviesQuery(
+ query = GetNonDetailedMoviesQuery(
limit=10,
offset=0,
)
- get_non_detailed_movies_handler = GetNonDetailedMoviesHandler(
+ handler = GetNonDetailedMoviesHandler(
non_detailed_movie_reader=non_detailed_movie_reader,
identity_provider=identity_provider,
)
@@ -85,8 +85,6 @@ def test_get_non_detailed_movies(
),
),
]
- result = get_non_detailed_movies_handler.execute(
- get_non_detailed_movies_query,
- )
+ result = handler.execute(query)
assert expected_result == result
From ad0f7c1bbf456515f988984d3d6d05a22a68e53a Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Mon, 26 Feb 2024 18:53:26 +0400
Subject: [PATCH 12/39] Add `GetMyDetailedRatingsQuery`, refactor view model
mappers
---
.../common/readers/detailed_movie.py | 2 +-
.../common/readers/detailed_review.py | 4 +-
.../common/readers/my_detailed_ratings.py | 16 +++
.../common/readers/non_detailed_movie.py | 4 +-
.../common/readers/rating_for_export.py | 14 +++
.../common/view_models/detailed_movie.py | 14 ++-
.../common/view_models/detailed_review.py | 8 +-
.../common/view_models/my_detailed_ratings.py | 30 +++++
.../common/view_models/non_detailed_movie.py | 10 +-
.../common/view_models/rating_for_export.py | 23 ++++
.../queries/my_detailed_ratings.py | 7 ++
.../query_handlers/detailed_movie.py | 6 +-
.../query_handlers/detailed_reviews.py | 10 +-
.../query_handlers/my_detailed_ratings.py | 35 ++++++
.../query_handlers/non_detailed_movies.py | 10 +-
.../mappers/view_models/detailed_movie.py | 104 +++++++----------
.../mappers/view_models/detailed_review.py | 85 +++++---------
.../view_models/my_detailed_ratings.py | 106 ++++++++++++++++++
.../mappers/view_models/non_detailed_movie.py | 73 +++++-------
.../mappers/view_models/rating_for_export.py | 61 ++++++++++
src/amdb/main/providers.py | 52 +++++++--
.../web_api/ratings/get_my_detailed.py | 57 ++++++++++
.../presentation/web_api/ratings/router.py | 6 +
tests/unit/application/conftest.py | 24 ++--
.../query_handlers/test_detailed_movie.py | 21 ++--
...ed_reviews.py => test_detailed_reviews.py} | 18 +--
.../test_my_detailed_ratings.py | 99 ++++++++++++++++
.../test_non_detailed_movies.py | 21 ++--
28 files changed, 680 insertions(+), 240 deletions(-)
create mode 100644 src/amdb/application/common/readers/my_detailed_ratings.py
create mode 100644 src/amdb/application/common/readers/rating_for_export.py
create mode 100644 src/amdb/application/common/view_models/my_detailed_ratings.py
create mode 100644 src/amdb/application/common/view_models/rating_for_export.py
create mode 100644 src/amdb/application/queries/my_detailed_ratings.py
create mode 100644 src/amdb/application/query_handlers/my_detailed_ratings.py
create mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/my_detailed_ratings.py
create mode 100644 src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/rating_for_export.py
create mode 100644 src/amdb/presentation/web_api/ratings/get_my_detailed.py
rename tests/unit/application/query_handlers/{test_get_detailed_reviews.py => test_detailed_reviews.py} (89%)
create mode 100644 tests/unit/application/query_handlers/test_my_detailed_ratings.py
diff --git a/src/amdb/application/common/readers/detailed_movie.py b/src/amdb/application/common/readers/detailed_movie.py
index 9cff8e4..10b3684 100644
--- a/src/amdb/application/common/readers/detailed_movie.py
+++ b/src/amdb/application/common/readers/detailed_movie.py
@@ -8,7 +8,7 @@
class DetailedMovieViewModelReader(Protocol):
- def one(
+ def get(
self,
movie_id: MovieId,
current_user_id: Optional[UserId],
diff --git a/src/amdb/application/common/readers/detailed_review.py b/src/amdb/application/common/readers/detailed_review.py
index 2cdf4a4..df752a7 100644
--- a/src/amdb/application/common/readers/detailed_review.py
+++ b/src/amdb/application/common/readers/detailed_review.py
@@ -6,8 +6,8 @@
)
-class DetailedReviewViewModelReader(Protocol):
- def list(
+class DetailedReviewViewModelsReader(Protocol):
+ def get(
self,
movie_id: MovieId,
limit: int,
diff --git a/src/amdb/application/common/readers/my_detailed_ratings.py b/src/amdb/application/common/readers/my_detailed_ratings.py
new file mode 100644
index 0000000..3291691
--- /dev/null
+++ b/src/amdb/application/common/readers/my_detailed_ratings.py
@@ -0,0 +1,16 @@
+from typing import Protocol
+
+from amdb.domain.entities.user import UserId
+from amdb.application.common.view_models.my_detailed_ratings import (
+ MyDetailedRatingsViewModel,
+)
+
+
+class MyDetailedRatingsViewModelReader(Protocol):
+ def get(
+ self,
+ current_user_id: UserId,
+ limit: int,
+ offset: int,
+ ) -> MyDetailedRatingsViewModel:
+ raise NotImplementedError
diff --git a/src/amdb/application/common/readers/non_detailed_movie.py b/src/amdb/application/common/readers/non_detailed_movie.py
index f799f3a..f1e7202 100644
--- a/src/amdb/application/common/readers/non_detailed_movie.py
+++ b/src/amdb/application/common/readers/non_detailed_movie.py
@@ -6,8 +6,8 @@
)
-class NonDetailedMovieViewModelReader(Protocol):
- def list(
+class NonDetailedMovieViewModelsReader(Protocol):
+ def get(
self,
current_user_id: Optional[UserId],
limit: int,
diff --git a/src/amdb/application/common/readers/rating_for_export.py b/src/amdb/application/common/readers/rating_for_export.py
new file mode 100644
index 0000000..6fa6130
--- /dev/null
+++ b/src/amdb/application/common/readers/rating_for_export.py
@@ -0,0 +1,14 @@
+from typing import Protocol
+
+from amdb.domain.entities.user import UserId
+from amdb.application.common.view_models.rating_for_export import (
+ RatingForExportViewModel,
+)
+
+
+class RatingForExportViewModelsReader(Protocol):
+ def get(
+ self,
+ current_user_id: UserId,
+ ) -> list[RatingForExportViewModel]:
+ raise NotImplementedError
diff --git a/src/amdb/application/common/view_models/detailed_movie.py b/src/amdb/application/common/view_models/detailed_movie.py
index 61b86e5..ef58bbd 100644
--- a/src/amdb/application/common/view_models/detailed_movie.py
+++ b/src/amdb/application/common/view_models/detailed_movie.py
@@ -8,13 +8,13 @@
from amdb.domain.entities.review import ReviewId, ReviewType
-class UserRating(TypedDict):
+class UserRatingViewModel(TypedDict):
id: RatingId
value: float
created_at: datetime
-class UserReview(TypedDict):
+class UserReviewViewModel(TypedDict):
id: ReviewId
title: str
content: str
@@ -22,11 +22,15 @@ class UserReview(TypedDict):
created_at: datetime
-class DetailedMovieViewModel(TypedDict):
+class MovieViewModel(TypedDict):
id: MovieId
title: str
release_date: date
rating: float
rating_count: int
- user_rating: Optional[UserRating]
- user_review: Optional[UserReview]
+
+
+class DetailedMovieViewModel(TypedDict):
+ movie: MovieViewModel
+ user_rating: Optional[UserRatingViewModel]
+ user_review: Optional[UserReviewViewModel]
diff --git a/src/amdb/application/common/view_models/detailed_review.py b/src/amdb/application/common/view_models/detailed_review.py
index da2f376..f97d2e5 100644
--- a/src/amdb/application/common/view_models/detailed_review.py
+++ b/src/amdb/application/common/view_models/detailed_review.py
@@ -8,13 +8,13 @@
from amdb.domain.entities.review import ReviewId, ReviewType
-class UserRating(TypedDict):
+class RatingViewModel(TypedDict):
id: RatingId
value: float
created_at: datetime
-class UserReview(TypedDict):
+class ReviewViewModel(TypedDict):
id: ReviewId
title: str
content: str
@@ -24,5 +24,5 @@ class UserReview(TypedDict):
class DetailedReviewViewModel(TypedDict):
user_id: UserId
- user_review: UserReview
- user_rating: Optional[UserRating]
+ review: ReviewViewModel
+ rating: Optional[RatingViewModel]
diff --git a/src/amdb/application/common/view_models/my_detailed_ratings.py b/src/amdb/application/common/view_models/my_detailed_ratings.py
new file mode 100644
index 0000000..f2c6aea
--- /dev/null
+++ b/src/amdb/application/common/view_models/my_detailed_ratings.py
@@ -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.rating import RatingId
+
+
+class MovieViewModel(TypedDict):
+ id: MovieId
+ title: str
+ release_date: date
+ rating: float
+ rating_count: int
+
+
+class RatingViewModel(TypedDict):
+ id: RatingId
+ value: float
+ created_at: datetime
+
+
+class DetailedRatingViewModel(TypedDict):
+ movie: MovieViewModel
+ rating: RatingViewModel
+
+
+class MyDetailedRatingsViewModel(TypedDict):
+ detailed_ratings: list[DetailedRatingViewModel]
+ rating_count: int
diff --git a/src/amdb/application/common/view_models/non_detailed_movie.py b/src/amdb/application/common/view_models/non_detailed_movie.py
index 03b3c09..29db03c 100644
--- a/src/amdb/application/common/view_models/non_detailed_movie.py
+++ b/src/amdb/application/common/view_models/non_detailed_movie.py
@@ -7,14 +7,18 @@
from amdb.domain.entities.rating import RatingId
-class UserRating(TypedDict):
+class UserRatingViewModel(TypedDict):
id: RatingId
value: float
-class NonDetailedMovieViewModel(TypedDict):
+class MovieViewModel(TypedDict):
id: MovieId
title: str
release_date: date
rating: float
- user_rating: Optional[UserRating]
+
+
+class NonDetailedMovieViewModel(TypedDict):
+ movie: MovieViewModel
+ user_rating: Optional[UserRatingViewModel]
diff --git a/src/amdb/application/common/view_models/rating_for_export.py b/src/amdb/application/common/view_models/rating_for_export.py
new file mode 100644
index 0000000..a138d35
--- /dev/null
+++ b/src/amdb/application/common/view_models/rating_for_export.py
@@ -0,0 +1,23 @@
+from datetime import date, datetime
+
+from typing_extensions import TypedDict
+
+from amdb.domain.entities.movie import MovieId
+
+
+class MovieViewModel(TypedDict):
+ id: MovieId
+ title: str
+ release_date: date
+ rating: float
+ rating_count: int
+
+
+class RatingViewModel(TypedDict):
+ value: float
+ created_at: datetime
+
+
+class RatingForExportViewModel(TypedDict):
+ movie: MovieViewModel
+ rating: RatingViewModel
diff --git a/src/amdb/application/queries/my_detailed_ratings.py b/src/amdb/application/queries/my_detailed_ratings.py
new file mode 100644
index 0000000..bf34d83
--- /dev/null
+++ b/src/amdb/application/queries/my_detailed_ratings.py
@@ -0,0 +1,7 @@
+from dataclasses import dataclass
+
+
+@dataclass(frozen=True, slots=True)
+class GetMyDetailedRatingsQuery:
+ limit: int
+ offset: int
diff --git a/src/amdb/application/query_handlers/detailed_movie.py b/src/amdb/application/query_handlers/detailed_movie.py
index fc139ca..3ee5b05 100644
--- a/src/amdb/application/query_handlers/detailed_movie.py
+++ b/src/amdb/application/query_handlers/detailed_movie.py
@@ -23,11 +23,11 @@ def __init__(
def execute(self, query: GetDetailedMovieQuery) -> DetailedMovieViewModel:
current_user_id = self._identity_provider.user_id_or_none()
- detailed_movie_view_model = self._detailed_movie_reader.one(
+ view_model = self._detailed_movie_reader.get(
movie_id=query.movie_id,
current_user_id=current_user_id,
)
- if not detailed_movie_view_model:
+ if not view_model:
raise ApplicationError(MOVIE_DOES_NOT_EXIST)
- return detailed_movie_view_model
+ return view_model
diff --git a/src/amdb/application/query_handlers/detailed_reviews.py b/src/amdb/application/query_handlers/detailed_reviews.py
index c60fa6a..8b86fc0 100644
--- a/src/amdb/application/query_handlers/detailed_reviews.py
+++ b/src/amdb/application/query_handlers/detailed_reviews.py
@@ -1,6 +1,6 @@
from amdb.application.common.gateways.movie import MovieGateway
from amdb.application.common.readers.detailed_review import (
- DetailedReviewViewModelReader,
+ DetailedReviewViewModelsReader,
)
from amdb.application.common.view_models.detailed_review import (
DetailedReviewViewModel,
@@ -15,10 +15,10 @@ def __init__(
self,
*,
movie_gateway: MovieGateway,
- detailed_review_reader: DetailedReviewViewModelReader,
+ detailed_reviews_reader: DetailedReviewViewModelsReader,
) -> None:
self._movie_gateway = movie_gateway
- self._detailed_review_reader = detailed_review_reader
+ self._detailed_reviews_reader = detailed_reviews_reader
def execute(
self,
@@ -28,10 +28,10 @@ def execute(
if not movie:
raise ApplicationError(MOVIE_DOES_NOT_EXIST)
- review_view_models = self._detailed_review_reader.list(
+ view_models = self._detailed_reviews_reader.get(
movie_id=query.movie_id,
limit=query.limit,
offset=query.offset,
)
- return review_view_models
+ return view_models
diff --git a/src/amdb/application/query_handlers/my_detailed_ratings.py b/src/amdb/application/query_handlers/my_detailed_ratings.py
new file mode 100644
index 0000000..5e933e8
--- /dev/null
+++ b/src/amdb/application/query_handlers/my_detailed_ratings.py
@@ -0,0 +1,35 @@
+from amdb.application.common.view_models.my_detailed_ratings import (
+ MyDetailedRatingsViewModel,
+)
+from amdb.application.common.readers.my_detailed_ratings import (
+ MyDetailedRatingsViewModelReader,
+)
+from amdb.application.common.identity_provider import IdentityProvider
+from amdb.application.queries.my_detailed_ratings import (
+ GetMyDetailedRatingsQuery,
+)
+
+
+class GetMyDetailedRatingsQueryHandler:
+ def __init__(
+ self,
+ *,
+ my_detailed_ratings_reader: MyDetailedRatingsViewModelReader,
+ identity_provider: IdentityProvider,
+ ) -> None:
+ self._my_detailed_ratings_reader = my_detailed_ratings_reader
+ self._identity_provider = identity_provider
+
+ def execute(
+ self,
+ query: GetMyDetailedRatingsQuery,
+ ) -> MyDetailedRatingsViewModel:
+ current_user_id = self._identity_provider.user_id()
+
+ view_model = self._my_detailed_ratings_reader.get(
+ current_user_id=current_user_id,
+ limit=query.limit,
+ offset=query.offset,
+ )
+
+ return view_model
diff --git a/src/amdb/application/query_handlers/non_detailed_movies.py b/src/amdb/application/query_handlers/non_detailed_movies.py
index d525d1e..68e236d 100644
--- a/src/amdb/application/query_handlers/non_detailed_movies.py
+++ b/src/amdb/application/query_handlers/non_detailed_movies.py
@@ -1,5 +1,5 @@
from amdb.application.common.readers.non_detailed_movie import (
- NonDetailedMovieViewModelReader,
+ NonDetailedMovieViewModelsReader,
)
from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.common.view_models.non_detailed_movie import (
@@ -14,10 +14,10 @@ class GetNonDetailedMoviesHandler:
def __init__(
self,
*,
- non_detailed_movie_reader: NonDetailedMovieViewModelReader,
+ non_detailed_movies_reader: NonDetailedMovieViewModelsReader,
identity_provider: IdentityProvider,
) -> None:
- self._non_detailed_movie_reader = non_detailed_movie_reader
+ self._non_detailed_movies_reader = non_detailed_movies_reader
self._identity_provider = identity_provider
def execute(
@@ -26,10 +26,10 @@ def execute(
) -> list[NonDetailedMovieViewModel]:
current_user_id = self._identity_provider.user_id_or_none()
- non_detailed_movie_models = self._non_detailed_movie_reader.list(
+ view_models = self._non_detailed_movies_reader.get(
current_user_id=current_user_id,
limit=query.limit,
offset=query.offset,
)
- return non_detailed_movie_models
+ return view_models
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/detailed_movie.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/detailed_movie.py
index de14469..467837a 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/detailed_movie.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/detailed_movie.py
@@ -1,47 +1,24 @@
-__all__ = ("DetailedMovieViewModelMapper",)
+from typing import Optional
-from typing import Optional, TypedDict
-from datetime import date, datetime
-from uuid import UUID
-
-from sqlalchemy import Connection, Row, text
+from sqlalchemy import Connection, text
from amdb.domain.entities.user import UserId
from amdb.domain.entities.movie import MovieId
from amdb.domain.entities.rating import RatingId
from amdb.domain.entities.review import ReviewId, ReviewType
from amdb.application.common.view_models.detailed_movie import (
- UserRating,
- UserReview,
+ MovieViewModel,
+ UserRatingViewModel,
+ UserReviewViewModel,
DetailedMovieViewModel,
)
-class RowAsDict(TypedDict):
- movie_id: UUID
- movie_title: str
- movie_release_date: date
- movie_rating: float
- movie_rating_count: int
- user_rating_id: Optional[UUID]
- user_rating_value: Optional[float]
- user_rating_created_at: Optional[datetime]
- user_review_id: Optional[UUID]
- user_review_title: Optional[str]
- user_review_content: Optional[str]
- user_review_type: Optional[int]
- user_review_created_at: Optional[datetime]
-
- @classmethod # type: ignore
- def from_row(cls, row: Row) -> "RowAsDict":
- return RowAsDict(**row._mapping) # noqa: SLF001
-
-
class DetailedMovieViewModelMapper:
def __init__(self, connection: Connection) -> None:
self._connection = connection
- def one(
+ def get(
self,
movie_id: MovieId,
current_user_id: Optional[UserId],
@@ -79,41 +56,42 @@ def one(
}
row = self._connection.execute(statement, parameters).fetchone()
if row:
- row_as_dict = RowAsDict.from_row(row) # type: ignore
- return self._to_view_model(row_as_dict)
- return None
+ row_as_dict = row._mapping # noqa: SLF001
- def _to_view_model(
- self,
- row_as_dict: RowAsDict,
- ) -> DetailedMovieViewModel:
- if row_as_dict["user_rating_id"]:
- user_rating = UserRating(
- id=RatingId(row_as_dict["user_rating_id"]), # type: ignore
- value=row_as_dict["user_rating_value"], # type: ignore
- created_at=row_as_dict["user_rating_created_at"], # type: ignore
+ 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"],
)
- else:
- user_rating = None
- if row_as_dict["user_review_id"]:
- user_review = UserReview(
- id=ReviewId(row_as_dict["user_review_id"]), # type: ignore
- title=row_as_dict["user_review_title"], # type: ignore
- content=row_as_dict["user_review_content"], # type: ignore
- type=ReviewType(row_as_dict["user_review_type"]), # type: ignore
- created_at=row_as_dict["user_review_created_at"], # type: ignore
- )
- else:
- user_review = None
+ rating_exists = row_as_dict["user_rating_id"] is not None
+ if rating_exists:
+ user_rating = UserRatingViewModel(
+ id=RatingId(row_as_dict["user_rating_id"]),
+ value=row_as_dict["user_rating_value"],
+ created_at=row_as_dict["user_rating_created_at"],
+ )
+ else:
+ user_rating = None
- detailed_movie_view_model = DetailedMovieViewModel(
- 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"],
- user_rating=user_rating,
- user_review=user_review,
- )
- return detailed_movie_view_model
+ review_exists = row_as_dict["user_review_id"] is not None
+ if review_exists:
+ user_review = UserReviewViewModel(
+ id=ReviewId(row_as_dict["user_review_id"]),
+ title=row_as_dict["user_review_title"],
+ content=row_as_dict["user_review_content"],
+ type=ReviewType(row_as_dict["user_review_type"]),
+ created_at=row_as_dict["user_review_created_at"],
+ )
+ else:
+ user_review = None
+
+ view_model = DetailedMovieViewModel(
+ movie=movie,
+ user_rating=user_rating,
+ user_review=user_review,
+ )
+ return view_model
+ return None
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/detailed_review.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/detailed_review.py
index 3145c32..9e5f239 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/detailed_review.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/detailed_review.py
@@ -1,43 +1,21 @@
-__all__ = ("DetailedReviewViewModelMapper",)
-
-from typing import Optional, TypedDict
-from datetime import datetime
-from uuid import UUID
-
-from sqlalchemy import Connection, Row, text
+from sqlalchemy import Connection, text
from amdb.domain.entities.user import UserId
from amdb.domain.entities.movie import MovieId
from amdb.domain.entities.rating import RatingId
from amdb.domain.entities.review import ReviewId, ReviewType
from amdb.application.common.view_models.detailed_review import (
- UserRating,
- UserReview,
+ RatingViewModel,
+ ReviewViewModel,
DetailedReviewViewModel,
)
-class RowAsDict(TypedDict):
- user_id: UUID
- user_review_id: UUID
- user_review_title: str
- user_review_content: str
- user_review_type: int
- user_review_created_at: datetime
- user_rating_id: Optional[UUID]
- user_rating_value: Optional[float]
- user_rating_created_at: Optional[datetime]
-
- @classmethod # type: ignore
- def from_row(cls, row: Row) -> "RowAsDict":
- return RowAsDict(row._mapping) # noqa: SLF001
-
-
-class DetailedReviewViewModelMapper:
+class DetailedReviewViewModelsMapper:
def __init__(self, connection: Connection) -> None:
self._connection = connection
- def list(
+ def get(
self,
movie_id: MovieId,
limit: int,
@@ -72,38 +50,33 @@ def list(
}
rows = self._connection.execute(statement, parameters).fetchall()
- review_view_models = []
+ view_models = []
for row in rows:
- row_as_dict = RowAsDict.from_row(row) # type: ignore
- review_view_model = self._to_view_model(row_as_dict)
- review_view_models.append(review_view_model)
+ row_as_dict = row._mapping # noqa: SLF001
- return review_view_models
+ review = ReviewViewModel(
+ id=ReviewId(row_as_dict["user_review_id"]),
+ title=row_as_dict["user_review_title"],
+ content=row_as_dict["user_review_content"],
+ type=ReviewType(row_as_dict["user_review_type"]),
+ created_at=row_as_dict["user_review_created_at"],
+ )
- def _to_view_model(
- self,
- row_as_dict: RowAsDict,
- ) -> DetailedReviewViewModel:
- user_review = UserReview(
- id=ReviewId(row_as_dict["user_review_id"]),
- title=row_as_dict["user_review_title"],
- content=row_as_dict["user_review_content"],
- type=ReviewType(row_as_dict["user_review_type"]),
- created_at=row_as_dict["user_review_created_at"],
- )
+ rating_exists = row_as_dict["user_rating_id"] is not None
+ if rating_exists:
+ rating = RatingViewModel(
+ id=RatingId(row_as_dict["user_rating_id"]),
+ value=row_as_dict["user_rating_value"],
+ created_at=row_as_dict["user_rating_created_at"],
+ )
+ else:
+ rating = None
- if row_as_dict["user_rating_id"]:
- user_rating = UserRating(
- id=RatingId(row_as_dict["user_rating_id"]), # type: ignore
- value=row_as_dict["user_rating_value"], # type: ignore
- created_at=row_as_dict["user_rating_created_at"], # type: ignore
+ view_model = DetailedReviewViewModel(
+ user_id=UserId(row_as_dict["user_id"]),
+ review=review,
+ rating=rating,
)
- else:
- user_rating = None
+ view_models.append(view_model)
- detailed_review_view_model = DetailedReviewViewModel(
- user_id=UserId(row_as_dict["user_id"]),
- user_review=user_review,
- user_rating=user_rating,
- )
- return detailed_review_view_model
+ return view_models
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/my_detailed_ratings.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/my_detailed_ratings.py
new file mode 100644
index 0000000..7ecb489
--- /dev/null
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/my_detailed_ratings.py
@@ -0,0 +1,106 @@
+from sqlalchemy import Connection, text
+
+from amdb.domain.entities.user import UserId
+from amdb.domain.entities.movie import MovieId
+from amdb.domain.entities.rating import RatingId
+from amdb.application.common.view_models.my_detailed_ratings import (
+ MovieViewModel,
+ RatingViewModel,
+ DetailedRatingViewModel,
+ MyDetailedRatingsViewModel,
+)
+
+
+class MyDetailedRatingsViewModelMapper:
+ def __init__(self, connecion: Connection) -> None:
+ self._connection = connecion
+
+ def get(
+ self,
+ current_user_id: UserId,
+ limit: int,
+ offset: int,
+ ) -> MyDetailedRatingsViewModel:
+ detailed_ratings = self._detailed_ratings(
+ current_user_id=current_user_id,
+ limit=limit,
+ offset=offset,
+ )
+ rating_count = self._rating_count(
+ current_user_id=current_user_id,
+ )
+ view_model = MyDetailedRatingsViewModel(
+ detailed_ratings=detailed_ratings,
+ rating_count=rating_count,
+ )
+ return view_model
+
+ def _detailed_ratings(
+ self,
+ current_user_id: UserId,
+ limit: int,
+ offset: int,
+ ) -> list[DetailedRatingViewModel]:
+ 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,
+ urt.id user_rating_id,
+ urt.value user_rating_value,
+ urt.created_at user_rating_created_at
+ FROM
+ ratings urt
+ LEFT JOIN movies m
+ ON m.id = urt.movie_id
+ WHERE
+ urt.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_ratings = []
+ for row in rows:
+ row_as_dict = row._mapping # noqa: SLF001
+ detailed_rating = DetailedRatingViewModel(
+ 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"],
+ ),
+ rating=RatingViewModel(
+ id=RatingId(row_as_dict["user_rating_id"]),
+ value=row_as_dict["user_rating_value"],
+ created_at=row_as_dict["user_rating_created_at"],
+ ),
+ )
+ detailed_ratings.append(detailed_rating)
+
+ return detailed_ratings
+
+ def _rating_count(self, current_user_id: UserId) -> int:
+ statement = text(
+ """
+ SELECT COUNT(urt.id) FROM ratings urt
+ WHERE urt.user_id = :current_user_id
+ """,
+ )
+ parameters = {
+ "current_user_id": current_user_id,
+ }
+ rating_count = self._connection.execute(
+ statement,
+ parameters,
+ ).scalar_one()
+ return rating_count
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/non_detailed_movie.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/non_detailed_movie.py
index b9b65ce..147fa94 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/non_detailed_movie.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/non_detailed_movie.py
@@ -1,38 +1,22 @@
-__all__ = ("NonDetailedMovieViewModelMapper",)
+from typing import Optional
-from typing import Optional, TypedDict
-from datetime import date
-from uuid import UUID
-
-from sqlalchemy import Connection, Row, text
+from sqlalchemy import Connection, text
from amdb.domain.entities.user import UserId
from amdb.domain.entities.movie import MovieId
from amdb.domain.entities.rating import RatingId
from amdb.application.common.view_models.non_detailed_movie import (
- UserRating,
+ MovieViewModel,
+ UserRatingViewModel,
NonDetailedMovieViewModel,
)
-class RowAsDict(TypedDict):
- movie_id: UUID
- movie_title: str
- movie_release_date: date
- movie_rating: float
- user_rating_id: Optional[UUID]
- user_rating_value: Optional[float]
-
- @classmethod # type: ignore
- def from_row(cls, row: Row) -> "RowAsDict":
- return RowAsDict(**row._mapping) # noqa: SLF001
-
-
-class NonDetailedMovieViewModelMapper:
+class NonDetailedMovieViewModelsMapper:
def __init__(self, connection: Connection) -> None:
self._connection = connection
- def list(
+ def get(
self,
current_user_id: Optional[UserId],
limit: int,
@@ -61,31 +45,30 @@ def list(
}
rows = self._connection.execute(statement, parameters).fetchall()
- non_detailed_view_models = []
+ view_models = []
for row in rows:
- row_as_dict = RowAsDict.from_row(row) # type: ignore
- non_detailed_view_model = self._to_view_model(row_as_dict)
- non_detailed_view_models.append(non_detailed_view_model)
+ row_as_dict = row._mapping # noqa: SLF001
- return non_detailed_view_models
+ 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"],
+ )
- def _to_view_model(
- self,
- row_as_dict: RowAsDict,
- ) -> NonDetailedMovieViewModel:
- if row_as_dict["user_rating_id"]:
- user_rating = UserRating(
- id=RatingId(row_as_dict["user_rating_id"]), # type: ignore
- value=row_as_dict["user_rating_value"], # type: ignore
+ rating_exists = row_as_dict["user_rating_id"] is not None
+ if rating_exists:
+ user_rating = UserRatingViewModel(
+ id=RatingId(row_as_dict["user_rating_id"]),
+ value=row_as_dict["user_rating_value"],
+ )
+ else:
+ user_rating = None
+
+ view_model = NonDetailedMovieViewModel(
+ movie=movie,
+ user_rating=user_rating,
)
- else:
- user_rating = None
+ view_models.append(view_model)
- non_detailed_movie_view_model = NonDetailedMovieViewModel(
- 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"],
- user_rating=user_rating,
- )
- return non_detailed_movie_view_model
+ return view_models
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/rating_for_export.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/rating_for_export.py
new file mode 100644
index 0000000..a9e2f24
--- /dev/null
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/view_models/rating_for_export.py
@@ -0,0 +1,61 @@
+from sqlalchemy import Connection, text
+
+from amdb.domain.entities.user import UserId
+from amdb.application.common.view_models.rating_for_export import (
+ MovieViewModel,
+ RatingViewModel,
+ RatingForExportViewModel,
+)
+
+
+class RatingForExportViewModelMapper:
+ def __init__(self, connection: Connection) -> None:
+ self._connection = connection
+
+ def get(
+ self,
+ current_user_id: UserId,
+ ) -> list[RatingForExportViewModel]:
+ 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,
+ urt.value user_rating_value,
+ urt.created_at user_rating_created_at
+ FROM
+ ratings urt
+ LEFT JOIN movies m
+ ON m.id = urt.movie_id
+ WHERE
+ urt.user_id = :current_user_id
+ """,
+ )
+ parameters = {"current_user_id": current_user_id}
+ rows = self._connection.execute(statement, parameters).fetchall()
+
+ view_models = []
+ for row in rows:
+ row_as_dict = row._mapping # noqa: SLF001
+
+ movie = MovieViewModel(
+ id=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"],
+ )
+ rating = RatingViewModel(
+ value=row_as_dict["user_rating_value"],
+ created_at=row_as_dict["user_rating_created_at"],
+ )
+ view_model = RatingForExportViewModel(
+ movie=movie,
+ rating=rating,
+ )
+ view_models.append(view_model)
+
+ return view_models
diff --git a/src/amdb/main/providers.py b/src/amdb/main/providers.py
index 1f082c2..eaf83e4 100644
--- a/src/amdb/main/providers.py
+++ b/src/amdb/main/providers.py
@@ -20,10 +20,13 @@
DetailedMovieViewModelReader,
)
from amdb.application.common.readers.non_detailed_movie import (
- NonDetailedMovieViewModelReader,
+ NonDetailedMovieViewModelsReader,
)
from amdb.application.common.readers.detailed_review import (
- DetailedReviewViewModelReader,
+ DetailedReviewViewModelsReader,
+)
+from amdb.application.common.readers.my_detailed_ratings import (
+ MyDetailedRatingsViewModelReader,
)
from amdb.application.common.password_manager import PasswordManager
from amdb.application.common.identity_provider import IdentityProvider
@@ -43,6 +46,9 @@
from amdb.application.query_handlers.detailed_reviews import (
GetDetailedReviewsHandler,
)
+from amdb.application.query_handlers.my_detailed_ratings import (
+ GetMyDetailedRatingsQueryHandler,
+)
from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
from amdb.infrastructure.persistence.redis.config import RedisConfig
from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.user import (
@@ -67,10 +73,13 @@
DetailedMovieViewModelMapper,
)
from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.non_detailed_movie import (
- NonDetailedMovieViewModelMapper,
+ NonDetailedMovieViewModelsMapper,
)
from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.detailed_review import (
- DetailedReviewViewModelMapper,
+ DetailedReviewViewModelsMapper,
+)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.my_detailed_ratings import (
+ MyDetailedRatingsViewModelMapper,
)
from amdb.infrastructure.persistence.redis.cache.permissions_mapper import (
PermissionsMapperCacheProvider,
@@ -131,12 +140,16 @@ class AdaptersProvider(Provider):
provides=DetailedMovieViewModelReader,
)
non_detailed_movie_reader = provide(
- source=NonDetailedMovieViewModelMapper,
- provides=NonDetailedMovieViewModelReader,
+ source=NonDetailedMovieViewModelsMapper,
+ provides=NonDetailedMovieViewModelsReader,
)
detailed_review_reader = provide(
- source=DetailedReviewViewModelMapper,
- provides=DetailedReviewViewModelReader,
+ source=DetailedReviewViewModelsMapper,
+ provides=DetailedReviewViewModelsReader,
+ )
+ my_detailed_ratings_reader = provide(
+ source=MyDetailedRatingsViewModelMapper,
+ provides=MyDetailedRatingsViewModelReader,
)
unit_of_work = alias(source=Connection, provides=UnitOfWork)
@@ -243,11 +256,11 @@ def delete_movie_handler(
def get_detailed_reviews_handler(
self,
movie_gateway: MovieGateway,
- detailed_review_reader: DetailedReviewViewModelReader,
+ detailed_reviews_reader: DetailedReviewViewModelsReader,
) -> GetDetailedReviewsHandler:
return GetDetailedReviewsHandler(
movie_gateway=movie_gateway,
- detailed_review_reader=detailed_review_reader,
+ detailed_reviews_reader=detailed_reviews_reader,
)
@@ -272,13 +285,28 @@ def create_handler(
@provide
def get_non_detailed_movies_handler(
self,
- non_detailed_movie_reader: NonDetailedMovieViewModelReader,
+ non_detailed_movies_reader: NonDetailedMovieViewModelsReader,
) -> CreateHandler[GetNonDetailedMoviesHandler]:
def create_handler(
identity_provider: IdentityProvider,
) -> GetNonDetailedMoviesHandler:
return GetNonDetailedMoviesHandler(
- non_detailed_movie_reader=non_detailed_movie_reader,
+ non_detailed_movies_reader=non_detailed_movies_reader,
+ identity_provider=identity_provider,
+ )
+
+ return create_handler
+
+ @provide
+ def get_my_detailed_ratings_handler(
+ self,
+ my_detailed_ratings_reader: MyDetailedRatingsViewModelReader,
+ ) -> CreateHandler[GetMyDetailedRatingsQueryHandler]:
+ def create_handler(
+ identity_provider: IdentityProvider,
+ ) -> GetMyDetailedRatingsQueryHandler:
+ return GetMyDetailedRatingsQueryHandler(
+ my_detailed_ratings_reader=my_detailed_ratings_reader,
identity_provider=identity_provider,
)
diff --git a/src/amdb/presentation/web_api/ratings/get_my_detailed.py b/src/amdb/presentation/web_api/ratings/get_my_detailed.py
new file mode 100644
index 0000000..0b8e4a0
--- /dev/null
+++ b/src/amdb/presentation/web_api/ratings/get_my_detailed.py
@@ -0,0 +1,57 @@
+from typing import Annotated, Optional
+
+from fastapi import Cookie
+from dishka.integrations.fastapi import Depends, inject
+
+from amdb.application.common.view_models.my_detailed_ratings import (
+ MyDetailedRatingsViewModel,
+)
+from amdb.application.queries.my_detailed_ratings import (
+ GetMyDetailedRatingsQuery,
+)
+from amdb.application.query_handlers.my_detailed_ratings import (
+ GetMyDetailedRatingsQueryHandler,
+)
+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
+
+
+HandlerCreator = CreateHandler[GetMyDetailedRatingsQueryHandler]
+
+
+@inject
+async def get_my_detailed_ratings(
+ *,
+ create_handler: Annotated[HandlerCreator, Depends()],
+ session_gateway: Annotated[SessionGateway, Depends()],
+ permissions_gateway: Annotated[PermissionsGateway, Depends()],
+ session_id: Annotated[
+ Optional[str],
+ Cookie(alias=SESSION_ID_COOKIE),
+ ] = None,
+ limit: int = 100,
+ offset: int = 0,
+) -> MyDetailedRatingsViewModel:
+ """
+ Returns current user ratings with movies information and
+ rating count.
+ """
+ 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 = GetMyDetailedRatingsQuery(
+ limit=limit,
+ offset=offset,
+ )
+
+ return handler.execute(query)
diff --git a/src/amdb/presentation/web_api/ratings/router.py b/src/amdb/presentation/web_api/ratings/router.py
index 410a184..4bc30b3 100644
--- a/src/amdb/presentation/web_api/ratings/router.py
+++ b/src/amdb/presentation/web_api/ratings/router.py
@@ -1,5 +1,6 @@
from fastapi import APIRouter
+from .get_my_detailed import get_my_detailed_ratings
from .rate_movie import rate_movie
from .unrate_movie import unrate_movie
@@ -8,6 +9,11 @@
prefix="/ratings",
tags=["ratings"],
)
+ratings_router.add_api_route(
+ path="/me/detailed-ratings",
+ endpoint=get_my_detailed_ratings,
+ methods=["GET"],
+)
ratings_router.add_api_route(
path="",
endpoint=rate_movie,
diff --git a/tests/unit/application/conftest.py b/tests/unit/application/conftest.py
index 5baac92..7601d36 100644
--- a/tests/unit/application/conftest.py
+++ b/tests/unit/application/conftest.py
@@ -25,13 +25,16 @@
PermissionsMapper,
)
from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.non_detailed_movie import (
- NonDetailedMovieViewModelMapper,
+ NonDetailedMovieViewModelsMapper,
)
from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.detailed_movie import (
DetailedMovieViewModelMapper,
)
from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.detailed_review import (
- DetailedReviewViewModelMapper,
+ DetailedReviewViewModelsMapper,
+)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.my_detailed_ratings import (
+ MyDetailedRatingsViewModelMapper,
)
from amdb.infrastructure.persistence.redis.cache.permissions_mapper import (
PermissionsMapperCacheProvider,
@@ -103,17 +106,24 @@ def detailed_movie_reader(
@pytest.fixture
-def non_detailed_movie_reader(
+def non_detailed_movies_reader(
sqlalchemy_connection: Connection,
-) -> NonDetailedMovieViewModelMapper:
- return NonDetailedMovieViewModelMapper(sqlalchemy_connection)
+) -> NonDetailedMovieViewModelsMapper:
+ return NonDetailedMovieViewModelsMapper(sqlalchemy_connection)
@pytest.fixture
-def detailed_review_reader(
+def detailed_reviews_reader(
sqlalchemy_connection: Connection,
) -> DetailedMovieViewModelMapper:
- return DetailedReviewViewModelMapper(sqlalchemy_connection)
+ return DetailedReviewViewModelsMapper(sqlalchemy_connection)
+
+
+@pytest.fixture
+def my_detailed_ratings_reader(
+ sqlalchemy_connection: Connection,
+) -> MyDetailedRatingsViewModelMapper:
+ return MyDetailedRatingsViewModelMapper(sqlalchemy_connection)
@pytest.fixture
diff --git a/tests/unit/application/query_handlers/test_detailed_movie.py b/tests/unit/application/query_handlers/test_detailed_movie.py
index 43af222..b2543b6 100644
--- a/tests/unit/application/query_handlers/test_detailed_movie.py
+++ b/tests/unit/application/query_handlers/test_detailed_movie.py
@@ -18,8 +18,9 @@
)
from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.common.view_models.detailed_movie import (
- UserRating,
- UserReview,
+ MovieViewModel,
+ UserRatingViewModel,
+ UserReviewViewModel,
DetailedMovieViewModel,
)
from amdb.application.queries.detailed_movie import GetDetailedMovieQuery
@@ -89,17 +90,19 @@ def test_get_detailed_movie(
)
expected_result = DetailedMovieViewModel(
- id=movie.id,
- title=movie.title,
- release_date=movie.release_date,
- rating=movie.rating,
- rating_count=movie.rating_count,
- user_rating=UserRating(
+ movie=MovieViewModel(
+ id=movie.id,
+ title=movie.title,
+ release_date=movie.release_date,
+ rating=movie.rating,
+ rating_count=movie.rating_count,
+ ),
+ user_rating=UserRatingViewModel(
id=rating.id,
value=rating.value,
created_at=rating.created_at,
),
- user_review=UserReview(
+ user_review=UserReviewViewModel(
id=review.id,
title=review.title,
content=review.content,
diff --git a/tests/unit/application/query_handlers/test_get_detailed_reviews.py b/tests/unit/application/query_handlers/test_detailed_reviews.py
similarity index 89%
rename from tests/unit/application/query_handlers/test_get_detailed_reviews.py
rename to tests/unit/application/query_handlers/test_detailed_reviews.py
index f4e4511..d7521c9 100644
--- a/tests/unit/application/query_handlers/test_get_detailed_reviews.py
+++ b/tests/unit/application/query_handlers/test_detailed_reviews.py
@@ -13,11 +13,11 @@
from amdb.application.common.gateways.review import ReviewGateway
from amdb.application.common.unit_of_work import UnitOfWork
from amdb.application.common.readers.detailed_review import (
- DetailedReviewViewModelReader,
+ DetailedReviewViewModelsReader,
)
from amdb.application.common.view_models.detailed_review import (
- UserRating,
- UserReview,
+ RatingViewModel,
+ ReviewViewModel,
DetailedReviewViewModel,
)
from amdb.application.queries.detailed_reviews import GetDetailedReviewsQuery
@@ -34,7 +34,7 @@ def test_get_detailed_reviews(
rating_gateway: RatingGateway,
review_gateway: ReviewGateway,
unit_of_work: UnitOfWork,
- detailed_review_reader: DetailedReviewViewModelReader,
+ detailed_reviews_reader: DetailedReviewViewModelsReader,
):
user = User(
id=UserId(uuid7()),
@@ -80,20 +80,20 @@ def test_get_detailed_reviews(
)
handler = GetDetailedReviewsHandler(
movie_gateway=movie_gateway,
- detailed_review_reader=detailed_review_reader,
+ detailed_reviews_reader=detailed_reviews_reader,
)
expected_result = [
DetailedReviewViewModel(
user_id=user.id,
- user_review=UserReview(
+ review=ReviewViewModel(
id=review.id,
title=review.title,
content=review.content,
type=review.type,
created_at=review.created_at,
),
- user_rating=UserRating(
+ rating=RatingViewModel(
id=rating.id,
value=rating.value,
created_at=rating.created_at,
@@ -107,7 +107,7 @@ def test_get_detailed_reviews(
def test_get_detailed_reviews_should_raise_error_when_movie_does_not_exist(
movie_gateway: MovieGateway,
- detailed_review_reader: DetailedReviewViewModelReader,
+ detailed_reviews_reader: DetailedReviewViewModelsReader,
):
query = GetDetailedReviewsQuery(
movie_id=MovieId(uuid7()),
@@ -116,7 +116,7 @@ def test_get_detailed_reviews_should_raise_error_when_movie_does_not_exist(
)
handler = GetDetailedReviewsHandler(
movie_gateway=movie_gateway,
- detailed_review_reader=detailed_review_reader,
+ detailed_reviews_reader=detailed_reviews_reader,
)
with pytest.raises(ApplicationError) as error:
diff --git a/tests/unit/application/query_handlers/test_my_detailed_ratings.py b/tests/unit/application/query_handlers/test_my_detailed_ratings.py
new file mode 100644
index 0000000..7c5e903
--- /dev/null
+++ b/tests/unit/application/query_handlers/test_my_detailed_ratings.py
@@ -0,0 +1,99 @@
+from datetime import date, datetime, timezone
+from unittest.mock import Mock
+
+from uuid_extensions import uuid7
+
+from amdb.domain.entities.user import UserId, User
+from amdb.domain.entities.movie import MovieId, Movie
+from amdb.domain.entities.rating import RatingId, Rating
+from amdb.application.common.gateways.user import UserGateway
+from amdb.application.common.gateways.movie import MovieGateway
+from amdb.application.common.gateways.rating import RatingGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.readers.my_detailed_ratings import (
+ MyDetailedRatingsViewModelReader,
+)
+from amdb.application.common.identity_provider import IdentityProvider
+from amdb.application.common.view_models.my_detailed_ratings import (
+ MovieViewModel,
+ RatingViewModel,
+ DetailedRatingViewModel,
+ MyDetailedRatingsViewModel,
+)
+from amdb.application.queries.my_detailed_ratings import (
+ GetMyDetailedRatingsQuery,
+)
+from amdb.application.query_handlers.my_detailed_ratings import (
+ GetMyDetailedRatingsQueryHandler,
+)
+
+
+def test_get_my_detailed_ratings(
+ user_gateway: UserGateway,
+ movie_gateway: MovieGateway,
+ rating_gateway: RatingGateway,
+ unit_of_work: UnitOfWork,
+ my_detailed_ratings_reader: MyDetailedRatingsViewModelReader,
+):
+ user = User(
+ id=UserId(uuid7()),
+ name="John Doe",
+ )
+ user_gateway.save(user)
+
+ movie = Movie(
+ id=MovieId(uuid7()),
+ title="Matrix",
+ release_date=date(1999, 3, 31),
+ rating=8,
+ rating_count=1,
+ )
+ movie_gateway.save(movie)
+
+ rating = Rating(
+ id=RatingId(uuid7()),
+ movie_id=movie.id,
+ user_id=user.id,
+ value=8,
+ created_at=datetime.now(timezone.utc),
+ )
+ rating_gateway.save(rating)
+
+ unit_of_work.commit()
+
+ identity_provider: IdentityProvider = Mock()
+ identity_provider.user_id = Mock(
+ return_value=user.id,
+ )
+
+ query = GetMyDetailedRatingsQuery(
+ limit=10,
+ offset=0,
+ )
+ handler = GetMyDetailedRatingsQueryHandler(
+ my_detailed_ratings_reader=my_detailed_ratings_reader,
+ identity_provider=identity_provider,
+ )
+
+ expected_result = MyDetailedRatingsViewModel(
+ detailed_ratings=[
+ DetailedRatingViewModel(
+ movie=MovieViewModel(
+ id=movie.id,
+ title=movie.title,
+ release_date=movie.release_date,
+ rating=movie.rating,
+ rating_count=movie.rating_count,
+ ),
+ rating=RatingViewModel(
+ id=rating.id,
+ value=rating.value,
+ created_at=rating.created_at,
+ ),
+ ),
+ ],
+ rating_count=1,
+ )
+ result = handler.execute(query)
+
+ assert expected_result == result
diff --git a/tests/unit/application/query_handlers/test_non_detailed_movies.py b/tests/unit/application/query_handlers/test_non_detailed_movies.py
index d92fea9..2fe5d0a 100644
--- a/tests/unit/application/query_handlers/test_non_detailed_movies.py
+++ b/tests/unit/application/query_handlers/test_non_detailed_movies.py
@@ -11,11 +11,12 @@
from amdb.application.common.gateways.rating import RatingGateway
from amdb.application.common.unit_of_work import UnitOfWork
from amdb.application.common.readers.non_detailed_movie import (
- NonDetailedMovieViewModelReader,
+ NonDetailedMovieViewModelsReader,
)
from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.common.view_models.non_detailed_movie import (
- UserRating,
+ MovieViewModel,
+ UserRatingViewModel,
NonDetailedMovieViewModel,
)
from amdb.application.queries.non_detailed_movies import (
@@ -31,7 +32,7 @@ def test_get_non_detailed_movies(
movie_gateway: MovieGateway,
rating_gateway: RatingGateway,
unit_of_work: UnitOfWork,
- non_detailed_movie_reader: NonDetailedMovieViewModelReader,
+ non_detailed_movies_reader: NonDetailedMovieViewModelsReader,
):
user = User(
id=UserId(uuid7()),
@@ -69,17 +70,19 @@ def test_get_non_detailed_movies(
offset=0,
)
handler = GetNonDetailedMoviesHandler(
- non_detailed_movie_reader=non_detailed_movie_reader,
+ non_detailed_movies_reader=non_detailed_movies_reader,
identity_provider=identity_provider,
)
expected_result = [
NonDetailedMovieViewModel(
- id=movie.id,
- title=movie.title,
- release_date=movie.release_date,
- rating=movie.rating,
- user_rating=UserRating(
+ movie=MovieViewModel(
+ id=movie.id,
+ title=movie.title,
+ release_date=movie.release_date,
+ rating=movie.rating,
+ ),
+ user_rating=UserRatingViewModel(
id=rating.id,
value=rating.value,
),
From c7edbf7449559a1677a18aa667b3c43c7dacc645 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Tue, 27 Feb 2024 19:27:57 +0400
Subject: [PATCH 13/39] Add email attribute to user entity
---
.../command_handlers/register_user.py | 10 ++++++++++
src/amdb/application/commands/register_user.py | 2 ++
.../application/common/constants/exceptions.py | 1 +
src/amdb/application/common/gateways/user.py | 3 +++
.../query_handlers/my_detailed_ratings.py | 2 +-
src/amdb/domain/constants/exceptions.py | 1 +
src/amdb/domain/entities/user.py | 3 ++-
src/amdb/domain/services/create_user.py | 16 ++++++++++++++++
src/amdb/domain/validators/__init__.py | 0
src/amdb/domain/validators/email.py | 14 ++++++++++++++
.../alembic/migrations/versions/65f8840f4494_.py | 6 +++---
.../alembic/migrations/versions/a2f7c2383ba8_.py | 8 +++++++-
.../sqlalchemy/mappers/entities/user.py | 9 +++++++++
.../persistence/sqlalchemy/models/user.py | 4 ++++
src/amdb/main/providers.py | 11 ++++++-----
15 files changed, 79 insertions(+), 11 deletions(-)
create mode 100644 src/amdb/domain/validators/__init__.py
create mode 100644 src/amdb/domain/validators/email.py
diff --git a/src/amdb/application/command_handlers/register_user.py b/src/amdb/application/command_handlers/register_user.py
index bfc0f6c..29363f4 100644
--- a/src/amdb/application/command_handlers/register_user.py
+++ b/src/amdb/application/command_handlers/register_user.py
@@ -10,6 +10,7 @@
from amdb.application.common.password_manager import PasswordManager
from amdb.application.common.constants.exceptions import (
USER_NAME_ALREADY_EXISTS,
+ USER_EMAIL_ALREADY_EXISTS,
)
from amdb.application.common.exception import ApplicationError
from amdb.application.commands.register_user import RegisterUserCommand
@@ -36,9 +37,13 @@ def execute(self, command: RegisterUserCommand) -> UserId:
if user:
raise ApplicationError(USER_NAME_ALREADY_EXISTS)
+ if command.email:
+ self._ensure_email_is_not_taken(command.email)
+
new_user = self._create_user(
id=UserId(uuid7()),
name=command.name,
+ email=command.email,
)
self._user_gateway.save(new_user)
@@ -54,3 +59,8 @@ def execute(self, command: RegisterUserCommand) -> UserId:
self._unit_of_work.commit()
return new_user.id
+
+ def _ensure_email_is_not_taken(self, email: str) -> None:
+ user = self._user_gateway.with_email(email)
+ if user:
+ raise ApplicationError(USER_EMAIL_ALREADY_EXISTS)
diff --git a/src/amdb/application/commands/register_user.py b/src/amdb/application/commands/register_user.py
index 54f0fba..bb15bef 100644
--- a/src/amdb/application/commands/register_user.py
+++ b/src/amdb/application/commands/register_user.py
@@ -1,7 +1,9 @@
from dataclasses import dataclass
+from typing import Optional
@dataclass(frozen=True, slots=True)
class RegisterUserCommand:
name: str
+ email: Optional[str]
password: str
diff --git a/src/amdb/application/common/constants/exceptions.py b/src/amdb/application/common/constants/exceptions.py
index dbd3147..c788bd8 100644
--- a/src/amdb/application/common/constants/exceptions.py
+++ b/src/amdb/application/common/constants/exceptions.py
@@ -6,6 +6,7 @@
USER_IS_NOT_OWNER = "User is not an owner"
USER_NAME_ALREADY_EXISTS = "User name already exists"
+USER_EMAIL_ALREADY_EXISTS = "User email already exists"
USER_DOES_NOT_EXIST = "User doesn't exist"
INCORRECT_PASSWORD = "Password is not correct"
diff --git a/src/amdb/application/common/gateways/user.py b/src/amdb/application/common/gateways/user.py
index 63aa44c..a0eefc6 100644
--- a/src/amdb/application/common/gateways/user.py
+++ b/src/amdb/application/common/gateways/user.py
@@ -10,5 +10,8 @@ def with_id(self, user_id: UserId) -> Optional[User]:
def with_name(self, user_name: str) -> Optional[User]:
raise NotImplementedError
+ def with_email(self, user_email: str) -> Optional[User]:
+ raise NotImplementedError
+
def save(self, user: User) -> None:
raise NotImplementedError
diff --git a/src/amdb/application/query_handlers/my_detailed_ratings.py b/src/amdb/application/query_handlers/my_detailed_ratings.py
index 5e933e8..9fd8e04 100644
--- a/src/amdb/application/query_handlers/my_detailed_ratings.py
+++ b/src/amdb/application/query_handlers/my_detailed_ratings.py
@@ -10,7 +10,7 @@
)
-class GetMyDetailedRatingsQueryHandler:
+class GetMyDetailedRatingsHandler:
def __init__(
self,
*,
diff --git a/src/amdb/domain/constants/exceptions.py b/src/amdb/domain/constants/exceptions.py
index dff4fd1..64f1a31 100644
--- a/src/amdb/domain/constants/exceptions.py
+++ b/src/amdb/domain/constants/exceptions.py
@@ -1,3 +1,4 @@
INVALID_RATING_VALUE = (
"Rating value must be from 0 to 10 and be a multiple of 0.5"
)
+INVALID_EMAIL = "Email is invalid"
diff --git a/src/amdb/domain/entities/user.py b/src/amdb/domain/entities/user.py
index 77e3a2b..46eb3b6 100644
--- a/src/amdb/domain/entities/user.py
+++ b/src/amdb/domain/entities/user.py
@@ -1,5 +1,5 @@
from dataclasses import dataclass
-from typing import NewType
+from typing import NewType, Optional
from uuid import UUID
@@ -10,3 +10,4 @@
class User:
id: UserId
name: str
+ email: Optional[str]
diff --git a/src/amdb/domain/services/create_user.py b/src/amdb/domain/services/create_user.py
index 6830cbd..86d942d 100644
--- a/src/amdb/domain/services/create_user.py
+++ b/src/amdb/domain/services/create_user.py
@@ -1,14 +1,30 @@
+from typing import Optional
+
from amdb.domain.entities.user import UserId, User
+from amdb.domain.validators.email import ValidateEmail
class CreateUser:
+ def __init__(
+ self,
+ validate_email: ValidateEmail,
+ ) -> None:
+ self._validate_email = validate_email
+
def __call__(
self,
*,
id: UserId,
name: str,
+ email: Optional[str],
) -> User:
+ if email:
+ email = self._validate_email(email)
+ else:
+ email = None
+
return User(
id=id,
name=name,
+ email=email,
)
diff --git a/src/amdb/domain/validators/__init__.py b/src/amdb/domain/validators/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/amdb/domain/validators/email.py b/src/amdb/domain/validators/email.py
new file mode 100644
index 0000000..8380eda
--- /dev/null
+++ b/src/amdb/domain/validators/email.py
@@ -0,0 +1,14 @@
+import re
+
+from amdb.domain.constants.exceptions import INVALID_EMAIL
+from amdb.domain.exception import DomainError
+
+
+class ValidateEmail:
+ _REGEX = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b"
+
+ def __call__(self, email: str) -> str:
+ match = re.fullmatch(self._REGEX, email)
+ if not match:
+ raise DomainError(INVALID_EMAIL)
+ return email
diff --git a/src/amdb/infrastructure/persistence/alembic/migrations/versions/65f8840f4494_.py b/src/amdb/infrastructure/persistence/alembic/migrations/versions/65f8840f4494_.py
index 11fb0b3..11c9645 100644
--- a/src/amdb/infrastructure/persistence/alembic/migrations/versions/65f8840f4494_.py
+++ b/src/amdb/infrastructure/persistence/alembic/migrations/versions/65f8840f4494_.py
@@ -1,9 +1,9 @@
"""
Add uuid7 function,
Add id column to ratings table,
-Add primary key constraint on id in ratings table,
-Add unique constraint on pair of user_id and movie_id in ratings table,
-Add unique constraint on pair of user_id and movie_id in reviews table
+Add primary key constraint on id of ratings table,
+Add unique constraint on pair of user_id and movie_id of ratings table,
+Add unique constraint on pair of user_id and movie_id of reviews table
Revision ID: 65f8840f4494
Revises: 85a348467b90
diff --git a/src/amdb/infrastructure/persistence/alembic/migrations/versions/a2f7c2383ba8_.py b/src/amdb/infrastructure/persistence/alembic/migrations/versions/a2f7c2383ba8_.py
index 061ebe4..90301ee 100644
--- a/src/amdb/infrastructure/persistence/alembic/migrations/versions/a2f7c2383ba8_.py
+++ b/src/amdb/infrastructure/persistence/alembic/migrations/versions/a2f7c2383ba8_.py
@@ -1,6 +1,7 @@
"""
Add permissions table,
-Make type column string in review table
+Make type column string to reviews table
+Add email column to users table
Revision ID: a2f7c2383ba8
Revises: 65f8840f4494
@@ -61,6 +62,10 @@ def upgrade() -> None:
"type",
nullable=False,
)
+ op.add_column(
+ "users",
+ sa.Column("email", sa.String(), nullable=True, unique=True),
+ )
def downgrade() -> None:
@@ -94,3 +99,4 @@ def downgrade() -> None:
"type",
nullable=False,
)
+ op.drop_column("users", "email")
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/user.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/user.py
index db427ac..87fbf7f 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/user.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/user.py
@@ -24,10 +24,18 @@ def with_name(self, user_name: str) -> Optional[User]:
return self._to_entity(row) # type: ignore
return None
+ def with_email(self, user_email: str) -> Optional[User]:
+ statement = select(UserModel).where(UserModel.email == user_email)
+ row = self._connection.execute(statement).one_or_none()
+ if row:
+ return self._to_entity(row) # type: ignore
+ return None
+
def save(self, user: User) -> None:
statement = insert(UserModel).values(
id=UserId(user.id),
name=user.name,
+ email=user.email,
)
self._connection.execute(statement)
@@ -38,4 +46,5 @@ def _to_entity(
return User(
id=UserId(row.id),
name=row.name,
+ email=row.email,
)
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/models/user.py b/src/amdb/infrastructure/persistence/sqlalchemy/models/user.py
index 52514d5..2b16918 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/models/user.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/models/user.py
@@ -1,3 +1,4 @@
+from typing import Optional
from uuid import UUID
from sqlalchemy.orm import Mapped, mapped_column
@@ -14,3 +15,6 @@ class UserModel(Model):
name: Mapped[str] = mapped_column(
unique=True,
)
+ email: Mapped[Optional[str]] = mapped_column(
+ unique=True,
+ )
diff --git a/src/amdb/main/providers.py b/src/amdb/main/providers.py
index eaf83e4..871e485 100644
--- a/src/amdb/main/providers.py
+++ b/src/amdb/main/providers.py
@@ -10,6 +10,7 @@
from amdb.domain.services.rate_movie import RateMovie
from amdb.domain.services.unrate_movie import UnrateMovie
from amdb.domain.services.review_movie import ReviewMovie
+from amdb.domain.validators.email import ValidateEmail
from amdb.application.common.gateways.user import UserGateway
from amdb.application.common.gateways.movie import MovieGateway
from amdb.application.common.gateways.rating import RatingGateway
@@ -47,7 +48,7 @@
GetDetailedReviewsHandler,
)
from amdb.application.query_handlers.my_detailed_ratings import (
- GetMyDetailedRatingsQueryHandler,
+ GetMyDetailedRatingsHandler,
)
from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
from amdb.infrastructure.persistence.redis.config import RedisConfig
@@ -204,7 +205,7 @@ def register_user_handler(
password_manager: PasswordManager,
) -> RegisterUserHandler:
return RegisterUserHandler(
- create_user=CreateUser(),
+ create_user=CreateUser(validate_email=ValidateEmail()),
user_gateway=user_gateway,
permissions_gateway=permissions_gateway,
unit_of_work=unit_of_work,
@@ -301,11 +302,11 @@ def create_handler(
def get_my_detailed_ratings_handler(
self,
my_detailed_ratings_reader: MyDetailedRatingsViewModelReader,
- ) -> CreateHandler[GetMyDetailedRatingsQueryHandler]:
+ ) -> CreateHandler[GetMyDetailedRatingsHandler]:
def create_handler(
identity_provider: IdentityProvider,
- ) -> GetMyDetailedRatingsQueryHandler:
- return GetMyDetailedRatingsQueryHandler(
+ ) -> GetMyDetailedRatingsHandler:
+ return GetMyDetailedRatingsHandler(
my_detailed_ratings_reader=my_detailed_ratings_reader,
identity_provider=identity_provider,
)
From ff46bc07edb4825ec2ef6f8fe0255fe0070b5ffc Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Tue, 27 Feb 2024 20:04:27 +0400
Subject: [PATCH 14/39] Fix tests
---
.../unit/application/command_handlers/test_rate_movie.py | 3 +++
.../application/command_handlers/test_register_user.py | 8 ++++++--
.../application/command_handlers/test_review_movie.py | 2 ++
.../application/command_handlers/test_unrate_movie.py | 2 ++
.../application/query_handlers/test_detailed_movie.py | 1 +
.../application/query_handlers/test_detailed_reviews.py | 1 +
tests/unit/application/query_handlers/test_login.py | 3 +++
.../query_handlers/test_my_detailed_ratings.py | 5 +++--
.../query_handlers/test_non_detailed_movies.py | 1 +
9 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/tests/unit/application/command_handlers/test_rate_movie.py b/tests/unit/application/command_handlers/test_rate_movie.py
index c7cb76f..1f3593c 100644
--- a/tests/unit/application/command_handlers/test_rate_movie.py
+++ b/tests/unit/application/command_handlers/test_rate_movie.py
@@ -50,6 +50,7 @@ def test_rate_movie(
user = User(
id=UserId(uuid7()),
name="John Doe",
+ email="John@doe.com",
)
user_gateway.save(user)
@@ -155,6 +156,7 @@ def test_rate_movie_should_raise_error_when_movie_already_rated(
user = User(
id=UserId(uuid7()),
name="John Doe",
+ email="John@doe.com",
)
user_gateway.save(user)
@@ -228,6 +230,7 @@ def test_rate_movie_should_raise_error_when_rating_is_invalid(
user = User(
id=UserId(uuid7()),
name="John Doe",
+ email="John@doe.com",
)
user_gateway.save(user)
diff --git a/tests/unit/application/command_handlers/test_register_user.py b/tests/unit/application/command_handlers/test_register_user.py
index 281c5b4..b9e8839 100644
--- a/tests/unit/application/command_handlers/test_register_user.py
+++ b/tests/unit/application/command_handlers/test_register_user.py
@@ -3,6 +3,7 @@
from amdb.domain.entities.user import UserId, User
from amdb.domain.services.create_user import CreateUser
+from amdb.domain.validators.email import ValidateEmail
from amdb.application.common.gateways.user import UserGateway
from amdb.application.common.gateways.permissions import PermissionsGateway
from amdb.application.common.unit_of_work import UnitOfWork
@@ -23,10 +24,11 @@ def test_register_user(
):
command = RegisterUserCommand(
name="John Doe",
+ email="John@doe.com",
password="Secret",
)
handler = RegisterUserHandler(
- create_user=CreateUser(),
+ create_user=CreateUser(validate_email=ValidateEmail()),
user_gateway=user_gateway,
permissions_gateway=permissions_gateway,
unit_of_work=unit_of_work,
@@ -47,16 +49,18 @@ def test_create_user_should_raise_error_when_user_name_already_exists(
user = User(
id=UserId(uuid7()),
name=user_name,
+ email="John@doe.com",
)
user_gateway.save(user)
unit_of_work.commit()
command = RegisterUserCommand(
name=user_name,
+ email=None,
password="Secret",
)
handler = RegisterUserHandler(
- create_user=CreateUser(),
+ create_user=CreateUser(validate_email=ValidateEmail()),
user_gateway=user_gateway,
permissions_gateway=permissions_gateway,
unit_of_work=unit_of_work,
diff --git a/tests/unit/application/command_handlers/test_review_movie.py b/tests/unit/application/command_handlers/test_review_movie.py
index 54b47d7..45681b1 100644
--- a/tests/unit/application/command_handlers/test_review_movie.py
+++ b/tests/unit/application/command_handlers/test_review_movie.py
@@ -48,6 +48,7 @@ def test_review_movie(
user = User(
id=UserId(uuid7()),
name="John Doe",
+ email="John@doe.com",
)
user_gateway.save(user)
@@ -159,6 +160,7 @@ def test_review_movie_should_raise_error_when_movie_already_reviewed(
user = User(
id=UserId(uuid7()),
name="John Doe",
+ email="John@doe.com",
)
user_gateway.save(user)
diff --git a/tests/unit/application/command_handlers/test_unrate_movie.py b/tests/unit/application/command_handlers/test_unrate_movie.py
index 34385f9..2bd6491 100644
--- a/tests/unit/application/command_handlers/test_unrate_movie.py
+++ b/tests/unit/application/command_handlers/test_unrate_movie.py
@@ -48,6 +48,7 @@ def test_unrate_movie(
user = User(
id=UserId(uuid7()),
name="John Doe",
+ email="John@doe.com",
)
user_gateway.save(user)
@@ -154,6 +155,7 @@ def test_unrate_movie_should_raise_error_when_user_is_not_rating_owner(
user = User(
id=UserId(uuid7()),
name="John Doe",
+ email="John@doe.com",
)
user_gateway.save(user)
diff --git a/tests/unit/application/query_handlers/test_detailed_movie.py b/tests/unit/application/query_handlers/test_detailed_movie.py
index b2543b6..d265eb3 100644
--- a/tests/unit/application/query_handlers/test_detailed_movie.py
+++ b/tests/unit/application/query_handlers/test_detailed_movie.py
@@ -42,6 +42,7 @@ def test_get_detailed_movie(
user = User(
id=UserId(uuid7()),
name="John Doe",
+ email="John@doe.com",
)
user_gateway.save(user)
diff --git a/tests/unit/application/query_handlers/test_detailed_reviews.py b/tests/unit/application/query_handlers/test_detailed_reviews.py
index d7521c9..cdcd8ad 100644
--- a/tests/unit/application/query_handlers/test_detailed_reviews.py
+++ b/tests/unit/application/query_handlers/test_detailed_reviews.py
@@ -39,6 +39,7 @@ def test_get_detailed_reviews(
user = User(
id=UserId(uuid7()),
name="John Doe",
+ email="John@doe.com",
)
user_gateway.save(user)
diff --git a/tests/unit/application/query_handlers/test_login.py b/tests/unit/application/query_handlers/test_login.py
index 9e6a943..cd2c203 100644
--- a/tests/unit/application/query_handlers/test_login.py
+++ b/tests/unit/application/query_handlers/test_login.py
@@ -28,6 +28,7 @@ def test_login(
user = User(
id=UserId(uuid7()),
name="John Doe",
+ email="John@doe.com",
)
user_gateway.save(user)
@@ -91,6 +92,7 @@ def test_login_should_raise_error_when_password_is_incorrect(
user = User(
id=UserId(uuid7()),
name="John Doe",
+ email="John@doe.com",
)
user_gateway.save(user)
@@ -129,6 +131,7 @@ def test_login_should_raise_error_when_access_is_denied(
user = User(
id=UserId(uuid7()),
name="John Doe",
+ email="John@doe.com",
)
user_gateway.save(user)
diff --git a/tests/unit/application/query_handlers/test_my_detailed_ratings.py b/tests/unit/application/query_handlers/test_my_detailed_ratings.py
index 7c5e903..dd7836d 100644
--- a/tests/unit/application/query_handlers/test_my_detailed_ratings.py
+++ b/tests/unit/application/query_handlers/test_my_detailed_ratings.py
@@ -24,7 +24,7 @@
GetMyDetailedRatingsQuery,
)
from amdb.application.query_handlers.my_detailed_ratings import (
- GetMyDetailedRatingsQueryHandler,
+ GetMyDetailedRatingsHandler,
)
@@ -38,6 +38,7 @@ def test_get_my_detailed_ratings(
user = User(
id=UserId(uuid7()),
name="John Doe",
+ email="John@doe.com",
)
user_gateway.save(user)
@@ -70,7 +71,7 @@ def test_get_my_detailed_ratings(
limit=10,
offset=0,
)
- handler = GetMyDetailedRatingsQueryHandler(
+ handler = GetMyDetailedRatingsHandler(
my_detailed_ratings_reader=my_detailed_ratings_reader,
identity_provider=identity_provider,
)
diff --git a/tests/unit/application/query_handlers/test_non_detailed_movies.py b/tests/unit/application/query_handlers/test_non_detailed_movies.py
index 2fe5d0a..150632f 100644
--- a/tests/unit/application/query_handlers/test_non_detailed_movies.py
+++ b/tests/unit/application/query_handlers/test_non_detailed_movies.py
@@ -37,6 +37,7 @@ def test_get_non_detailed_movies(
user = User(
id=UserId(uuid7()),
name="John Doe",
+ email="John@doe.com",
)
user_gateway.save(user)
From 0484238137563cd3afcd1028b1b1cdbe93a807e2 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Tue, 27 Feb 2024 20:08:28 +0400
Subject: [PATCH 15/39] Add new test for `RegisterUserCommand`
---
.../command_handlers/test_register_user.py | 38 +++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/tests/unit/application/command_handlers/test_register_user.py b/tests/unit/application/command_handlers/test_register_user.py
index b9e8839..113656b 100644
--- a/tests/unit/application/command_handlers/test_register_user.py
+++ b/tests/unit/application/command_handlers/test_register_user.py
@@ -12,6 +12,7 @@
from amdb.application.command_handlers.register_user import RegisterUserHandler
from amdb.application.common.constants.exceptions import (
USER_NAME_ALREADY_EXISTS,
+ USER_EMAIL_ALREADY_EXISTS,
)
from amdb.application.common.exception import ApplicationError
@@ -52,6 +53,7 @@ def test_create_user_should_raise_error_when_user_name_already_exists(
email="John@doe.com",
)
user_gateway.save(user)
+
unit_of_work.commit()
command = RegisterUserCommand(
@@ -71,3 +73,39 @@ def test_create_user_should_raise_error_when_user_name_already_exists(
handler.execute(command)
assert error.value.message == USER_NAME_ALREADY_EXISTS
+
+
+def test_create_user_should_raise_error_when_user_email_already_exists(
+ user_gateway: UserGateway,
+ permissions_gateway: PermissionsGateway,
+ unit_of_work: UnitOfWork,
+ password_manager: PasswordManager,
+):
+ user_email = "John@doe.com"
+
+ user = User(
+ id=UserId(uuid7()),
+ name="John Doe",
+ email=user_email,
+ )
+ user_gateway.save(user)
+
+ unit_of_work.commit()
+
+ command = RegisterUserCommand(
+ name="Johny Doe",
+ email=user_email,
+ password="Secret",
+ )
+ handler = RegisterUserHandler(
+ create_user=CreateUser(validate_email=ValidateEmail()),
+ user_gateway=user_gateway,
+ permissions_gateway=permissions_gateway,
+ unit_of_work=unit_of_work,
+ password_manager=password_manager,
+ )
+
+ with pytest.raises(ApplicationError) as error:
+ handler.execute(command)
+
+ assert error.value.message == USER_EMAIL_ALREADY_EXISTS
From a02cb8c851c59efb10b670b5bbe9dee503610284 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Tue, 27 Feb 2024 21:03:55 +0400
Subject: [PATCH 16/39] Add `ExportMyRatingsQuery`
---
.../application/common/constants/export.py | 5 ++
.../application/common/converters/__init__.py | 0
.../common/converters/rating_for_export.py | 13 ++++
.../application/queries/export_my_ratings.py | 8 ++
.../query_handlers/export_my_ratings.py | 44 +++++++++++
.../infrastructure/converters/__init__.py | 0
.../converters/ratings_for_export.py | 40 ++++++++++
src/amdb/main/providers.py | 27 +++++++
.../presentation/web_api/exports/__init__.py | 0
.../web_api/exports/my_ratings.py | 59 +++++++++++++++
.../presentation/web_api/exports/router.py | 16 ++++
.../web_api/ratings/get_my_detailed.py | 4 +-
src/amdb/presentation/web_api/router.py | 3 +
tests/unit/application/conftest.py | 10 +++
.../query_handlers/test_export_my_ratings.py | 73 +++++++++++++++++++
15 files changed, 300 insertions(+), 2 deletions(-)
create mode 100644 src/amdb/application/common/constants/export.py
create mode 100644 src/amdb/application/common/converters/__init__.py
create mode 100644 src/amdb/application/common/converters/rating_for_export.py
create mode 100644 src/amdb/application/queries/export_my_ratings.py
create mode 100644 src/amdb/application/query_handlers/export_my_ratings.py
create mode 100644 src/amdb/infrastructure/converters/__init__.py
create mode 100644 src/amdb/infrastructure/converters/ratings_for_export.py
create mode 100644 src/amdb/presentation/web_api/exports/__init__.py
create mode 100644 src/amdb/presentation/web_api/exports/my_ratings.py
create mode 100644 src/amdb/presentation/web_api/exports/router.py
create mode 100644 tests/unit/application/query_handlers/test_export_my_ratings.py
diff --git a/src/amdb/application/common/constants/export.py b/src/amdb/application/common/constants/export.py
new file mode 100644
index 0000000..5f1ae6c
--- /dev/null
+++ b/src/amdb/application/common/constants/export.py
@@ -0,0 +1,5 @@
+from enum import Enum
+
+
+class ExportFormat(Enum):
+ CSV = "csv"
diff --git a/src/amdb/application/common/converters/__init__.py b/src/amdb/application/common/converters/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/amdb/application/common/converters/rating_for_export.py b/src/amdb/application/common/converters/rating_for_export.py
new file mode 100644
index 0000000..cdd2083
--- /dev/null
+++ b/src/amdb/application/common/converters/rating_for_export.py
@@ -0,0 +1,13 @@
+from typing import Protocol
+
+from amdb.application.common.view_models.rating_for_export import (
+ RatingForExportViewModel,
+)
+
+
+class RatingsForExportConverter(Protocol):
+ def to_csv(
+ self,
+ view_models: list[RatingForExportViewModel],
+ ) -> str:
+ raise NotImplementedError
diff --git a/src/amdb/application/queries/export_my_ratings.py b/src/amdb/application/queries/export_my_ratings.py
new file mode 100644
index 0000000..9b785aa
--- /dev/null
+++ b/src/amdb/application/queries/export_my_ratings.py
@@ -0,0 +1,8 @@
+from dataclasses import dataclass
+
+from amdb.application.common.constants.export import ExportFormat
+
+
+@dataclass(frozen=True, slots=True)
+class ExportMyRatingsQuery:
+ format: ExportFormat
diff --git a/src/amdb/application/query_handlers/export_my_ratings.py b/src/amdb/application/query_handlers/export_my_ratings.py
new file mode 100644
index 0000000..3f002e3
--- /dev/null
+++ b/src/amdb/application/query_handlers/export_my_ratings.py
@@ -0,0 +1,44 @@
+from amdb.application.common.constants.export import ExportFormat
+from amdb.application.common.readers.rating_for_export import (
+ RatingForExportViewModelsReader,
+)
+from amdb.application.common.converters.rating_for_export import (
+ RatingsForExportConverter,
+)
+from amdb.application.common.identity_provider import IdentityProvider
+from amdb.application.common.view_models.rating_for_export import (
+ RatingForExportViewModel,
+)
+from amdb.application.queries.export_my_ratings import ExportMyRatingsQuery
+
+
+class ExportMyRatingsHandler:
+ def __init__(
+ self,
+ *,
+ ratings_for_export_reader: RatingForExportViewModelsReader,
+ ratings_for_export_converter: RatingsForExportConverter,
+ identity_provider: IdentityProvider,
+ ) -> None:
+ self._ratings_for_export_reader = ratings_for_export_reader
+ self._ratings_for_export_converter = ratings_for_export_converter
+ self._identity_provider = identity_provider
+
+ def execute(self, query: ExportMyRatingsQuery) -> str:
+ current_user_id = self._identity_provider.user_id()
+
+ view_models = self._ratings_for_export_reader.get(
+ current_user_id=current_user_id,
+ )
+ return self._convert_view_models_to_format(
+ view_models=view_models,
+ format=query.format,
+ )
+
+ def _convert_view_models_to_format(
+ self,
+ view_models: list[RatingForExportViewModel],
+ format: ExportFormat,
+ ) -> str:
+ if format is ExportFormat.CSV:
+ return self._ratings_for_export_converter.to_csv(view_models)
diff --git a/src/amdb/infrastructure/converters/__init__.py b/src/amdb/infrastructure/converters/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/amdb/infrastructure/converters/ratings_for_export.py b/src/amdb/infrastructure/converters/ratings_for_export.py
new file mode 100644
index 0000000..1d4e6d2
--- /dev/null
+++ b/src/amdb/infrastructure/converters/ratings_for_export.py
@@ -0,0 +1,40 @@
+import csv
+from io import StringIO
+
+from amdb.application.common.view_models.rating_for_export import (
+ RatingForExportViewModel,
+)
+
+
+class RealRatingsForExportConverter:
+ def to_csv(
+ self,
+ view_models: list[RatingForExportViewModel],
+ ) -> str:
+ with StringIO() as file:
+ csv_writer = csv.writer(file)
+ csv_writer.writerow(
+ [
+ "id",
+ "title",
+ "release_date",
+ "rating",
+ "rating_count",
+ "your_rating",
+ "your_rating_created_at",
+ ],
+ )
+ for view_model in view_models:
+ csv_writer.writerow(
+ [
+ view_model["movie"]["id"],
+ view_model["movie"]["title"],
+ view_model["movie"]["release_date"],
+ view_model["movie"]["rating"],
+ view_model["movie"]["rating_count"],
+ view_model["rating"]["value"],
+ view_model["rating"]["created_at"],
+ ],
+ )
+ csv_file = file.getvalue()
+ return csv_file
diff --git a/src/amdb/main/providers.py b/src/amdb/main/providers.py
index 871e485..6539466 100644
--- a/src/amdb/main/providers.py
+++ b/src/amdb/main/providers.py
@@ -50,6 +50,9 @@
from amdb.application.query_handlers.my_detailed_ratings import (
GetMyDetailedRatingsHandler,
)
+from amdb.application.query_handlers.export_my_ratings import (
+ ExportMyRatingsHandler,
+)
from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
from amdb.infrastructure.persistence.redis.config import RedisConfig
from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.user import (
@@ -82,6 +85,9 @@
from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.my_detailed_ratings import (
MyDetailedRatingsViewModelMapper,
)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.rating_for_export import (
+ RatingForExportViewModelMapper,
+)
from amdb.infrastructure.persistence.redis.cache.permissions_mapper import (
PermissionsMapperCacheProvider,
)
@@ -92,6 +98,9 @@
from amdb.infrastructure.password_manager.password_manager import (
HashingPasswordManager,
)
+from amdb.infrastructure.converters.ratings_for_export import (
+ RealRatingsForExportConverter,
+)
from amdb.presentation.create_handler import CreateHandler
@@ -385,3 +394,21 @@ def create_handler(
)
return create_handler
+
+ @provide
+ def export_my_ratings(
+ self,
+ sqlaclhemy_connection: Connection,
+ ) -> CreateHandler[ExportMyRatingsHandler]:
+ def create_handler(
+ identity_provider: IdentityProvider,
+ ) -> ExportMyRatingsHandler:
+ return ExportMyRatingsHandler(
+ ratings_for_export_reader=RatingForExportViewModelMapper(
+ connection=sqlaclhemy_connection,
+ ),
+ ratings_for_export_converter=RealRatingsForExportConverter(),
+ identity_provider=identity_provider,
+ )
+
+ return create_handler
diff --git a/src/amdb/presentation/web_api/exports/__init__.py b/src/amdb/presentation/web_api/exports/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/amdb/presentation/web_api/exports/my_ratings.py b/src/amdb/presentation/web_api/exports/my_ratings.py
new file mode 100644
index 0000000..581bce3
--- /dev/null
+++ b/src/amdb/presentation/web_api/exports/my_ratings.py
@@ -0,0 +1,59 @@
+from typing import Annotated, Optional
+
+from fastapi import Cookie
+from fastapi.responses import StreamingResponse
+from dishka.integrations.fastapi import Depends, inject
+
+from amdb.application.common.constants.export import ExportFormat
+from amdb.application.queries.export_my_ratings import ExportMyRatingsQuery
+from amdb.application.query_handlers.export_my_ratings import (
+ ExportMyRatingsHandler,
+)
+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
+
+
+HandlerCreator = CreateHandler[ExportMyRatingsHandler]
+
+
+@inject
+async def export_my_ratings(
+ *,
+ create_handler: Annotated[HandlerCreator, Depends()],
+ session_gateway: Annotated[SessionGateway, Depends()],
+ permissions_gateway: Annotated[PermissionsGateway, Depends()],
+ session_id: Annotated[
+ Optional[str],
+ Cookie(alias=SESSION_ID_COOKIE),
+ ] = None,
+ format: ExportFormat = ExportFormat.CSV,
+) -> bytes:
+ """
+ Creates file of specified format with current user ratings and
+ returns it.\n\n
+ """
+ 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 = ExportMyRatingsQuery(format=format)
+ file = handler.execute(query)
+
+ if query.format is ExportFormat.CSV:
+ media_type = "text/csv"
+
+ response = StreamingResponse(
+ content=iter(file),
+ media_type=media_type,
+ headers={"Content-Disposition": "attachment; filename=export.csv"},
+ )
+ return response
diff --git a/src/amdb/presentation/web_api/exports/router.py b/src/amdb/presentation/web_api/exports/router.py
new file mode 100644
index 0000000..110ac0b
--- /dev/null
+++ b/src/amdb/presentation/web_api/exports/router.py
@@ -0,0 +1,16 @@
+from fastapi import APIRouter
+from fastapi.responses import StreamingResponse
+
+from .my_ratings import export_my_ratings
+
+
+exports_router = APIRouter(
+ prefix="/exports",
+ tags=["exports"],
+)
+exports_router.add_api_route(
+ path="/my-ratings",
+ endpoint=export_my_ratings,
+ methods=["GET"],
+ response_class=StreamingResponse,
+)
diff --git a/src/amdb/presentation/web_api/ratings/get_my_detailed.py b/src/amdb/presentation/web_api/ratings/get_my_detailed.py
index 0b8e4a0..50bad45 100644
--- a/src/amdb/presentation/web_api/ratings/get_my_detailed.py
+++ b/src/amdb/presentation/web_api/ratings/get_my_detailed.py
@@ -10,7 +10,7 @@
GetMyDetailedRatingsQuery,
)
from amdb.application.query_handlers.my_detailed_ratings import (
- GetMyDetailedRatingsQueryHandler,
+ GetMyDetailedRatingsHandler,
)
from amdb.application.common.gateways.permissions import PermissionsGateway
from amdb.infrastructure.auth.session.session import SessionId
@@ -22,7 +22,7 @@
from amdb.presentation.web_api.constants import SESSION_ID_COOKIE
-HandlerCreator = CreateHandler[GetMyDetailedRatingsQueryHandler]
+HandlerCreator = CreateHandler[GetMyDetailedRatingsHandler]
@inject
diff --git a/src/amdb/presentation/web_api/router.py b/src/amdb/presentation/web_api/router.py
index a24f55a..661120e 100644
--- a/src/amdb/presentation/web_api/router.py
+++ b/src/amdb/presentation/web_api/router.py
@@ -4,10 +4,13 @@
from .movies.router import movies_router
from .ratings.router import ratings_router
from .reviews.router import reviews_router
+from .exports.router import exports_router
router = APIRouter(prefix="/v1")
+
router.include_router(auth_router)
router.include_router(movies_router)
router.include_router(ratings_router)
router.include_router(reviews_router)
+router.include_router(exports_router)
diff --git a/tests/unit/application/conftest.py b/tests/unit/application/conftest.py
index 7601d36..f4d68dc 100644
--- a/tests/unit/application/conftest.py
+++ b/tests/unit/application/conftest.py
@@ -36,6 +36,9 @@
from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.my_detailed_ratings import (
MyDetailedRatingsViewModelMapper,
)
+from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.rating_for_export import (
+ RatingForExportViewModelMapper,
+)
from amdb.infrastructure.persistence.redis.cache.permissions_mapper import (
PermissionsMapperCacheProvider,
)
@@ -126,6 +129,13 @@ def my_detailed_ratings_reader(
return MyDetailedRatingsViewModelMapper(sqlalchemy_connection)
+@pytest.fixture
+def ratings_for_export_reader(
+ sqlalchemy_connection: Connection,
+) -> RatingForExportViewModelMapper:
+ return RatingForExportViewModelMapper(sqlalchemy_connection)
+
+
@pytest.fixture
def password_manager(
sqlalchemy_connection: Connection,
diff --git a/tests/unit/application/query_handlers/test_export_my_ratings.py b/tests/unit/application/query_handlers/test_export_my_ratings.py
new file mode 100644
index 0000000..9ea0310
--- /dev/null
+++ b/tests/unit/application/query_handlers/test_export_my_ratings.py
@@ -0,0 +1,73 @@
+from datetime import date, datetime, timezone
+from unittest.mock import Mock
+
+from uuid_extensions import uuid7
+
+from amdb.domain.entities.user import User, UserId
+from amdb.domain.entities.movie import Movie, MovieId
+from amdb.domain.entities.rating import Rating, RatingId
+from amdb.application.common.constants.export import ExportFormat
+from amdb.application.common.gateways.user import UserGateway
+from amdb.application.common.gateways.movie import MovieGateway
+from amdb.application.common.gateways.rating import RatingGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.identity_provider import IdentityProvider
+from amdb.application.common.readers.rating_for_export import (
+ RatingForExportViewModelsReader,
+)
+from amdb.infrastructure.converters.ratings_for_export import (
+ RealRatingsForExportConverter,
+)
+from amdb.application.queries.export_my_ratings import ExportMyRatingsQuery
+from amdb.application.query_handlers.export_my_ratings import (
+ ExportMyRatingsHandler,
+)
+
+
+def test_export_my_ratings_in_csv(
+ user_gateway: UserGateway,
+ movie_gateway: MovieGateway,
+ rating_gateway: RatingGateway,
+ unit_of_work: UnitOfWork,
+ ratings_for_export_reader: RatingForExportViewModelsReader,
+):
+ user = User(
+ id=UserId(uuid7()),
+ name="John Doe",
+ email="John@doe.com",
+ )
+ user_gateway.save(user)
+
+ movie = Movie(
+ id=MovieId(uuid7()),
+ title="Matrix",
+ release_date=date(1999, 3, 31),
+ rating=8,
+ rating_count=1,
+ )
+ movie_gateway.save(movie)
+
+ rating = Rating(
+ id=RatingId(uuid7()),
+ movie_id=movie.id,
+ user_id=user.id,
+ value=8,
+ created_at=datetime.now(timezone.utc),
+ )
+ rating_gateway.save(rating)
+
+ unit_of_work.commit()
+
+ identity_provider: IdentityProvider = Mock()
+ identity_provider.user_id = Mock(
+ return_value=user.id,
+ )
+
+ query = ExportMyRatingsQuery(format=ExportFormat.CSV)
+ handler = ExportMyRatingsHandler(
+ ratings_for_export_reader=ratings_for_export_reader,
+ ratings_for_export_converter=RealRatingsForExportConverter(),
+ identity_provider=identity_provider,
+ )
+
+ handler.execute(query)
From 656aa7741122bf3d37c014782d7020c5dd773a3b Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Wed, 28 Feb 2024 12:37:36 +0400
Subject: [PATCH 17/39] Add `UpdateMyProfileCommand`
---
.../command_handlers/update_my_profile.py | 37 ++++++++++++++++
.../application/commands/update_my_profile.py | 7 +++
src/amdb/application/common/gateways/user.py | 3 ++
src/amdb/domain/services/update_profile.py | 13 ++++++
.../sqlalchemy/mappers/entities/user.py | 6 ++-
src/amdb/main/providers.py | 24 +++++++++-
.../presentation/web_api/profiles/__init__.py | 0
.../presentation/web_api/profiles/router.py | 11 +++++
.../web_api/profiles/update_my.py | 44 +++++++++++++++++++
src/amdb/presentation/web_api/router.py | 2 +
.../test_update_my_profile.py | 44 +++++++++++++++++++
11 files changed, 189 insertions(+), 2 deletions(-)
create mode 100644 src/amdb/application/command_handlers/update_my_profile.py
create mode 100644 src/amdb/application/commands/update_my_profile.py
create mode 100644 src/amdb/domain/services/update_profile.py
create mode 100644 src/amdb/presentation/web_api/profiles/__init__.py
create mode 100644 src/amdb/presentation/web_api/profiles/router.py
create mode 100644 src/amdb/presentation/web_api/profiles/update_my.py
create mode 100644 tests/unit/application/command_handlers/test_update_my_profile.py
diff --git a/src/amdb/application/command_handlers/update_my_profile.py b/src/amdb/application/command_handlers/update_my_profile.py
new file mode 100644
index 0000000..bb3066f
--- /dev/null
+++ b/src/amdb/application/command_handlers/update_my_profile.py
@@ -0,0 +1,37 @@
+from typing import cast
+
+from amdb.domain.entities.user import User
+from amdb.domain.services.update_profile import UpdateProfile
+from amdb.application.common.gateways.user import UserGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.identity_provider import IdentityProvider
+from amdb.application.commands.update_my_profile import UpdateMyProfileCommand
+
+
+class UpdateMyProfileHandler:
+ def __init__(
+ self,
+ *,
+ update_profile: UpdateProfile,
+ user_gateway: UserGateway,
+ unit_of_work: UnitOfWork,
+ identity_provider: IdentityProvider,
+ ) -> None:
+ self._update_profile = update_profile
+ self._user_gateway = user_gateway
+ self._unit_of_work = unit_of_work
+ self._identity_provider = identity_provider
+
+ def execute(self, command: UpdateMyProfileCommand) -> None:
+ current_user_id = self._identity_provider.user_id()
+
+ user = self._user_gateway.with_id(current_user_id)
+ user = cast(User, user)
+
+ self._update_profile(
+ user=user,
+ email=command.email,
+ )
+ self._user_gateway.update(user)
+
+ self._unit_of_work.commit()
diff --git a/src/amdb/application/commands/update_my_profile.py b/src/amdb/application/commands/update_my_profile.py
new file mode 100644
index 0000000..ac62ee8
--- /dev/null
+++ b/src/amdb/application/commands/update_my_profile.py
@@ -0,0 +1,7 @@
+from dataclasses import dataclass
+from typing import Optional
+
+
+@dataclass(frozen=True, slots=True)
+class UpdateMyProfileCommand:
+ email: Optional[str]
diff --git a/src/amdb/application/common/gateways/user.py b/src/amdb/application/common/gateways/user.py
index a0eefc6..b7a98e8 100644
--- a/src/amdb/application/common/gateways/user.py
+++ b/src/amdb/application/common/gateways/user.py
@@ -15,3 +15,6 @@ def with_email(self, user_email: str) -> Optional[User]:
def save(self, user: User) -> None:
raise NotImplementedError
+
+ def update(self, user: User) -> None:
+ raise NotImplementedError
diff --git a/src/amdb/domain/services/update_profile.py b/src/amdb/domain/services/update_profile.py
new file mode 100644
index 0000000..e3eae83
--- /dev/null
+++ b/src/amdb/domain/services/update_profile.py
@@ -0,0 +1,13 @@
+from typing import Optional
+
+from amdb.domain.entities.user import User
+
+
+class UpdateProfile:
+ def __call__(
+ self,
+ *,
+ user: User,
+ email: Optional[str],
+ ) -> None:
+ user.email = email
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/user.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/user.py
index 87fbf7f..8b58f30 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/user.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/user.py
@@ -1,6 +1,6 @@
from typing import Annotated, Optional
-from sqlalchemy import Connection, Row, select, insert
+from sqlalchemy import Connection, Row, select, insert, update
from amdb.domain.entities.user import UserId, User
from amdb.infrastructure.persistence.sqlalchemy.models.user import UserModel
@@ -39,6 +39,10 @@ def save(self, user: User) -> None:
)
self._connection.execute(statement)
+ def update(self, user: User) -> None:
+ statement = update(UserModel).values(email=user.email)
+ self._connection.execute(statement)
+
def _to_entity(
self,
row: Annotated[UserModel, Row[tuple[UserModel]]],
diff --git a/src/amdb/main/providers.py b/src/amdb/main/providers.py
index 6539466..01c5ac4 100644
--- a/src/amdb/main/providers.py
+++ b/src/amdb/main/providers.py
@@ -6,6 +6,7 @@
from amdb.domain.services.access_concern import AccessConcern
from amdb.domain.services.create_user import CreateUser
+from amdb.domain.services.update_profile import UpdateProfile
from amdb.domain.services.create_movie import CreateMovie
from amdb.domain.services.rate_movie import RateMovie
from amdb.domain.services.unrate_movie import UnrateMovie
@@ -32,6 +33,9 @@
from amdb.application.common.password_manager import PasswordManager
from amdb.application.common.identity_provider import IdentityProvider
from amdb.application.command_handlers.register_user import RegisterUserHandler
+from amdb.application.command_handlers.update_my_profile import (
+ UpdateMyProfileHandler,
+)
from amdb.application.command_handlers.create_movie import CreateMovieHandler
from amdb.application.command_handlers.delete_movie import DeleteMovieHandler
from amdb.application.command_handlers.rate_movie import RateMovieHandler
@@ -277,6 +281,24 @@ def get_detailed_reviews_handler(
class HandlerCreatorsProvider(Provider):
scope = Scope.REQUEST
+ @provide
+ def update_my_profile_handler(
+ self,
+ user_gateway: UserGateway,
+ unit_of_work: UnitOfWork,
+ ) -> CreateHandler[UpdateMyProfileHandler]:
+ def create_handler(
+ identity_provider: IdentityProvider,
+ ) -> UpdateMyProfileHandler:
+ return UpdateMyProfileHandler(
+ update_profile=UpdateProfile(),
+ user_gateway=user_gateway,
+ unit_of_work=unit_of_work,
+ identity_provider=identity_provider,
+ )
+
+ return create_handler
+
@provide
def get_detailed_movie_handler(
self,
@@ -396,7 +418,7 @@ def create_handler(
return create_handler
@provide
- def export_my_ratings(
+ def export_my_ratings_handler(
self,
sqlaclhemy_connection: Connection,
) -> CreateHandler[ExportMyRatingsHandler]:
diff --git a/src/amdb/presentation/web_api/profiles/__init__.py b/src/amdb/presentation/web_api/profiles/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/amdb/presentation/web_api/profiles/router.py b/src/amdb/presentation/web_api/profiles/router.py
new file mode 100644
index 0000000..a818e4e
--- /dev/null
+++ b/src/amdb/presentation/web_api/profiles/router.py
@@ -0,0 +1,11 @@
+from fastapi import APIRouter
+
+from .update_my import update_my_profile
+
+
+profiles_router = APIRouter(tags=["profiles"])
+profiles_router.add_api_route(
+ path="/me/profile",
+ endpoint=update_my_profile,
+ methods=["PATCH"],
+)
diff --git a/src/amdb/presentation/web_api/profiles/update_my.py b/src/amdb/presentation/web_api/profiles/update_my.py
new file mode 100644
index 0000000..3dec5bd
--- /dev/null
+++ b/src/amdb/presentation/web_api/profiles/update_my.py
@@ -0,0 +1,44 @@
+from typing import Annotated, Optional
+
+from fastapi import Cookie
+from dishka.integrations.fastapi import Depends, inject
+from amdb.application.commands.update_my_profile import UpdateMyProfileCommand
+from amdb.application.command_handlers.update_my_profile import (
+ UpdateMyProfileHandler,
+)
+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
+
+
+HandlerCreator = CreateHandler[UpdateMyProfileHandler]
+
+
+@inject
+async def update_my_profile(
+ *,
+ create_handler: Annotated[HandlerCreator, Depends()],
+ session_gateway: Annotated[SessionGateway, Depends()],
+ permissions_gateway: Annotated[PermissionsGateway, Depends()],
+ session_id: Annotated[
+ Optional[str],
+ Cookie(alias=SESSION_ID_COOKIE),
+ ] = None,
+ command: UpdateMyProfileCommand,
+) -> None:
+ """
+ Updates current user profile
+ """
+ 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)
+
+ handler.execute(command)
diff --git a/src/amdb/presentation/web_api/router.py b/src/amdb/presentation/web_api/router.py
index 661120e..9264645 100644
--- a/src/amdb/presentation/web_api/router.py
+++ b/src/amdb/presentation/web_api/router.py
@@ -1,6 +1,7 @@
from fastapi import APIRouter
from .auth.router import auth_router
+from .profiles.router import profiles_router
from .movies.router import movies_router
from .ratings.router import ratings_router
from .reviews.router import reviews_router
@@ -10,6 +11,7 @@
router = APIRouter(prefix="/v1")
router.include_router(auth_router)
+router.include_router(profiles_router)
router.include_router(movies_router)
router.include_router(ratings_router)
router.include_router(reviews_router)
diff --git a/tests/unit/application/command_handlers/test_update_my_profile.py b/tests/unit/application/command_handlers/test_update_my_profile.py
new file mode 100644
index 0000000..daad48e
--- /dev/null
+++ b/tests/unit/application/command_handlers/test_update_my_profile.py
@@ -0,0 +1,44 @@
+from unittest.mock import Mock
+
+from uuid_extensions import uuid7
+
+from amdb.domain.entities.user import User, UserId
+from amdb.domain.services.update_profile import UpdateProfile
+from amdb.application.common.gateways.user import UserGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.identity_provider import IdentityProvider
+from amdb.application.commands.update_my_profile import UpdateMyProfileCommand
+from amdb.application.command_handlers.update_my_profile import (
+ UpdateMyProfileHandler,
+)
+
+
+def test_update_my_profile(
+ user_gateway: UserGateway,
+ unit_of_work: UnitOfWork,
+):
+ user = User(
+ id=UserId(uuid7()),
+ name="John Doe",
+ email="John@doe.com",
+ )
+ user_gateway.save(user)
+
+ unit_of_work.commit()
+
+ identity_provider: IdentityProvider = Mock()
+ identity_provider.user_id = Mock(
+ return_value=user.id,
+ )
+
+ command = UpdateMyProfileCommand(
+ email="Johny@doe.com",
+ )
+ handler = UpdateMyProfileHandler(
+ update_profile=UpdateProfile(),
+ user_gateway=user_gateway,
+ unit_of_work=unit_of_work,
+ identity_provider=identity_provider,
+ )
+
+ handler.execute(command)
From 35a831e9f61bde351fc0a5adb5bd8768b1b513ac Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Wed, 28 Feb 2024 12:45:47 +0400
Subject: [PATCH 18/39] Refactor web api routes docstrings, update path to get
ratings route
---
src/amdb/presentation/web_api/auth/login.py | 8 ++++----
src/amdb/presentation/web_api/auth/register.py | 2 +-
src/amdb/presentation/web_api/exports/my_ratings.py | 4 ++--
src/amdb/presentation/web_api/movies/get_detailed.py | 2 +-
src/amdb/presentation/web_api/movies/get_non_detailed.py | 2 +-
src/amdb/presentation/web_api/profiles/update_my.py | 2 +-
src/amdb/presentation/web_api/ratings/router.py | 6 +++---
7 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/src/amdb/presentation/web_api/auth/login.py b/src/amdb/presentation/web_api/auth/login.py
index e3f8fa8..7477686 100644
--- a/src/amdb/presentation/web_api/auth/login.py
+++ b/src/amdb/presentation/web_api/auth/login.py
@@ -27,10 +27,10 @@ async def login(
Logins, returns user id, creates new authentication session
and sets cookie with its id. \n\n
- #### Returns 400: \n
- * When user doesn't exist \n
- * When password is incorrect \n
- * When access is denied \n
+ #### Returns 400:
+ * When user doesn't exist
+ * When password is incorrect
+ * When access is denied
"""
user_id = handler.execute(query)
diff --git a/src/amdb/presentation/web_api/auth/register.py b/src/amdb/presentation/web_api/auth/register.py
index 5f78e0c..e73517f 100644
--- a/src/amdb/presentation/web_api/auth/register.py
+++ b/src/amdb/presentation/web_api/auth/register.py
@@ -27,7 +27,7 @@ async def register(
Registers user, returns his id, creates new
authentication session and sets cookie with its id. \n\n
- #### Returns 400: \n
+ #### Returns 400:
* When name is already taken
"""
user_id = handler.execute(command)
diff --git a/src/amdb/presentation/web_api/exports/my_ratings.py b/src/amdb/presentation/web_api/exports/my_ratings.py
index 581bce3..54d3f90 100644
--- a/src/amdb/presentation/web_api/exports/my_ratings.py
+++ b/src/amdb/presentation/web_api/exports/my_ratings.py
@@ -33,10 +33,10 @@ async def export_my_ratings(
Cookie(alias=SESSION_ID_COOKIE),
] = None,
format: ExportFormat = ExportFormat.CSV,
-) -> bytes:
+):
"""
Creates file of specified format with current user ratings and
- returns it.\n\n
+ returns it.
"""
identity_provider = SessionIdentityProvider(
session_id=SessionId(session_id) if session_id else None,
diff --git a/src/amdb/presentation/web_api/movies/get_detailed.py b/src/amdb/presentation/web_api/movies/get_detailed.py
index ec2153a..a6eefc4 100644
--- a/src/amdb/presentation/web_api/movies/get_detailed.py
+++ b/src/amdb/presentation/web_api/movies/get_detailed.py
@@ -40,7 +40,7 @@ async def get_detailed_movie(
Returns detailed movie information, detailed current user rating
and review on it. \n\n
- #### Returns 400: \n
+ #### Returns 400:
* When movie doesn't exist
"""
identity_provider = SessionIdentityProvider(
diff --git a/src/amdb/presentation/web_api/movies/get_non_detailed.py b/src/amdb/presentation/web_api/movies/get_non_detailed.py
index 65596f3..bf4752b 100644
--- a/src/amdb/presentation/web_api/movies/get_non_detailed.py
+++ b/src/amdb/presentation/web_api/movies/get_non_detailed.py
@@ -40,7 +40,7 @@ async def get_non_detailed_movies(
) -> list[NonDetailedMovieViewModel]:
"""
Returns list of non detailed movies and non detailed current
- user rating. \n\n
+ user rating.
"""
identity_provider = SessionIdentityProvider(
session_id=SessionId(session_id) if session_id else None,
diff --git a/src/amdb/presentation/web_api/profiles/update_my.py b/src/amdb/presentation/web_api/profiles/update_my.py
index 3dec5bd..f1ec2a8 100644
--- a/src/amdb/presentation/web_api/profiles/update_my.py
+++ b/src/amdb/presentation/web_api/profiles/update_my.py
@@ -32,7 +32,7 @@ async def update_my_profile(
command: UpdateMyProfileCommand,
) -> None:
"""
- Updates current user profile
+ Updates current user profile.
"""
identity_provider = SessionIdentityProvider(
session_id=SessionId(session_id) if session_id else None,
diff --git a/src/amdb/presentation/web_api/ratings/router.py b/src/amdb/presentation/web_api/ratings/router.py
index 4bc30b3..32daaa1 100644
--- a/src/amdb/presentation/web_api/ratings/router.py
+++ b/src/amdb/presentation/web_api/ratings/router.py
@@ -6,7 +6,7 @@
ratings_router = APIRouter(
- prefix="/ratings",
+ prefix="",
tags=["ratings"],
)
ratings_router.add_api_route(
@@ -15,12 +15,12 @@
methods=["GET"],
)
ratings_router.add_api_route(
- path="",
+ path="/ratings",
endpoint=rate_movie,
methods=["POST"],
)
ratings_router.add_api_route(
- path="/{rating_id}",
+ path="/ratings/{rating_id}",
endpoint=unrate_movie,
methods=["DELETE"],
)
From bdce374af746718d1daa53b53b0e3f6120fdd9af Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Wed, 28 Feb 2024 13:00:03 +0400
Subject: [PATCH 19/39] Update `UpdateProfile` domain service
---
src/amdb/domain/services/update_profile.py | 10 ++++++++++
src/amdb/main/providers.py | 2 +-
.../command_handlers/test_update_my_profile.py | 3 ++-
3 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/src/amdb/domain/services/update_profile.py b/src/amdb/domain/services/update_profile.py
index e3eae83..a862bdb 100644
--- a/src/amdb/domain/services/update_profile.py
+++ b/src/amdb/domain/services/update_profile.py
@@ -1,13 +1,23 @@
from typing import Optional
from amdb.domain.entities.user import User
+from amdb.domain.validators.email import ValidateEmail
class UpdateProfile:
+ def __init__(
+ self,
+ *,
+ validate_email: ValidateEmail,
+ ) -> None:
+ self._validate_email = validate_email
+
def __call__(
self,
*,
user: User,
email: Optional[str],
) -> None:
+ if email:
+ self._validate_email(email)
user.email = email
diff --git a/src/amdb/main/providers.py b/src/amdb/main/providers.py
index 01c5ac4..17f0dc5 100644
--- a/src/amdb/main/providers.py
+++ b/src/amdb/main/providers.py
@@ -291,7 +291,7 @@ def create_handler(
identity_provider: IdentityProvider,
) -> UpdateMyProfileHandler:
return UpdateMyProfileHandler(
- update_profile=UpdateProfile(),
+ update_profile=UpdateProfile(validate_email=ValidateEmail()),
user_gateway=user_gateway,
unit_of_work=unit_of_work,
identity_provider=identity_provider,
diff --git a/tests/unit/application/command_handlers/test_update_my_profile.py b/tests/unit/application/command_handlers/test_update_my_profile.py
index daad48e..2d57022 100644
--- a/tests/unit/application/command_handlers/test_update_my_profile.py
+++ b/tests/unit/application/command_handlers/test_update_my_profile.py
@@ -4,6 +4,7 @@
from amdb.domain.entities.user import User, UserId
from amdb.domain.services.update_profile import UpdateProfile
+from amdb.domain.validators.email import ValidateEmail
from amdb.application.common.gateways.user import UserGateway
from amdb.application.common.unit_of_work import UnitOfWork
from amdb.application.common.identity_provider import IdentityProvider
@@ -35,7 +36,7 @@ def test_update_my_profile(
email="Johny@doe.com",
)
handler = UpdateMyProfileHandler(
- update_profile=UpdateProfile(),
+ update_profile=UpdateProfile(validate_email=ValidateEmail()),
user_gateway=user_gateway,
unit_of_work=unit_of_work,
identity_provider=identity_provider,
From 370b56d24fa8b64bd957e5b7061a9886df0f11b1 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Thu, 29 Feb 2024 00:43:18 +0400
Subject: [PATCH 20/39] Update `update_my_profile` route docstring
---
src/amdb/presentation/web_api/profiles/update_my.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/amdb/presentation/web_api/profiles/update_my.py b/src/amdb/presentation/web_api/profiles/update_my.py
index f1ec2a8..6cddafd 100644
--- a/src/amdb/presentation/web_api/profiles/update_my.py
+++ b/src/amdb/presentation/web_api/profiles/update_my.py
@@ -32,7 +32,10 @@ async def update_my_profile(
command: UpdateMyProfileCommand,
) -> None:
"""
- Updates current user profile.
+ Updates current user profile.\n\n
+
+ ####Returns 400:
+ * When email is invalid
"""
identity_provider = SessionIdentityProvider(
session_id=SessionId(session_id) if session_id else None,
From ef48ecb6c1e885492b0b5a9203a93ed03ccf312c Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Thu, 29 Feb 2024 12:41:43 +0400
Subject: [PATCH 21/39] Add `RequestMyRatingsExportQuery`
---
.../common/constants/exceptions.py | 2 +-
.../application/common/constants/sending.py | 5 +
.../application/common/services/__init__.py | 0
.../services/ensure_can_use_sending_method.py | 15 +++
.../application/common/task_queue/__init__.py | 0
.../task_queue/export_and_send_my_ratings.py | 16 +++
.../queries/request_my_ratings_export.py | 10 ++
.../request_my_ratings_export.py | 45 +++++++
src/amdb/main/providers.py | 23 ++++
.../web_api/exports/request_my_ratings.py | 51 ++++++++
.../presentation/web_api/exports/router.py | 10 +-
.../test_request_my_ratings_export.py | 111 ++++++++++++++++++
12 files changed, 285 insertions(+), 3 deletions(-)
create mode 100644 src/amdb/application/common/constants/sending.py
create mode 100644 src/amdb/application/common/services/__init__.py
create mode 100644 src/amdb/application/common/services/ensure_can_use_sending_method.py
create mode 100644 src/amdb/application/common/task_queue/__init__.py
create mode 100644 src/amdb/application/common/task_queue/export_and_send_my_ratings.py
create mode 100644 src/amdb/application/queries/request_my_ratings_export.py
create mode 100644 src/amdb/application/query_handlers/request_my_ratings_export.py
create mode 100644 src/amdb/presentation/web_api/exports/request_my_ratings.py
create mode 100644 tests/unit/application/query_handlers/test_request_my_ratings_export.py
diff --git a/src/amdb/application/common/constants/exceptions.py b/src/amdb/application/common/constants/exceptions.py
index c788bd8..995215f 100644
--- a/src/amdb/application/common/constants/exceptions.py
+++ b/src/amdb/application/common/constants/exceptions.py
@@ -4,7 +4,7 @@
REVIEW_MOVIE_ACCESS_DENIED = "Access to movie reviewing is denied"
USER_IS_NOT_OWNER = "User is not an owner"
-
+USER_HAS_NO_EMAIL = "User has no email"
USER_NAME_ALREADY_EXISTS = "User name already exists"
USER_EMAIL_ALREADY_EXISTS = "User email already exists"
USER_DOES_NOT_EXIST = "User doesn't exist"
diff --git a/src/amdb/application/common/constants/sending.py b/src/amdb/application/common/constants/sending.py
new file mode 100644
index 0000000..c0a7a90
--- /dev/null
+++ b/src/amdb/application/common/constants/sending.py
@@ -0,0 +1,5 @@
+from enum import Enum
+
+
+class SendingMethod(Enum):
+ EMAIL = "email"
diff --git a/src/amdb/application/common/services/__init__.py b/src/amdb/application/common/services/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/amdb/application/common/services/ensure_can_use_sending_method.py b/src/amdb/application/common/services/ensure_can_use_sending_method.py
new file mode 100644
index 0000000..f03667a
--- /dev/null
+++ b/src/amdb/application/common/services/ensure_can_use_sending_method.py
@@ -0,0 +1,15 @@
+from amdb.domain.entities.user import User
+from amdb.application.common.constants.sending import SendingMethod
+from amdb.application.common.constants.exceptions import USER_HAS_NO_EMAIL
+from amdb.application.common.exception import ApplicationError
+
+
+class EnsureCanUseSendingMethod:
+ def __call__(
+ self,
+ *,
+ user: User,
+ sending_method: SendingMethod,
+ ) -> None:
+ if sending_method is SendingMethod.EMAIL and not user.email:
+ raise ApplicationError(USER_HAS_NO_EMAIL)
diff --git a/src/amdb/application/common/task_queue/__init__.py b/src/amdb/application/common/task_queue/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/amdb/application/common/task_queue/export_and_send_my_ratings.py b/src/amdb/application/common/task_queue/export_and_send_my_ratings.py
new file mode 100644
index 0000000..a5ba5ee
--- /dev/null
+++ b/src/amdb/application/common/task_queue/export_and_send_my_ratings.py
@@ -0,0 +1,16 @@
+from typing import Protocol
+
+from amdb.domain.entities.user import UserId
+from amdb.application.common.constants.export import ExportFormat
+from amdb.application.common.constants.sending import SendingMethod
+
+
+class EnqueueExportAndSendingMyRatings(Protocol):
+ def __call__(
+ self,
+ *,
+ user_id: UserId,
+ export_format: ExportFormat,
+ sending_method: SendingMethod,
+ ) -> None:
+ raise NotImplementedError
diff --git a/src/amdb/application/queries/request_my_ratings_export.py b/src/amdb/application/queries/request_my_ratings_export.py
new file mode 100644
index 0000000..dfadb40
--- /dev/null
+++ b/src/amdb/application/queries/request_my_ratings_export.py
@@ -0,0 +1,10 @@
+from dataclasses import dataclass
+
+from amdb.application.common.constants.export import ExportFormat
+from amdb.application.common.constants.sending import SendingMethod
+
+
+@dataclass(frozen=True, slots=True)
+class RequestMyRatingsExportQuery:
+ format: ExportFormat
+ sending_method: SendingMethod
diff --git a/src/amdb/application/query_handlers/request_my_ratings_export.py b/src/amdb/application/query_handlers/request_my_ratings_export.py
new file mode 100644
index 0000000..2e3eb63
--- /dev/null
+++ b/src/amdb/application/query_handlers/request_my_ratings_export.py
@@ -0,0 +1,45 @@
+from typing import cast
+
+from amdb.domain.entities.user import User
+from amdb.application.common.services.ensure_can_use_sending_method import (
+ EnsureCanUseSendingMethod,
+)
+from amdb.application.common.gateways.user import UserGateway
+from amdb.application.common.task_queue.export_and_send_my_ratings import (
+ EnqueueExportAndSendingMyRatings,
+)
+from amdb.application.common.identity_provider import IdentityProvider
+from amdb.application.queries.request_my_ratings_export import (
+ RequestMyRatingsExportQuery,
+)
+
+
+class RequestMyRatingsExportHandler:
+ def __init__(
+ self,
+ *,
+ ensure_can_use_sending_method: EnsureCanUseSendingMethod,
+ user_gateway: UserGateway,
+ enqueue_export_and_sending: EnqueueExportAndSendingMyRatings,
+ identity_provider: IdentityProvider,
+ ) -> None:
+ self._ensure_can_use_sending_method = ensure_can_use_sending_method
+ self._user_gateway = user_gateway
+ self._enqueue_export_and_sending = enqueue_export_and_sending
+ self._identity_provider = identity_provider
+
+ def execute(self, query: RequestMyRatingsExportQuery) -> None:
+ current_user_id = self._identity_provider.user_id()
+
+ user = self._user_gateway.with_id(current_user_id)
+ user = cast(User, user)
+
+ self._ensure_can_use_sending_method(
+ user=user,
+ sending_method=query.sending_method,
+ )
+ self._enqueue_export_and_sending(
+ user_id=current_user_id,
+ export_format=query.format,
+ sending_method=query.sending_method,
+ )
diff --git a/src/amdb/main/providers.py b/src/amdb/main/providers.py
index 17f0dc5..4f0e14a 100644
--- a/src/amdb/main/providers.py
+++ b/src/amdb/main/providers.py
@@ -12,6 +12,9 @@
from amdb.domain.services.unrate_movie import UnrateMovie
from amdb.domain.services.review_movie import ReviewMovie
from amdb.domain.validators.email import ValidateEmail
+from amdb.application.common.services.ensure_can_use_sending_method import (
+ EnsureCanUseSendingMethod,
+)
from amdb.application.common.gateways.user import UserGateway
from amdb.application.common.gateways.movie import MovieGateway
from amdb.application.common.gateways.rating import RatingGateway
@@ -57,6 +60,9 @@
from amdb.application.query_handlers.export_my_ratings import (
ExportMyRatingsHandler,
)
+from amdb.application.query_handlers.request_my_ratings_export import (
+ RequestMyRatingsExportHandler,
+)
from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
from amdb.infrastructure.persistence.redis.config import RedisConfig
from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.user import (
@@ -434,3 +440,20 @@ def create_handler(
)
return create_handler
+
+ @provide
+ def request_my_ratings_export_handler(
+ self,
+ user_gateway: UserGateway,
+ ) -> CreateHandler[RequestMyRatingsExportHandler]:
+ def create_handler(
+ identity_provider: IdentityProvider,
+ ) -> RequestMyRatingsExportHandler:
+ return RequestMyRatingsExportHandler(
+ ensure_can_use_sending_method=EnsureCanUseSendingMethod(),
+ user_gateway=user_gateway,
+ enqueue_export_and_sending=lambda **kwargs: ..., # type: ignore
+ identity_provider=identity_provider,
+ )
+
+ return create_handler
diff --git a/src/amdb/presentation/web_api/exports/request_my_ratings.py b/src/amdb/presentation/web_api/exports/request_my_ratings.py
new file mode 100644
index 0000000..23014f3
--- /dev/null
+++ b/src/amdb/presentation/web_api/exports/request_my_ratings.py
@@ -0,0 +1,51 @@
+from typing import Annotated, Optional
+
+from fastapi import Cookie
+from dishka.integrations.fastapi import Depends, inject
+
+from amdb.application.queries.request_my_ratings_export import (
+ RequestMyRatingsExportQuery,
+)
+from amdb.application.query_handlers.request_my_ratings_export import (
+ RequestMyRatingsExportHandler,
+)
+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
+
+
+HandlerCreator = CreateHandler[RequestMyRatingsExportHandler]
+
+
+@inject
+async def request_my_ratings_export(
+ *,
+ create_handler: Annotated[HandlerCreator, Depends()],
+ session_gateway: Annotated[SessionGateway, Depends()],
+ permissions_gateway: Annotated[PermissionsGateway, Depends()],
+ session_id: Annotated[
+ Optional[str],
+ Cookie(alias=SESSION_ID_COOKIE),
+ ] = None,
+ query: RequestMyRatingsExportQuery,
+) -> None:
+ """
+ Sends file of specified format with current user ratings using
+ specified sending method.\n\n
+
+ ####Returns 400:
+ * When email sending method was passed and user has no email
+ """
+ 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)
+
+ handler.execute(query)
diff --git a/src/amdb/presentation/web_api/exports/router.py b/src/amdb/presentation/web_api/exports/router.py
index 110ac0b..e6c3649 100644
--- a/src/amdb/presentation/web_api/exports/router.py
+++ b/src/amdb/presentation/web_api/exports/router.py
@@ -2,15 +2,21 @@
from fastapi.responses import StreamingResponse
from .my_ratings import export_my_ratings
+from .request_my_ratings import request_my_ratings_export
exports_router = APIRouter(
- prefix="/exports",
+ prefix="",
tags=["exports"],
)
exports_router.add_api_route(
- path="/my-ratings",
+ path="/exports/my-ratings",
endpoint=export_my_ratings,
methods=["GET"],
response_class=StreamingResponse,
)
+exports_router.add_api_route(
+ path="/export-requests/my-ratings",
+ endpoint=request_my_ratings_export,
+ methods=["POST"],
+)
diff --git a/tests/unit/application/query_handlers/test_request_my_ratings_export.py b/tests/unit/application/query_handlers/test_request_my_ratings_export.py
new file mode 100644
index 0000000..87822a2
--- /dev/null
+++ b/tests/unit/application/query_handlers/test_request_my_ratings_export.py
@@ -0,0 +1,111 @@
+from unittest.mock import Mock
+from typing import Optional
+
+import pytest
+from uuid_extensions import uuid7
+
+from amdb.domain.entities.user import User, UserId
+from amdb.application.common.constants.export import ExportFormat
+from amdb.application.common.constants.sending import SendingMethod
+from amdb.application.common.services.ensure_can_use_sending_method import (
+ EnsureCanUseSendingMethod,
+)
+from amdb.application.common.gateways.user import UserGateway
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.task_queue.export_and_send_my_ratings import (
+ EnqueueExportAndSendingMyRatings,
+)
+from amdb.application.common.identity_provider import IdentityProvider
+from amdb.application.common.constants.exceptions import (
+ USER_HAS_NO_EMAIL,
+)
+from amdb.application.common.exception import ApplicationError
+from amdb.application.queries.request_my_ratings_export import (
+ RequestMyRatingsExportQuery,
+)
+from amdb.application.query_handlers.request_my_ratings_export import (
+ RequestMyRatingsExportHandler,
+)
+
+
+def test_request_my_ratings_export(
+ user_gateway: UserGateway,
+ unit_of_work: UnitOfWork,
+):
+ user = User(
+ id=UserId(uuid7()),
+ name="John Doe",
+ email="John@doe.com",
+ )
+ user_gateway.save(user)
+
+ unit_of_work.commit()
+
+ identity_provider: IdentityProvider = Mock()
+ identity_provider.user_id = Mock(
+ return_value=user.id,
+ )
+ enqueue_export_and_sending: EnqueueExportAndSendingMyRatings = Mock()
+
+ query = RequestMyRatingsExportQuery(
+ format=ExportFormat.CSV,
+ sending_method=SendingMethod.EMAIL,
+ )
+ handler = RequestMyRatingsExportHandler(
+ ensure_can_use_sending_method=EnsureCanUseSendingMethod(),
+ user_gateway=user_gateway,
+ enqueue_export_and_sending=enqueue_export_and_sending,
+ identity_provider=identity_provider,
+ )
+
+ handler.execute(query)
+
+
+@pytest.mark.parametrize(
+ (
+ "sending_method",
+ "email",
+ ),
+ (
+ (
+ SendingMethod.EMAIL,
+ None,
+ ),
+ ),
+)
+def test_request_my_ratings_export_should_raise_error_when_user_cannot_use_sending_method(
+ sending_method: SendingMethod,
+ email: Optional[str],
+ user_gateway: UserGateway,
+ unit_of_work: UnitOfWork,
+):
+ user = User(
+ id=UserId(uuid7()),
+ name="John Doe",
+ email=email,
+ )
+ user_gateway.save(user)
+
+ unit_of_work.commit()
+
+ identity_provider: IdentityProvider = Mock()
+ identity_provider.user_id = Mock(
+ return_value=user.id,
+ )
+ enqueue_export_and_sending: EnqueueExportAndSendingMyRatings = Mock()
+
+ query = RequestMyRatingsExportQuery(
+ format=ExportFormat.CSV,
+ sending_method=sending_method,
+ )
+ handler = RequestMyRatingsExportHandler(
+ ensure_can_use_sending_method=EnsureCanUseSendingMethod(),
+ user_gateway=user_gateway,
+ enqueue_export_and_sending=enqueue_export_and_sending,
+ identity_provider=identity_provider,
+ )
+
+ with pytest.raises(ApplicationError) as error:
+ handler.execute(query)
+
+ assert error.value.message == USER_HAS_NO_EMAIL
From 8cb0b7eb97b5f2ca2a01da76357540c429084bc6 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Wed, 6 Mar 2024 15:17:18 +0400
Subject: [PATCH 22/39] Add `ExportAndSendMyRatingsQuery`
---
.../common/converters/rating_for_export.py | 3 +-
.../application/common/entities/__init__.py | 0
src/amdb/application/common/entities/file.py | 4 ++
.../application/common/senders/__init__.py | 0
src/amdb/application/common/senders/email.py | 14 ++++
.../common/services/convert_to_file.py | 22 ++++++
.../queries/export_and_send_my_ratings.py | 12 ++++
.../export_and_send_my_ratings.py | 72 +++++++++++++++++++
.../query_handlers/export_my_ratings.py | 27 +++----
.../converters/ratings_for_export.py | 5 +-
src/amdb/infrastructure/senders/__init__.py | 0
src/amdb/infrastructure/senders/email.py | 14 ++++
.../infrastructure/task_queue/__init__.py | 0
.../task_queue/export_and_send_my_ratings.py | 14 ++++
14 files changed, 166 insertions(+), 21 deletions(-)
create mode 100644 src/amdb/application/common/entities/__init__.py
create mode 100644 src/amdb/application/common/entities/file.py
create mode 100644 src/amdb/application/common/senders/__init__.py
create mode 100644 src/amdb/application/common/senders/email.py
create mode 100644 src/amdb/application/common/services/convert_to_file.py
create mode 100644 src/amdb/application/queries/export_and_send_my_ratings.py
create mode 100644 src/amdb/application/query_handlers/export_and_send_my_ratings.py
create mode 100644 src/amdb/infrastructure/senders/__init__.py
create mode 100644 src/amdb/infrastructure/senders/email.py
create mode 100644 src/amdb/infrastructure/task_queue/__init__.py
create mode 100644 src/amdb/infrastructure/task_queue/export_and_send_my_ratings.py
diff --git a/src/amdb/application/common/converters/rating_for_export.py b/src/amdb/application/common/converters/rating_for_export.py
index cdd2083..fa88497 100644
--- a/src/amdb/application/common/converters/rating_for_export.py
+++ b/src/amdb/application/common/converters/rating_for_export.py
@@ -1,5 +1,6 @@
from typing import Protocol
+from amdb.application.common.entities.file import File
from amdb.application.common.view_models.rating_for_export import (
RatingForExportViewModel,
)
@@ -9,5 +10,5 @@ class RatingsForExportConverter(Protocol):
def to_csv(
self,
view_models: list[RatingForExportViewModel],
- ) -> str:
+ ) -> File:
raise NotImplementedError
diff --git a/src/amdb/application/common/entities/__init__.py b/src/amdb/application/common/entities/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/amdb/application/common/entities/file.py b/src/amdb/application/common/entities/file.py
new file mode 100644
index 0000000..2f849f5
--- /dev/null
+++ b/src/amdb/application/common/entities/file.py
@@ -0,0 +1,4 @@
+from typing import NewType
+
+
+File = NewType("File", str)
diff --git a/src/amdb/application/common/senders/__init__.py b/src/amdb/application/common/senders/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/amdb/application/common/senders/email.py b/src/amdb/application/common/senders/email.py
new file mode 100644
index 0000000..778aa04
--- /dev/null
+++ b/src/amdb/application/common/senders/email.py
@@ -0,0 +1,14 @@
+from typing import Protocol
+
+from amdb.application.common.entities.file import File
+
+
+class SendEmail(Protocol):
+ def __call__(
+ self,
+ *,
+ email: str,
+ subject: str,
+ files: list[File],
+ ) -> None:
+ raise NotImplementedError
diff --git a/src/amdb/application/common/services/convert_to_file.py b/src/amdb/application/common/services/convert_to_file.py
new file mode 100644
index 0000000..b47a68b
--- /dev/null
+++ b/src/amdb/application/common/services/convert_to_file.py
@@ -0,0 +1,22 @@
+from amdb.application.common.constants.export import ExportFormat
+from amdb.application.common.entities.file import File
+from amdb.application.common.converters.rating_for_export import (
+ RatingsForExportConverter,
+)
+from amdb.application.common.view_models.rating_for_export import (
+ RatingForExportViewModel,
+)
+
+
+class ConvertMyRatingsToFile:
+ def __init__(self, converter: RatingsForExportConverter) -> None:
+ self._converter = converter
+
+ def __call__(
+ self,
+ *,
+ view_models: list[RatingForExportViewModel],
+ format: ExportFormat,
+ ) -> File:
+ if format is ExportFormat.CSV:
+ return self._converter.to_csv(view_models)
diff --git a/src/amdb/application/queries/export_and_send_my_ratings.py b/src/amdb/application/queries/export_and_send_my_ratings.py
new file mode 100644
index 0000000..884c337
--- /dev/null
+++ b/src/amdb/application/queries/export_and_send_my_ratings.py
@@ -0,0 +1,12 @@
+from dataclasses import dataclass
+
+from amdb.domain.entities.user import UserId
+from amdb.application.common.constants.export import ExportFormat
+from amdb.application.common.constants.sending import SendingMethod
+
+
+@dataclass(frozen=True, slots=True)
+class ExportAndSendMyRatingsQuery:
+ user_id: UserId
+ format: ExportFormat
+ sending_method: SendingMethod
diff --git a/src/amdb/application/query_handlers/export_and_send_my_ratings.py b/src/amdb/application/query_handlers/export_and_send_my_ratings.py
new file mode 100644
index 0000000..2966b55
--- /dev/null
+++ b/src/amdb/application/query_handlers/export_and_send_my_ratings.py
@@ -0,0 +1,72 @@
+from amdb.domain.entities.user import User
+from amdb.application.common.constants.sending import SendingMethod
+from amdb.application.common.entities.file import File
+from amdb.application.common.services.ensure_can_use_sending_method import (
+ EnsureCanUseSendingMethod,
+)
+from amdb.application.common.services.convert_to_file import (
+ ConvertMyRatingsToFile,
+)
+from amdb.application.common.gateways.user import UserGateway
+from amdb.application.common.readers.rating_for_export import (
+ RatingForExportViewModelsReader,
+)
+from amdb.application.common.senders.email import SendEmail
+from amdb.application.common.constants.exceptions import USER_DOES_NOT_EXIST
+from amdb.application.common.exception import ApplicationError
+from amdb.application.queries.export_and_send_my_ratings import (
+ ExportAndSendMyRatingsQuery,
+)
+
+
+class ExportAndSendMyRatingsHandler:
+ def __init__(
+ self,
+ *,
+ ensure_can_use_sending_method: EnsureCanUseSendingMethod,
+ convert_my_ratings_to_file: ConvertMyRatingsToFile,
+ user_gateway: UserGateway,
+ ratings_for_export_reader: RatingForExportViewModelsReader,
+ send_email: SendEmail,
+ ) -> None:
+ self._ensure_can_use_sending_method = ensure_can_use_sending_method
+ self._convert_my_ratings_to_file = convert_my_ratings_to_file
+ self._user_gateway = user_gateway
+ self._ratings_for_export_reader = ratings_for_export_reader
+ self._send_email = send_email
+
+ def execute(self, query: ExportAndSendMyRatingsQuery) -> None:
+ user = self._user_gateway.with_id(query.user_id)
+ if not user:
+ raise ApplicationError(USER_DOES_NOT_EXIST)
+
+ self._ensure_can_use_sending_method(
+ user=user,
+ sending_method=query.sending_method,
+ )
+ view_models = self._ratings_for_export_reader.get(
+ current_user_id=query.user_id,
+ )
+ file = self._convert_my_ratings_to_file(
+ view_models=view_models,
+ format=query.format,
+ )
+ self._send_file(
+ user=user,
+ file=file,
+ sending_method=query.sending_method,
+ )
+
+ def _send_file(
+ self,
+ *,
+ user: User,
+ file: File,
+ sending_method: SendingMethod,
+ ) -> None:
+ if sending_method is SendingMethod.EMAIL and user.email:
+ self._send_email(
+ email=user.email,
+ subject="Your exported ratings",
+ files=[file],
+ )
diff --git a/src/amdb/application/query_handlers/export_my_ratings.py b/src/amdb/application/query_handlers/export_my_ratings.py
index 3f002e3..d989808 100644
--- a/src/amdb/application/query_handlers/export_my_ratings.py
+++ b/src/amdb/application/query_handlers/export_my_ratings.py
@@ -1,14 +1,11 @@
-from amdb.application.common.constants.export import ExportFormat
+from amdb.application.common.entities.file import File
+from amdb.application.common.services.convert_to_file import (
+ ConvertMyRatingsToFile,
+)
from amdb.application.common.readers.rating_for_export import (
RatingForExportViewModelsReader,
)
-from amdb.application.common.converters.rating_for_export import (
- RatingsForExportConverter,
-)
from amdb.application.common.identity_provider import IdentityProvider
-from amdb.application.common.view_models.rating_for_export import (
- RatingForExportViewModel,
-)
from amdb.application.queries.export_my_ratings import ExportMyRatingsQuery
@@ -16,29 +13,23 @@ class ExportMyRatingsHandler:
def __init__(
self,
*,
+ convert_my_ratings_to_file: ConvertMyRatingsToFile,
ratings_for_export_reader: RatingForExportViewModelsReader,
- ratings_for_export_converter: RatingsForExportConverter,
identity_provider: IdentityProvider,
) -> None:
+ self._convert_my_ratings_to_file = convert_my_ratings_to_file
self._ratings_for_export_reader = ratings_for_export_reader
- self._ratings_for_export_converter = ratings_for_export_converter
self._identity_provider = identity_provider
- def execute(self, query: ExportMyRatingsQuery) -> str:
+ def execute(self, query: ExportMyRatingsQuery) -> File:
current_user_id = self._identity_provider.user_id()
view_models = self._ratings_for_export_reader.get(
current_user_id=current_user_id,
)
- return self._convert_view_models_to_format(
+ file = self._convert_my_ratings_to_file(
view_models=view_models,
format=query.format,
)
- def _convert_view_models_to_format(
- self,
- view_models: list[RatingForExportViewModel],
- format: ExportFormat,
- ) -> str:
- if format is ExportFormat.CSV:
- return self._ratings_for_export_converter.to_csv(view_models)
+ return file
diff --git a/src/amdb/infrastructure/converters/ratings_for_export.py b/src/amdb/infrastructure/converters/ratings_for_export.py
index 1d4e6d2..10d5ae3 100644
--- a/src/amdb/infrastructure/converters/ratings_for_export.py
+++ b/src/amdb/infrastructure/converters/ratings_for_export.py
@@ -1,6 +1,7 @@
import csv
from io import StringIO
+from amdb.application.common.entities.file import File
from amdb.application.common.view_models.rating_for_export import (
RatingForExportViewModel,
)
@@ -10,7 +11,7 @@ class RealRatingsForExportConverter:
def to_csv(
self,
view_models: list[RatingForExportViewModel],
- ) -> str:
+ ) -> File:
with StringIO() as file:
csv_writer = csv.writer(file)
csv_writer.writerow(
@@ -37,4 +38,4 @@ def to_csv(
],
)
csv_file = file.getvalue()
- return csv_file
+ return File(csv_file)
diff --git a/src/amdb/infrastructure/senders/__init__.py b/src/amdb/infrastructure/senders/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/amdb/infrastructure/senders/email.py b/src/amdb/infrastructure/senders/email.py
new file mode 100644
index 0000000..db4d294
--- /dev/null
+++ b/src/amdb/infrastructure/senders/email.py
@@ -0,0 +1,14 @@
+from typing import Protocol
+
+from amdb.application.common.entities.file import File
+
+
+class SendFakeEmail(Protocol):
+ def __call__(
+ self,
+ *,
+ email: str,
+ subject: str,
+ files: list[File],
+ ) -> None:
+ ...
diff --git a/src/amdb/infrastructure/task_queue/__init__.py b/src/amdb/infrastructure/task_queue/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/amdb/infrastructure/task_queue/export_and_send_my_ratings.py b/src/amdb/infrastructure/task_queue/export_and_send_my_ratings.py
new file mode 100644
index 0000000..b6a91ab
--- /dev/null
+++ b/src/amdb/infrastructure/task_queue/export_and_send_my_ratings.py
@@ -0,0 +1,14 @@
+from amdb.domain.entities.user import UserId
+from amdb.application.common.constants.export import ExportFormat
+from amdb.application.common.constants.sending import SendingMethod
+
+
+class EnqueueFakeExportAndSendingMyRatings:
+ def __call__(
+ self,
+ *,
+ user_id: UserId,
+ export_format: ExportFormat,
+ sending_method: SendingMethod,
+ ) -> None:
+ ...
From 1c9625abe2cf8bd7f03e9d4410a7fd1992754efd Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Wed, 6 Mar 2024 15:33:37 +0400
Subject: [PATCH 23/39] Update dishka version to 0.6.0, refactor
---
pyproject.toml | 2 +-
src/amdb/presentation/web_api/auth/login.py | 10 +++++-----
src/amdb/presentation/web_api/auth/register.py | 10 +++++-----
src/amdb/presentation/web_api/exports/my_ratings.py | 8 ++++----
.../presentation/web_api/exports/request_my_ratings.py | 8 ++++----
src/amdb/presentation/web_api/movies/get_detailed.py | 8 ++++----
.../presentation/web_api/movies/get_non_detailed.py | 8 ++++----
src/amdb/presentation/web_api/profiles/update_my.py | 10 +++++-----
.../presentation/web_api/ratings/get_my_detailed.py | 8 ++++----
src/amdb/presentation/web_api/ratings/rate_movie.py | 8 ++++----
src/amdb/presentation/web_api/ratings/unrate_movie.py | 8 ++++----
src/amdb/presentation/web_api/reviews/get_detailed.py | 4 ++--
src/amdb/presentation/web_api/reviews/review_movie.py | 8 ++++----
13 files changed, 50 insertions(+), 50 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 1adc221..0e653d6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -26,7 +26,7 @@ maintainers = [
dependencies = [
"uuid7==0.1.*",
"toml==0.10.*",
- "dishka==0.4.*",
+ "dishka==0.6.*",
"sqlalchemy==2.0.*",
"psycopg2-binary==2.9.*",
"alembic==1.13.*",
diff --git a/src/amdb/presentation/web_api/auth/login.py b/src/amdb/presentation/web_api/auth/login.py
index 7477686..c9fd441 100644
--- a/src/amdb/presentation/web_api/auth/login.py
+++ b/src/amdb/presentation/web_api/auth/login.py
@@ -2,7 +2,7 @@
from datetime import datetime, timezone
from fastapi import Response
-from dishka.integrations.fastapi import Depends, inject
+from dishka.integrations.fastapi import FromDishka, inject
from amdb.domain.entities.user import UserId
from amdb.application.queries.login import LoginQuery
@@ -16,10 +16,10 @@
@inject
async def login(
*,
- handler: Annotated[LoginHandler, Depends()],
- session_processor: Annotated[SessionProcessor, Depends()],
- session_mapper: Annotated[SessionMapper, Depends()],
- session_config: Annotated[SessionConfig, Depends()],
+ handler: Annotated[LoginHandler, FromDishka()],
+ session_processor: Annotated[SessionProcessor, FromDishka()],
+ session_mapper: Annotated[SessionMapper, FromDishka()],
+ session_config: Annotated[SessionConfig, FromDishka()],
query: LoginQuery,
response: Response,
) -> UserId:
diff --git a/src/amdb/presentation/web_api/auth/register.py b/src/amdb/presentation/web_api/auth/register.py
index e73517f..4595177 100644
--- a/src/amdb/presentation/web_api/auth/register.py
+++ b/src/amdb/presentation/web_api/auth/register.py
@@ -2,7 +2,7 @@
from datetime import datetime, timezone
from fastapi import Response
-from dishka.integrations.fastapi import Depends, inject
+from dishka.integrations.fastapi import FromDishka, inject
from amdb.domain.entities.user import UserId
from amdb.application.commands.register_user import RegisterUserCommand
@@ -16,10 +16,10 @@
@inject
async def register(
*,
- handler: Annotated[RegisterUserHandler, Depends()],
- session_processor: Annotated[SessionProcessor, Depends()],
- session_mapper: Annotated[SessionMapper, Depends()],
- session_config: Annotated[SessionConfig, Depends()],
+ handler: Annotated[RegisterUserHandler, FromDishka()],
+ session_processor: Annotated[SessionProcessor, FromDishka()],
+ session_mapper: Annotated[SessionMapper, FromDishka()],
+ session_config: Annotated[SessionConfig, FromDishka()],
command: RegisterUserCommand,
response: Response,
) -> UserId:
diff --git a/src/amdb/presentation/web_api/exports/my_ratings.py b/src/amdb/presentation/web_api/exports/my_ratings.py
index 54d3f90..22796a9 100644
--- a/src/amdb/presentation/web_api/exports/my_ratings.py
+++ b/src/amdb/presentation/web_api/exports/my_ratings.py
@@ -2,7 +2,7 @@
from fastapi import Cookie
from fastapi.responses import StreamingResponse
-from dishka.integrations.fastapi import Depends, inject
+from dishka.integrations.fastapi import FromDishka, inject
from amdb.application.common.constants.export import ExportFormat
from amdb.application.queries.export_my_ratings import ExportMyRatingsQuery
@@ -25,9 +25,9 @@
@inject
async def export_my_ratings(
*,
- create_handler: Annotated[HandlerCreator, Depends()],
- session_gateway: Annotated[SessionGateway, Depends()],
- permissions_gateway: Annotated[PermissionsGateway, Depends()],
+ create_handler: Annotated[HandlerCreator, FromDishka()],
+ session_gateway: Annotated[SessionGateway, FromDishka()],
+ permissions_gateway: Annotated[PermissionsGateway, FromDishka()],
session_id: Annotated[
Optional[str],
Cookie(alias=SESSION_ID_COOKIE),
diff --git a/src/amdb/presentation/web_api/exports/request_my_ratings.py b/src/amdb/presentation/web_api/exports/request_my_ratings.py
index 23014f3..13b27c6 100644
--- a/src/amdb/presentation/web_api/exports/request_my_ratings.py
+++ b/src/amdb/presentation/web_api/exports/request_my_ratings.py
@@ -1,7 +1,7 @@
from typing import Annotated, Optional
from fastapi import Cookie
-from dishka.integrations.fastapi import Depends, inject
+from dishka.integrations.fastapi import FromDishka, inject
from amdb.application.queries.request_my_ratings_export import (
RequestMyRatingsExportQuery,
@@ -25,9 +25,9 @@
@inject
async def request_my_ratings_export(
*,
- create_handler: Annotated[HandlerCreator, Depends()],
- session_gateway: Annotated[SessionGateway, Depends()],
- permissions_gateway: Annotated[PermissionsGateway, Depends()],
+ create_handler: Annotated[HandlerCreator, FromDishka()],
+ session_gateway: Annotated[SessionGateway, FromDishka()],
+ permissions_gateway: Annotated[PermissionsGateway, FromDishka()],
session_id: Annotated[
Optional[str],
Cookie(alias=SESSION_ID_COOKIE),
diff --git a/src/amdb/presentation/web_api/movies/get_detailed.py b/src/amdb/presentation/web_api/movies/get_detailed.py
index a6eefc4..3b9a53f 100644
--- a/src/amdb/presentation/web_api/movies/get_detailed.py
+++ b/src/amdb/presentation/web_api/movies/get_detailed.py
@@ -1,7 +1,7 @@
from typing import Annotated, Optional
from fastapi import Cookie
-from dishka.integrations.fastapi import Depends, inject
+from dishka.integrations.fastapi import FromDishka, inject
from amdb.domain.entities.movie import MovieId
from amdb.application.common.view_models.detailed_movie import (
@@ -27,9 +27,9 @@
@inject
async def get_detailed_movie(
*,
- create_handler: Annotated[HandlerCreator, Depends()],
- session_gateway: Annotated[SessionGateway, Depends()],
- permissions_gateway: Annotated[PermissionsGateway, Depends()],
+ create_handler: Annotated[HandlerCreator, FromDishka()],
+ session_gateway: Annotated[SessionGateway, FromDishka()],
+ permissions_gateway: Annotated[PermissionsGateway, FromDishka()],
session_id: Annotated[
Optional[str],
Cookie(alias=SESSION_ID_COOKIE),
diff --git a/src/amdb/presentation/web_api/movies/get_non_detailed.py b/src/amdb/presentation/web_api/movies/get_non_detailed.py
index bf4752b..c2ce3d7 100644
--- a/src/amdb/presentation/web_api/movies/get_non_detailed.py
+++ b/src/amdb/presentation/web_api/movies/get_non_detailed.py
@@ -1,7 +1,7 @@
from typing import Annotated, Optional
from fastapi import Cookie
-from dishka.integrations.fastapi import Depends, inject
+from dishka.integrations.fastapi import FromDishka, inject
from amdb.application.common.view_models.non_detailed_movie import (
NonDetailedMovieViewModel,
@@ -28,9 +28,9 @@
@inject
async def get_non_detailed_movies(
*,
- create_handler: Annotated[HandlerCreator, Depends()],
- session_gateway: Annotated[SessionGateway, Depends()],
- permissions_gateway: Annotated[PermissionsGateway, Depends()],
+ create_handler: Annotated[HandlerCreator, FromDishka()],
+ session_gateway: Annotated[SessionGateway, FromDishka()],
+ permissions_gateway: Annotated[PermissionsGateway, FromDishka()],
session_id: Annotated[
Optional[str],
Cookie(alias=SESSION_ID_COOKIE),
diff --git a/src/amdb/presentation/web_api/profiles/update_my.py b/src/amdb/presentation/web_api/profiles/update_my.py
index 6cddafd..9f87b3e 100644
--- a/src/amdb/presentation/web_api/profiles/update_my.py
+++ b/src/amdb/presentation/web_api/profiles/update_my.py
@@ -1,7 +1,7 @@
from typing import Annotated, Optional
from fastapi import Cookie
-from dishka.integrations.fastapi import Depends, inject
+from dishka.integrations.fastapi import FromDishka, inject
from amdb.application.commands.update_my_profile import UpdateMyProfileCommand
from amdb.application.command_handlers.update_my_profile import (
UpdateMyProfileHandler,
@@ -22,9 +22,9 @@
@inject
async def update_my_profile(
*,
- create_handler: Annotated[HandlerCreator, Depends()],
- session_gateway: Annotated[SessionGateway, Depends()],
- permissions_gateway: Annotated[PermissionsGateway, Depends()],
+ create_handler: Annotated[HandlerCreator, FromDishka()],
+ session_gateway: Annotated[SessionGateway, FromDishka()],
+ permissions_gateway: Annotated[PermissionsGateway, FromDishka()],
session_id: Annotated[
Optional[str],
Cookie(alias=SESSION_ID_COOKIE),
@@ -34,7 +34,7 @@ async def update_my_profile(
"""
Updates current user profile.\n\n
- ####Returns 400:
+ #### Returns 400:
* When email is invalid
"""
identity_provider = SessionIdentityProvider(
diff --git a/src/amdb/presentation/web_api/ratings/get_my_detailed.py b/src/amdb/presentation/web_api/ratings/get_my_detailed.py
index 50bad45..285b22c 100644
--- a/src/amdb/presentation/web_api/ratings/get_my_detailed.py
+++ b/src/amdb/presentation/web_api/ratings/get_my_detailed.py
@@ -1,7 +1,7 @@
from typing import Annotated, Optional
from fastapi import Cookie
-from dishka.integrations.fastapi import Depends, inject
+from dishka.integrations.fastapi import FromDishka, inject
from amdb.application.common.view_models.my_detailed_ratings import (
MyDetailedRatingsViewModel,
@@ -28,9 +28,9 @@
@inject
async def get_my_detailed_ratings(
*,
- create_handler: Annotated[HandlerCreator, Depends()],
- session_gateway: Annotated[SessionGateway, Depends()],
- permissions_gateway: Annotated[PermissionsGateway, Depends()],
+ create_handler: Annotated[HandlerCreator, FromDishka()],
+ session_gateway: Annotated[SessionGateway, FromDishka()],
+ permissions_gateway: Annotated[PermissionsGateway, FromDishka()],
session_id: Annotated[
Optional[str],
Cookie(alias=SESSION_ID_COOKIE),
diff --git a/src/amdb/presentation/web_api/ratings/rate_movie.py b/src/amdb/presentation/web_api/ratings/rate_movie.py
index f824a98..11a0e76 100644
--- a/src/amdb/presentation/web_api/ratings/rate_movie.py
+++ b/src/amdb/presentation/web_api/ratings/rate_movie.py
@@ -1,7 +1,7 @@
from typing import Annotated, Optional
from fastapi import Cookie
-from dishka.integrations.fastapi import Depends, inject
+from dishka.integrations.fastapi import FromDishka, inject
from amdb.domain.entities.rating import RatingId
from amdb.application.commands.rate_movie import RateMovieCommand
@@ -22,9 +22,9 @@
@inject
async def rate_movie(
*,
- create_handler: Annotated[HandlerCreator, Depends()],
- session_gateway: Annotated[SessionGateway, Depends()],
- permissions_gateway: Annotated[PermissionsGateway, Depends()],
+ create_handler: Annotated[HandlerCreator, FromDishka()],
+ session_gateway: Annotated[SessionGateway, FromDishka()],
+ permissions_gateway: Annotated[PermissionsGateway, FromDishka()],
session_id: Annotated[
Optional[str],
Cookie(alias=SESSION_ID_COOKIE),
diff --git a/src/amdb/presentation/web_api/ratings/unrate_movie.py b/src/amdb/presentation/web_api/ratings/unrate_movie.py
index 44fbb37..59e2c56 100644
--- a/src/amdb/presentation/web_api/ratings/unrate_movie.py
+++ b/src/amdb/presentation/web_api/ratings/unrate_movie.py
@@ -1,7 +1,7 @@
from typing import Annotated, Optional
from fastapi import Cookie
-from dishka.integrations.fastapi import Depends, inject
+from dishka.integrations.fastapi import FromDishka, inject
from amdb.domain.entities.rating import RatingId
from amdb.application.commands.unrate_movie import UnrateMovieCommand
@@ -22,9 +22,9 @@
@inject
async def unrate_movie(
*,
- create_handler: Annotated[HandlerCreator, Depends()],
- session_gateway: Annotated[SessionGateway, Depends()],
- permissions_gateway: Annotated[PermissionsGateway, Depends()],
+ create_handler: Annotated[HandlerCreator, FromDishka()],
+ session_gateway: Annotated[SessionGateway, FromDishka()],
+ permissions_gateway: Annotated[PermissionsGateway, FromDishka()],
session_id: Annotated[
Optional[str],
Cookie(alias=SESSION_ID_COOKIE),
diff --git a/src/amdb/presentation/web_api/reviews/get_detailed.py b/src/amdb/presentation/web_api/reviews/get_detailed.py
index 454ba64..c5b64c7 100644
--- a/src/amdb/presentation/web_api/reviews/get_detailed.py
+++ b/src/amdb/presentation/web_api/reviews/get_detailed.py
@@ -1,6 +1,6 @@
from typing import Annotated
-from dishka.integrations.fastapi import Depends, inject
+from dishka.integrations.fastapi import FromDishka, inject
from amdb.domain.entities.movie import MovieId
from amdb.application.common.view_models.detailed_review import (
@@ -15,7 +15,7 @@
@inject
async def get_detailed_reviews(
*,
- handler: Annotated[GetDetailedReviewsHandler, Depends()],
+ handler: Annotated[GetDetailedReviewsHandler, FromDishka()],
movie_id: MovieId,
limit: int = 100,
offset: int = 0,
diff --git a/src/amdb/presentation/web_api/reviews/review_movie.py b/src/amdb/presentation/web_api/reviews/review_movie.py
index e28f471..5498a21 100644
--- a/src/amdb/presentation/web_api/reviews/review_movie.py
+++ b/src/amdb/presentation/web_api/reviews/review_movie.py
@@ -1,7 +1,7 @@
from typing import Annotated, Optional
from fastapi import Cookie
-from dishka.integrations.fastapi import Depends, inject
+from dishka.integrations.fastapi import FromDishka, inject
from amdb.domain.entities.review import ReviewId
from amdb.application.commands.review_movie import ReviewMovieCommand
@@ -22,9 +22,9 @@
@inject
async def review_movie(
*,
- create_handler: Annotated[HandlerCreator, Depends()],
- session_gateway: Annotated[SessionGateway, Depends()],
- permissions_gateway: Annotated[PermissionsGateway, Depends()],
+ create_handler: Annotated[HandlerCreator, FromDishka()],
+ session_gateway: Annotated[SessionGateway, FromDishka()],
+ permissions_gateway: Annotated[PermissionsGateway, FromDishka()],
session_id: Annotated[
Optional[str],
Cookie(alias=SESSION_ID_COOKIE),
From e730fec7983eeb07ffc1e514b44df4d96e427a3e Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Wed, 6 Mar 2024 15:47:37 +0400
Subject: [PATCH 24/39] Rename export endpoints
---
src/amdb/presentation/web_api/exports/request_my_ratings.py | 2 +-
src/amdb/presentation/web_api/exports/router.py | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/amdb/presentation/web_api/exports/request_my_ratings.py b/src/amdb/presentation/web_api/exports/request_my_ratings.py
index 13b27c6..ece5de0 100644
--- a/src/amdb/presentation/web_api/exports/request_my_ratings.py
+++ b/src/amdb/presentation/web_api/exports/request_my_ratings.py
@@ -38,7 +38,7 @@ async def request_my_ratings_export(
Sends file of specified format with current user ratings using
specified sending method.\n\n
- ####Returns 400:
+ #### Returns 400:
* When email sending method was passed and user has no email
"""
identity_provider = SessionIdentityProvider(
diff --git a/src/amdb/presentation/web_api/exports/router.py b/src/amdb/presentation/web_api/exports/router.py
index e6c3649..c7adec5 100644
--- a/src/amdb/presentation/web_api/exports/router.py
+++ b/src/amdb/presentation/web_api/exports/router.py
@@ -10,13 +10,13 @@
tags=["exports"],
)
exports_router.add_api_route(
- path="/exports/my-ratings",
+ path="/my-ratings-export",
endpoint=export_my_ratings,
methods=["GET"],
response_class=StreamingResponse,
)
exports_router.add_api_route(
- path="/export-requests/my-ratings",
+ path="/my-ratings-export-requests",
endpoint=request_my_ratings_export,
methods=["POST"],
)
From 8bd0bad7fd0480b4ac53c3448bb2af8f069cb316 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Wed, 6 Mar 2024 15:50:31 +0400
Subject: [PATCH 25/39] Refactor
---
src/amdb/presentation/create_handler.py | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/src/amdb/presentation/create_handler.py b/src/amdb/presentation/create_handler.py
index a408fdc..c737f67 100644
--- a/src/amdb/presentation/create_handler.py
+++ b/src/amdb/presentation/create_handler.py
@@ -1,16 +1,14 @@
-__all__ = ("CreateHandler",)
-
from typing import TypeVar, Protocol
from amdb.application.common.identity_provider import IdentityProvider
-H = TypeVar("H", covariant=True)
+_H = TypeVar("_H", covariant=True)
-class CreateHandler(Protocol[H]):
+class CreateHandler(Protocol[_H]):
def __call__(
self,
identity_provider: IdentityProvider,
- ) -> H:
+ ) -> _H:
raise NotImplementedError
From c6d65fbddd82b8717dc43e614bb7ec9b9e8d815b Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Wed, 6 Mar 2024 16:37:25 +0400
Subject: [PATCH 26/39] Fix export my ratings test
---
.../application/query_handlers/test_export_my_ratings.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/tests/unit/application/query_handlers/test_export_my_ratings.py b/tests/unit/application/query_handlers/test_export_my_ratings.py
index 9ea0310..9cad525 100644
--- a/tests/unit/application/query_handlers/test_export_my_ratings.py
+++ b/tests/unit/application/query_handlers/test_export_my_ratings.py
@@ -6,6 +6,9 @@
from amdb.domain.entities.user import User, UserId
from amdb.domain.entities.movie import Movie, MovieId
from amdb.domain.entities.rating import Rating, RatingId
+from amdb.application.common.services.convert_to_file import (
+ ConvertMyRatingsToFile,
+)
from amdb.application.common.constants.export import ExportFormat
from amdb.application.common.gateways.user import UserGateway
from amdb.application.common.gateways.movie import MovieGateway
@@ -65,8 +68,10 @@ def test_export_my_ratings_in_csv(
query = ExportMyRatingsQuery(format=ExportFormat.CSV)
handler = ExportMyRatingsHandler(
+ convert_my_ratings_to_file=ConvertMyRatingsToFile(
+ converter=RealRatingsForExportConverter(),
+ ),
ratings_for_export_reader=ratings_for_export_reader,
- ratings_for_export_converter=RealRatingsForExportConverter(),
identity_provider=identity_provider,
)
From 565b36061e1be668455b137a5e923eb4f0d55013 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Wed, 6 Mar 2024 18:23:08 +0400
Subject: [PATCH 27/39] Refactor sqlalchemy models and mappers
---
.../persistence/sqlalchemy/mappers/entities/movie.py | 5 +----
.../persistence/sqlalchemy/mappers/entities/rating.py | 2 +-
.../persistence/sqlalchemy/mappers/entities/review.py | 2 +-
.../persistence/sqlalchemy/mappers/entities/user.py | 2 +-
.../infrastructure/persistence/sqlalchemy/models/rating.py | 7 +------
.../infrastructure/persistence/sqlalchemy/models/review.py | 7 +------
6 files changed, 6 insertions(+), 19 deletions(-)
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/movie.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/movie.py
index 67ca571..5f43986 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/movie.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/movie.py
@@ -40,10 +40,7 @@ def delete(self, movie: Movie) -> None:
statement = delete(MovieModel).where(MovieModel.id == movie.id)
self._connection.execute(statement)
- def _to_entity(
- self,
- row: Annotated[MovieModel, Row[tuple[MovieModel]]],
- ) -> Movie:
+ def _to_entity(self, row: Annotated[MovieModel, Row]) -> Movie:
return Movie(
id=MovieId(row.id),
title=row.title,
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/rating.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/rating.py
index 846e8f5..c6523fa 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/rating.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/rating.py
@@ -57,7 +57,7 @@ def delete_with_movie_id(self, movie_id: MovieId) -> None:
def _to_entity(
self,
- row: Annotated[RatingModel, Row[tuple[RatingModel]]],
+ row: Annotated[RatingModel, Row],
) -> Rating:
return Rating(
id=RatingId(row.id),
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/review.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/review.py
index d08255d..7c9612a 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/review.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/review.py
@@ -55,7 +55,7 @@ def delete_with_movie_id(self, movie_id: MovieId) -> None:
def _to_entity(
self,
- row: Annotated[ReviewModel, Row[tuple[ReviewModel]]],
+ row: Annotated[ReviewModel, Row],
) -> Review:
return Review(
id=ReviewId(row.id),
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/user.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/user.py
index 8b58f30..b1f44a9 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/user.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/user.py
@@ -45,7 +45,7 @@ def update(self, user: User) -> None:
def _to_entity(
self,
- row: Annotated[UserModel, Row[tuple[UserModel]]],
+ row: Annotated[UserModel, Row],
) -> User:
return User(
id=UserId(row.id),
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/models/rating.py b/src/amdb/infrastructure/persistence/sqlalchemy/models/rating.py
index baaab42..deadce6 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/models/rating.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/models/rating.py
@@ -2,11 +2,9 @@
from uuid import UUID
from sqlalchemy import ForeignKey, UniqueConstraint
-from sqlalchemy.orm import Mapped, mapped_column, relationship
+from sqlalchemy.orm import Mapped, mapped_column
from .base import Model
-from .user import UserModel
-from .movie import MovieModel
class RatingModel(Model):
@@ -24,7 +22,4 @@ class RatingModel(Model):
value: Mapped[float]
created_at: Mapped[datetime]
- movie: Mapped[MovieModel] = relationship()
- user: Mapped[UserModel] = relationship()
-
__table_args__ = (UniqueConstraint("user_id", "movie_id"),)
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/models/review.py b/src/amdb/infrastructure/persistence/sqlalchemy/models/review.py
index 37d3221..79c78fe 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/models/review.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/models/review.py
@@ -2,11 +2,9 @@
from uuid import UUID
from sqlalchemy import ForeignKey
-from sqlalchemy.orm import Mapped, mapped_column, relationship
+from sqlalchemy.orm import Mapped, mapped_column
from .base import Model
-from .user import UserModel
-from .movie import MovieModel
class ReviewModel(Model):
@@ -25,6 +23,3 @@ class ReviewModel(Model):
content: Mapped[str]
type: Mapped[str]
created_at: Mapped[datetime]
-
- user: Mapped[UserModel] = relationship()
- movie: Mapped[MovieModel] = relationship()
From 152303631ef25ec7e6500a52c3be472b6af2a1a2 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Fri, 8 Mar 2024 11:37:43 +0400
Subject: [PATCH 28/39] Refactor dishka providers, refactor imports
---
.../application/command_handlers/__init__.py | 17 +
.../{converters => converting}/__init__.py | 0
.../rating_for_export.py | 0
.../application/common/gateways/__init__.py | 13 +
.../application/common/readers/__init__.py | 13 +
.../common/{senders => sending}/__init__.py | 0
.../common/{senders => sending}/email.py | 0
.../application/common/services/__init__.py | 7 +
.../common/services/convert_to_file.py | 2 +-
...se_sending_method.py => ensure_can_use.py} | 0
.../application/query_handlers/__init__.py | 19 +
.../export_and_send_my_ratings.py | 4 +-
.../request_my_ratings_export.py | 2 +-
src/amdb/domain/services/__init__.py | 17 +
src/amdb/domain/services/update_profile.py | 1 -
.../{converters => converting}/__init__.py | 0
.../ratings_for_export.py | 0
.../sqlalchemy/mappers/__init__.py | 25 +
.../{senders => sending}/__init__.py | 0
.../{senders => sending}/email.py | 0
src/amdb/main/providers.py | 459 ------------------
src/amdb/main/providers/__init__.py | 41 ++
.../main/providers/application_services.py | 19 +
src/amdb/main/providers/command_handlers.py | 184 +++++++
src/amdb/main/providers/configs.py | 24 +
src/amdb/main/providers/connections.py | 33 ++
src/amdb/main/providers/converting.py | 17 +
src/amdb/main/providers/data_mappers.py | 97 ++++
src/amdb/main/providers/domain_services.py | 23 +
src/amdb/main/providers/domain_validators.py | 9 +
src/amdb/main/providers/password_manager.py | 22 +
src/amdb/main/providers/query_handlers.py | 170 +++++++
src/amdb/main/providers/sending.py | 10 +
src/amdb/main/providers/task_queue.py | 17 +
src/amdb/main/web_api/app.py | 38 +-
tests/unit/application/conftest.py | 30 +-
.../query_handlers/test_export_my_ratings.py | 2 +-
.../test_request_my_ratings_export.py | 2 +-
38 files changed, 819 insertions(+), 498 deletions(-)
rename src/amdb/application/common/{converters => converting}/__init__.py (100%)
rename src/amdb/application/common/{converters => converting}/rating_for_export.py (100%)
rename src/amdb/application/common/{senders => sending}/__init__.py (100%)
rename src/amdb/application/common/{senders => sending}/email.py (100%)
rename src/amdb/application/common/services/{ensure_can_use_sending_method.py => ensure_can_use.py} (100%)
rename src/amdb/infrastructure/{converters => converting}/__init__.py (100%)
rename src/amdb/infrastructure/{converters => converting}/ratings_for_export.py (100%)
rename src/amdb/infrastructure/{senders => sending}/__init__.py (100%)
rename src/amdb/infrastructure/{senders => sending}/email.py (100%)
delete mode 100644 src/amdb/main/providers.py
create mode 100644 src/amdb/main/providers/__init__.py
create mode 100644 src/amdb/main/providers/application_services.py
create mode 100644 src/amdb/main/providers/command_handlers.py
create mode 100644 src/amdb/main/providers/configs.py
create mode 100644 src/amdb/main/providers/connections.py
create mode 100644 src/amdb/main/providers/converting.py
create mode 100644 src/amdb/main/providers/data_mappers.py
create mode 100644 src/amdb/main/providers/domain_services.py
create mode 100644 src/amdb/main/providers/domain_validators.py
create mode 100644 src/amdb/main/providers/password_manager.py
create mode 100644 src/amdb/main/providers/query_handlers.py
create mode 100644 src/amdb/main/providers/sending.py
create mode 100644 src/amdb/main/providers/task_queue.py
diff --git a/src/amdb/application/command_handlers/__init__.py b/src/amdb/application/command_handlers/__init__.py
index e69de29..9315147 100644
--- a/src/amdb/application/command_handlers/__init__.py
+++ b/src/amdb/application/command_handlers/__init__.py
@@ -0,0 +1,17 @@
+__all__ = (
+ "RegisterUserHandler",
+ "UpdateMyProfileHandler",
+ "CreateMovieHandler",
+ "DeleteMovieHandler",
+ "RateMovieHandler",
+ "UnrateMovieHandler",
+ "ReviewMovieHandler",
+)
+
+from .register_user import RegisterUserHandler
+from .update_my_profile import UpdateMyProfileHandler
+from .create_movie import CreateMovieHandler
+from .delete_movie import DeleteMovieHandler
+from .rate_movie import RateMovieHandler
+from .unrate_movie import UnrateMovieHandler
+from .review_movie import ReviewMovieHandler
diff --git a/src/amdb/application/common/converters/__init__.py b/src/amdb/application/common/converting/__init__.py
similarity index 100%
rename from src/amdb/application/common/converters/__init__.py
rename to src/amdb/application/common/converting/__init__.py
diff --git a/src/amdb/application/common/converters/rating_for_export.py b/src/amdb/application/common/converting/rating_for_export.py
similarity index 100%
rename from src/amdb/application/common/converters/rating_for_export.py
rename to src/amdb/application/common/converting/rating_for_export.py
diff --git a/src/amdb/application/common/gateways/__init__.py b/src/amdb/application/common/gateways/__init__.py
index e69de29..79c5c57 100644
--- a/src/amdb/application/common/gateways/__init__.py
+++ b/src/amdb/application/common/gateways/__init__.py
@@ -0,0 +1,13 @@
+__all__ = (
+ "UserGateway",
+ "MovieGateway",
+ "RatingGateway",
+ "ReviewGateway",
+ "PermissionsGateway",
+)
+
+from .user import UserGateway
+from .movie import MovieGateway
+from .rating import RatingGateway
+from .review import ReviewGateway
+from .permissions import PermissionsGateway
diff --git a/src/amdb/application/common/readers/__init__.py b/src/amdb/application/common/readers/__init__.py
index e69de29..771f12d 100644
--- a/src/amdb/application/common/readers/__init__.py
+++ b/src/amdb/application/common/readers/__init__.py
@@ -0,0 +1,13 @@
+__all__ = (
+ "DetailedMovieViewModelReader",
+ "DetailedReviewViewModelsReader",
+ "RatingForExportViewModelsReader",
+ "NonDetailedMovieViewModelsReader",
+ "MyDetailedRatingsViewModelReader",
+)
+
+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
diff --git a/src/amdb/application/common/senders/__init__.py b/src/amdb/application/common/sending/__init__.py
similarity index 100%
rename from src/amdb/application/common/senders/__init__.py
rename to src/amdb/application/common/sending/__init__.py
diff --git a/src/amdb/application/common/senders/email.py b/src/amdb/application/common/sending/email.py
similarity index 100%
rename from src/amdb/application/common/senders/email.py
rename to src/amdb/application/common/sending/email.py
diff --git a/src/amdb/application/common/services/__init__.py b/src/amdb/application/common/services/__init__.py
index e69de29..e2809d7 100644
--- a/src/amdb/application/common/services/__init__.py
+++ b/src/amdb/application/common/services/__init__.py
@@ -0,0 +1,7 @@
+__all__ = (
+ "ConvertMyRatingsToFile",
+ "EnsureCanUseSendingMethod",
+)
+
+from .convert_to_file import ConvertMyRatingsToFile
+from .ensure_can_use import EnsureCanUseSendingMethod
diff --git a/src/amdb/application/common/services/convert_to_file.py b/src/amdb/application/common/services/convert_to_file.py
index b47a68b..77f13ba 100644
--- a/src/amdb/application/common/services/convert_to_file.py
+++ b/src/amdb/application/common/services/convert_to_file.py
@@ -1,6 +1,6 @@
from amdb.application.common.constants.export import ExportFormat
from amdb.application.common.entities.file import File
-from amdb.application.common.converters.rating_for_export import (
+from amdb.application.common.converting.rating_for_export import (
RatingsForExportConverter,
)
from amdb.application.common.view_models.rating_for_export import (
diff --git a/src/amdb/application/common/services/ensure_can_use_sending_method.py b/src/amdb/application/common/services/ensure_can_use.py
similarity index 100%
rename from src/amdb/application/common/services/ensure_can_use_sending_method.py
rename to src/amdb/application/common/services/ensure_can_use.py
diff --git a/src/amdb/application/query_handlers/__init__.py b/src/amdb/application/query_handlers/__init__.py
index e69de29..5734721 100644
--- a/src/amdb/application/query_handlers/__init__.py
+++ b/src/amdb/application/query_handlers/__init__.py
@@ -0,0 +1,19 @@
+__all__ = (
+ "LoginHandler",
+ "GetDetailedMovieHandler",
+ "GetDetailedReviewsHandler",
+ "ExportMyRatingsHandler",
+ "RequestMyRatingsExportHandler",
+ "ExportAndSendMyRatingsHandler",
+ "GetMyDetailedRatingsHandler",
+ "GetNonDetailedMoviesHandler",
+)
+
+from .login import LoginHandler
+from .detailed_movie import GetDetailedMovieHandler
+from .detailed_reviews import GetDetailedReviewsHandler
+from .export_my_ratings import ExportMyRatingsHandler
+from .request_my_ratings_export import RequestMyRatingsExportHandler
+from .export_and_send_my_ratings import ExportAndSendMyRatingsHandler
+from .my_detailed_ratings import GetMyDetailedRatingsHandler
+from .non_detailed_movies import GetNonDetailedMoviesHandler
diff --git a/src/amdb/application/query_handlers/export_and_send_my_ratings.py b/src/amdb/application/query_handlers/export_and_send_my_ratings.py
index 2966b55..cf53ebd 100644
--- a/src/amdb/application/query_handlers/export_and_send_my_ratings.py
+++ b/src/amdb/application/query_handlers/export_and_send_my_ratings.py
@@ -1,7 +1,7 @@
from amdb.domain.entities.user import User
from amdb.application.common.constants.sending import SendingMethod
from amdb.application.common.entities.file import File
-from amdb.application.common.services.ensure_can_use_sending_method import (
+from amdb.application.common.services.ensure_can_use import (
EnsureCanUseSendingMethod,
)
from amdb.application.common.services.convert_to_file import (
@@ -11,7 +11,7 @@
from amdb.application.common.readers.rating_for_export import (
RatingForExportViewModelsReader,
)
-from amdb.application.common.senders.email import SendEmail
+from amdb.application.common.sending.email import SendEmail
from amdb.application.common.constants.exceptions import USER_DOES_NOT_EXIST
from amdb.application.common.exception import ApplicationError
from amdb.application.queries.export_and_send_my_ratings import (
diff --git a/src/amdb/application/query_handlers/request_my_ratings_export.py b/src/amdb/application/query_handlers/request_my_ratings_export.py
index 2e3eb63..2e3e6a7 100644
--- a/src/amdb/application/query_handlers/request_my_ratings_export.py
+++ b/src/amdb/application/query_handlers/request_my_ratings_export.py
@@ -1,7 +1,7 @@
from typing import cast
from amdb.domain.entities.user import User
-from amdb.application.common.services.ensure_can_use_sending_method import (
+from amdb.application.common.services.ensure_can_use import (
EnsureCanUseSendingMethod,
)
from amdb.application.common.gateways.user import UserGateway
diff --git a/src/amdb/domain/services/__init__.py b/src/amdb/domain/services/__init__.py
index e69de29..d9a39f6 100644
--- a/src/amdb/domain/services/__init__.py
+++ b/src/amdb/domain/services/__init__.py
@@ -0,0 +1,17 @@
+__all__ = (
+ "AccessConcern",
+ "CreateUser",
+ "UpdateProfile",
+ "CreateMovie",
+ "RateMovie",
+ "UnrateMovie",
+ "ReviewMovie",
+)
+
+from .access_concern import AccessConcern
+from .create_user import CreateUser
+from .update_profile import UpdateProfile
+from .create_movie import CreateMovie
+from .rate_movie import RateMovie
+from .unrate_movie import UnrateMovie
+from .review_movie import ReviewMovie
diff --git a/src/amdb/domain/services/update_profile.py b/src/amdb/domain/services/update_profile.py
index a862bdb..fba3921 100644
--- a/src/amdb/domain/services/update_profile.py
+++ b/src/amdb/domain/services/update_profile.py
@@ -7,7 +7,6 @@
class UpdateProfile:
def __init__(
self,
- *,
validate_email: ValidateEmail,
) -> None:
self._validate_email = validate_email
diff --git a/src/amdb/infrastructure/converters/__init__.py b/src/amdb/infrastructure/converting/__init__.py
similarity index 100%
rename from src/amdb/infrastructure/converters/__init__.py
rename to src/amdb/infrastructure/converting/__init__.py
diff --git a/src/amdb/infrastructure/converters/ratings_for_export.py b/src/amdb/infrastructure/converting/ratings_for_export.py
similarity index 100%
rename from src/amdb/infrastructure/converters/ratings_for_export.py
rename to src/amdb/infrastructure/converting/ratings_for_export.py
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/__init__.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/__init__.py
index e69de29..d9977a7 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/__init__.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/__init__.py
@@ -0,0 +1,25 @@
+__all__ = (
+ "UserMapper",
+ "MovieMapper",
+ "RatingMapper",
+ "ReviewMapper",
+ "DetailedMovieViewModelMapper",
+ "DetailedReviewViewModelsMapper",
+ "RatingForExportViewModelMapper",
+ "NonDetailedMovieViewModelsMapper",
+ "MyDetailedRatingsViewModelMapper",
+ "PermissionsMapper",
+ "PasswordHashMapper",
+)
+
+from .entities.user import UserMapper
+from .entities.movie import MovieMapper
+from .entities.rating import RatingMapper
+from .entities.review import ReviewMapper
+from .view_models.detailed_movie import DetailedMovieViewModelMapper
+from .view_models.detailed_review import DetailedReviewViewModelsMapper
+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 .permissions import PermissionsMapper
+from .password_hash import PasswordHashMapper
diff --git a/src/amdb/infrastructure/senders/__init__.py b/src/amdb/infrastructure/sending/__init__.py
similarity index 100%
rename from src/amdb/infrastructure/senders/__init__.py
rename to src/amdb/infrastructure/sending/__init__.py
diff --git a/src/amdb/infrastructure/senders/email.py b/src/amdb/infrastructure/sending/email.py
similarity index 100%
rename from src/amdb/infrastructure/senders/email.py
rename to src/amdb/infrastructure/sending/email.py
diff --git a/src/amdb/main/providers.py b/src/amdb/main/providers.py
deleted file mode 100644
index 4f0e14a..0000000
--- a/src/amdb/main/providers.py
+++ /dev/null
@@ -1,459 +0,0 @@
-from typing import Iterable, cast
-
-from dishka import Provider, Scope, provide, alias
-from sqlalchemy import Connection, Engine, create_engine
-from redis import Redis
-
-from amdb.domain.services.access_concern import AccessConcern
-from amdb.domain.services.create_user import CreateUser
-from amdb.domain.services.update_profile import UpdateProfile
-from amdb.domain.services.create_movie import CreateMovie
-from amdb.domain.services.rate_movie import RateMovie
-from amdb.domain.services.unrate_movie import UnrateMovie
-from amdb.domain.services.review_movie import ReviewMovie
-from amdb.domain.validators.email import ValidateEmail
-from amdb.application.common.services.ensure_can_use_sending_method import (
- EnsureCanUseSendingMethod,
-)
-from amdb.application.common.gateways.user import UserGateway
-from amdb.application.common.gateways.movie import MovieGateway
-from amdb.application.common.gateways.rating import RatingGateway
-from amdb.application.common.gateways.review import ReviewGateway
-from amdb.application.common.gateways.permissions import PermissionsGateway
-from amdb.application.common.unit_of_work import UnitOfWork
-from amdb.application.common.readers.detailed_movie import (
- DetailedMovieViewModelReader,
-)
-from amdb.application.common.readers.non_detailed_movie import (
- NonDetailedMovieViewModelsReader,
-)
-from amdb.application.common.readers.detailed_review import (
- DetailedReviewViewModelsReader,
-)
-from amdb.application.common.readers.my_detailed_ratings import (
- MyDetailedRatingsViewModelReader,
-)
-from amdb.application.common.password_manager import PasswordManager
-from amdb.application.common.identity_provider import IdentityProvider
-from amdb.application.command_handlers.register_user import RegisterUserHandler
-from amdb.application.command_handlers.update_my_profile import (
- UpdateMyProfileHandler,
-)
-from amdb.application.command_handlers.create_movie import CreateMovieHandler
-from amdb.application.command_handlers.delete_movie import DeleteMovieHandler
-from amdb.application.command_handlers.rate_movie import RateMovieHandler
-from amdb.application.command_handlers.unrate_movie import UnrateMovieHandler
-from amdb.application.command_handlers.review_movie import ReviewMovieHandler
-from amdb.application.query_handlers.login import LoginHandler
-from amdb.application.query_handlers.detailed_movie import (
- GetDetailedMovieHandler,
-)
-from amdb.application.query_handlers.non_detailed_movies import (
- GetNonDetailedMoviesHandler,
-)
-from amdb.application.query_handlers.detailed_reviews import (
- GetDetailedReviewsHandler,
-)
-from amdb.application.query_handlers.my_detailed_ratings import (
- GetMyDetailedRatingsHandler,
-)
-from amdb.application.query_handlers.export_my_ratings import (
- ExportMyRatingsHandler,
-)
-from amdb.application.query_handlers.request_my_ratings_export import (
- RequestMyRatingsExportHandler,
-)
-from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
-from amdb.infrastructure.persistence.redis.config import RedisConfig
-from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.user import (
- UserMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.movie import (
- MovieMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.rating import (
- RatingMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.review import (
- ReviewMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.permissions import (
- PermissionsMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.password_hash import (
- PasswordHashMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.detailed_movie import (
- DetailedMovieViewModelMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.non_detailed_movie import (
- NonDetailedMovieViewModelsMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.detailed_review import (
- DetailedReviewViewModelsMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.my_detailed_ratings import (
- MyDetailedRatingsViewModelMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.rating_for_export import (
- RatingForExportViewModelMapper,
-)
-from amdb.infrastructure.persistence.redis.cache.permissions_mapper import (
- PermissionsMapperCacheProvider,
-)
-from amdb.infrastructure.persistence.caching.permissions_mapper import (
- CachingPermissionsMapper,
-)
-from amdb.infrastructure.password_manager.hash_computer import HashComputer
-from amdb.infrastructure.password_manager.password_manager import (
- HashingPasswordManager,
-)
-from amdb.infrastructure.converters.ratings_for_export import (
- RealRatingsForExportConverter,
-)
-from amdb.presentation.create_handler import CreateHandler
-
-
-class ConnectionsProvider(Provider):
- scope = Scope.APP
-
- def __init__(
- self,
- *,
- postgres_config: PostgresConfig,
- redis_config: RedisConfig,
- ) -> None:
- super().__init__()
- self._postgsres_config = postgres_config
- self._redis_config = redis_config
-
- @provide
- def sqlaclhemy_engine(self) -> Engine:
- return create_engine(self._postgsres_config.url)
-
- @provide
- def redis(self) -> Redis:
- redis = Redis.from_url(
- url=self._redis_config.url,
- decode_responses=True,
- )
- return cast(Redis, redis)
-
- @provide(scope=Scope.REQUEST)
- def sqlalchemy_connection(
- self,
- sqlalchemy_engine: Engine,
- ) -> Iterable[Connection]:
- with sqlalchemy_engine.connect() as sqlalchemy_connection:
- yield sqlalchemy_connection
-
-
-class AdaptersProvider(Provider):
- scope = Scope.REQUEST
-
- user_gateway = provide(source=UserMapper, provides=UserGateway)
- movie_gateway = provide(source=MovieMapper, provides=MovieGateway)
- rating_gateway = provide(source=RatingMapper, provides=RatingGateway)
- review_gateway = provide(source=ReviewMapper, provides=ReviewGateway)
- detailed_movie_reader = provide(
- source=DetailedMovieViewModelMapper,
- provides=DetailedMovieViewModelReader,
- )
- non_detailed_movie_reader = provide(
- source=NonDetailedMovieViewModelsMapper,
- provides=NonDetailedMovieViewModelsReader,
- )
- detailed_review_reader = provide(
- source=DetailedReviewViewModelsMapper,
- provides=DetailedReviewViewModelsReader,
- )
- my_detailed_ratings_reader = provide(
- source=MyDetailedRatingsViewModelMapper,
- provides=MyDetailedRatingsViewModelReader,
- )
-
- unit_of_work = alias(source=Connection, provides=UnitOfWork)
-
- @provide
- def permissions_mapper(
- self,
- sqlalchemy_connection: Connection,
- redis: Redis,
- ) -> PermissionsMapper:
- permissions_mapper = PermissionsMapper(sqlalchemy_connection)
- cache_provider = PermissionsMapperCacheProvider(redis)
- return CachingPermissionsMapper( # type: ignore
- permissions_mapper=permissions_mapper,
- cache_provider=cache_provider,
- )
-
- @provide
- def permissions_gateway(
- self,
- sqlalchemy_connection: Connection,
- redis: Redis,
- ) -> PermissionsGateway:
- permissions_mapper = PermissionsMapper(sqlalchemy_connection)
- cache_provider = PermissionsMapperCacheProvider(redis)
- return CachingPermissionsMapper(
- permissions_mapper=permissions_mapper,
- cache_provider=cache_provider,
- )
-
- @provide
- def password_manager(
- self,
- sqlalchemy_connection: Connection,
- ) -> PasswordManager:
- password_hash_mapper = PasswordHashMapper(sqlalchemy_connection)
- return HashingPasswordManager(
- hash_computer=HashComputer(),
- password_hash_gateway=password_hash_mapper,
- )
-
-
-class HandlersProvider(Provider):
- scope = Scope.REQUEST
-
- @provide
- def register_user_handler(
- self,
- user_gateway: UserGateway,
- permissions_gateway: PermissionsGateway,
- unit_of_work: UnitOfWork,
- password_manager: PasswordManager,
- ) -> RegisterUserHandler:
- return RegisterUserHandler(
- create_user=CreateUser(validate_email=ValidateEmail()),
- user_gateway=user_gateway,
- permissions_gateway=permissions_gateway,
- unit_of_work=unit_of_work,
- password_manager=password_manager,
- )
-
- @provide
- def login_handler(
- self,
- user_gateway: UserGateway,
- permissions_gateway: PermissionsGateway,
- password_manager: PasswordManager,
- ) -> LoginHandler:
- return LoginHandler(
- access_concern=AccessConcern(),
- user_gateway=user_gateway,
- permissions_gateway=permissions_gateway,
- password_manager=password_manager,
- )
-
- @provide
- def create_movie_handler(
- self,
- movie_gateway: MovieGateway,
- unit_of_work: UnitOfWork,
- ) -> CreateMovieHandler:
- return CreateMovieHandler(
- create_movie=CreateMovie(),
- movie_gateway=movie_gateway,
- unit_of_work=unit_of_work,
- )
-
- @provide
- def delete_movie_handler(
- self,
- movie_gateway: MovieGateway,
- rating_gateway: RatingGateway,
- review_gateway: ReviewGateway,
- unit_of_work: UnitOfWork,
- ) -> DeleteMovieHandler:
- return DeleteMovieHandler(
- movie_gateway=movie_gateway,
- rating_gateway=rating_gateway,
- review_gateway=review_gateway,
- unit_of_work=unit_of_work,
- )
-
- @provide
- def get_detailed_reviews_handler(
- self,
- movie_gateway: MovieGateway,
- detailed_reviews_reader: DetailedReviewViewModelsReader,
- ) -> GetDetailedReviewsHandler:
- return GetDetailedReviewsHandler(
- movie_gateway=movie_gateway,
- detailed_reviews_reader=detailed_reviews_reader,
- )
-
-
-class HandlerCreatorsProvider(Provider):
- scope = Scope.REQUEST
-
- @provide
- def update_my_profile_handler(
- self,
- user_gateway: UserGateway,
- unit_of_work: UnitOfWork,
- ) -> CreateHandler[UpdateMyProfileHandler]:
- def create_handler(
- identity_provider: IdentityProvider,
- ) -> UpdateMyProfileHandler:
- return UpdateMyProfileHandler(
- update_profile=UpdateProfile(validate_email=ValidateEmail()),
- user_gateway=user_gateway,
- unit_of_work=unit_of_work,
- identity_provider=identity_provider,
- )
-
- return create_handler
-
- @provide
- def get_detailed_movie_handler(
- self,
- detailed_movie_reader: DetailedMovieViewModelReader,
- ) -> CreateHandler[GetDetailedMovieHandler]:
- def create_handler(
- identity_provider: IdentityProvider,
- ) -> GetDetailedMovieHandler:
- return GetDetailedMovieHandler(
- detailed_movie_reader=detailed_movie_reader,
- identity_provider=identity_provider,
- )
-
- return create_handler
-
- @provide
- def get_non_detailed_movies_handler(
- self,
- non_detailed_movies_reader: NonDetailedMovieViewModelsReader,
- ) -> CreateHandler[GetNonDetailedMoviesHandler]:
- def create_handler(
- identity_provider: IdentityProvider,
- ) -> GetNonDetailedMoviesHandler:
- return GetNonDetailedMoviesHandler(
- non_detailed_movies_reader=non_detailed_movies_reader,
- identity_provider=identity_provider,
- )
-
- return create_handler
-
- @provide
- def get_my_detailed_ratings_handler(
- self,
- my_detailed_ratings_reader: MyDetailedRatingsViewModelReader,
- ) -> CreateHandler[GetMyDetailedRatingsHandler]:
- def create_handler(
- identity_provider: IdentityProvider,
- ) -> GetMyDetailedRatingsHandler:
- return GetMyDetailedRatingsHandler(
- my_detailed_ratings_reader=my_detailed_ratings_reader,
- identity_provider=identity_provider,
- )
-
- return create_handler
-
- @provide
- def rate_movie_handler(
- self,
- permissions_gateway: PermissionsGateway,
- user_gateway: UserGateway,
- movie_gateway: MovieGateway,
- rating_gateway: RatingGateway,
- unit_of_work: UnitOfWork,
- ) -> CreateHandler[RateMovieHandler]:
- def create_handler(
- identity_provider: IdentityProvider,
- ) -> RateMovieHandler:
- return RateMovieHandler(
- access_concern=AccessConcern(),
- rate_movie=RateMovie(),
- permissions_gateway=permissions_gateway,
- user_gateway=user_gateway,
- movie_gateway=movie_gateway,
- rating_gateway=rating_gateway,
- unit_of_work=unit_of_work,
- identity_provider=identity_provider,
- )
-
- return create_handler
-
- @provide
- def unrate_movie_handler(
- self,
- permissions_gateway: PermissionsGateway,
- movie_gateway: MovieGateway,
- rating_gateway: RatingGateway,
- unit_of_work: UnitOfWork,
- ) -> CreateHandler[UnrateMovieHandler]:
- def create_handler(
- identity_provider: IdentityProvider,
- ) -> UnrateMovieHandler:
- return UnrateMovieHandler(
- access_concern=AccessConcern(),
- unrate_movie=UnrateMovie(),
- permissions_gateway=permissions_gateway,
- movie_gateway=movie_gateway,
- rating_gateway=rating_gateway,
- unit_of_work=unit_of_work,
- identity_provider=identity_provider,
- )
-
- return create_handler
-
- @provide
- def review_movie_handler(
- self,
- permissions_gateway: PermissionsGateway,
- user_gateway: UserGateway,
- movie_gateway: MovieGateway,
- review_gateway: ReviewGateway,
- unit_of_work: UnitOfWork,
- ) -> CreateHandler[ReviewMovieHandler]:
- def create_handler(
- identity_provider: IdentityProvider,
- ) -> ReviewMovieHandler:
- return ReviewMovieHandler(
- access_concern=AccessConcern(),
- review_movie=ReviewMovie(),
- permissions_gateway=permissions_gateway,
- user_gateway=user_gateway,
- movie_gateway=movie_gateway,
- review_gateway=review_gateway,
- unit_of_work=unit_of_work,
- identity_provider=identity_provider,
- )
-
- return create_handler
-
- @provide
- def export_my_ratings_handler(
- self,
- sqlaclhemy_connection: Connection,
- ) -> CreateHandler[ExportMyRatingsHandler]:
- def create_handler(
- identity_provider: IdentityProvider,
- ) -> ExportMyRatingsHandler:
- return ExportMyRatingsHandler(
- ratings_for_export_reader=RatingForExportViewModelMapper(
- connection=sqlaclhemy_connection,
- ),
- ratings_for_export_converter=RealRatingsForExportConverter(),
- identity_provider=identity_provider,
- )
-
- return create_handler
-
- @provide
- def request_my_ratings_export_handler(
- self,
- user_gateway: UserGateway,
- ) -> CreateHandler[RequestMyRatingsExportHandler]:
- def create_handler(
- identity_provider: IdentityProvider,
- ) -> RequestMyRatingsExportHandler:
- return RequestMyRatingsExportHandler(
- ensure_can_use_sending_method=EnsureCanUseSendingMethod(),
- user_gateway=user_gateway,
- enqueue_export_and_sending=lambda **kwargs: ..., # type: ignore
- identity_provider=identity_provider,
- )
-
- return create_handler
diff --git a/src/amdb/main/providers/__init__.py b/src/amdb/main/providers/__init__.py
new file mode 100644
index 0000000..6677090
--- /dev/null
+++ b/src/amdb/main/providers/__init__.py
@@ -0,0 +1,41 @@
+__all__ = (
+ "ConfigsProvider",
+ "DomainValidatorsProvider",
+ "DomainServicesProvider",
+ "ConnectionsProvider",
+ "EntityMappersProvider",
+ "ViewModelMappersProvider",
+ "ApplicationModelMappersProvider",
+ "SendingAdaptersProvider",
+ "TaskQueueAdaptersProvider",
+ "ConvertingAdaptersProvider",
+ "PasswordManagerProvider",
+ "ApllicationServicesProvider",
+ "CommandHandlersProvider",
+ "CommandHandlerMakersProvider",
+ "QueryHandlersProvider",
+ "QueryHandlerMakersProvider",
+)
+
+from .configs import ConfigsProvider
+from .domain_validators import DomainValidatorsProvider
+from .domain_services import DomainServicesProvider
+from .connections import ConnectionsProvider
+from .data_mappers import (
+ EntityMappersProvider,
+ ViewModelMappersProvider,
+ ApplicationModelMappersProvider,
+)
+from .sending import SendingAdaptersProvider
+from .task_queue import TaskQueueAdaptersProvider
+from .converting import ConvertingAdaptersProvider
+from .password_manager import PasswordManagerProvider
+from .application_services import ApllicationServicesProvider
+from .command_handlers import (
+ CommandHandlersProvider,
+ CommandHandlerMakersProvider,
+)
+from .query_handlers import (
+ QueryHandlersProvider,
+ QueryHandlerMakersProvider,
+)
diff --git a/src/amdb/main/providers/application_services.py b/src/amdb/main/providers/application_services.py
new file mode 100644
index 0000000..70b923e
--- /dev/null
+++ b/src/amdb/main/providers/application_services.py
@@ -0,0 +1,19 @@
+from dishka import Provider, Scope, provide
+
+from amdb.application.common.services import (
+ ConvertMyRatingsToFile,
+ EnsureCanUseSendingMethod,
+)
+
+
+class ApllicationServicesProvider(Provider):
+ scope = Scope.APP
+
+ convert_my_ratings_to_file = provide(
+ ConvertMyRatingsToFile,
+ provides=ConvertMyRatingsToFile,
+ )
+ ensure_can_use_sending_method = provide(
+ EnsureCanUseSendingMethod,
+ provides=EnsureCanUseSendingMethod,
+ )
diff --git a/src/amdb/main/providers/command_handlers.py b/src/amdb/main/providers/command_handlers.py
new file mode 100644
index 0000000..c08d467
--- /dev/null
+++ b/src/amdb/main/providers/command_handlers.py
@@ -0,0 +1,184 @@
+from dishka import Provider, Scope, provide
+
+from amdb.domain.services import (
+ AccessConcern,
+ CreateUser,
+ UpdateProfile,
+ CreateMovie,
+ RateMovie,
+ UnrateMovie,
+ ReviewMovie,
+)
+from amdb.application.common.gateways import (
+ UserGateway,
+ MovieGateway,
+ RatingGateway,
+ ReviewGateway,
+ PermissionsGateway,
+)
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.application.common.password_manager import PasswordManager
+from amdb.application.common.identity_provider import (
+ IdentityProvider,
+)
+from amdb.application.command_handlers import (
+ RegisterUserHandler,
+ UpdateMyProfileHandler,
+ CreateMovieHandler,
+ DeleteMovieHandler,
+ RateMovieHandler,
+ UnrateMovieHandler,
+ ReviewMovieHandler,
+)
+from amdb.presentation.create_handler import CreateHandler
+
+
+class CommandHandlersProvider(Provider):
+ scope = Scope.REQUEST
+
+ @provide
+ def register_user(
+ self,
+ create_user: CreateUser,
+ user_gateway: UserGateway,
+ permissions_gateway: PermissionsGateway,
+ unit_of_work: UnitOfWork,
+ password_manager: PasswordManager,
+ ) -> RegisterUserHandler:
+ return RegisterUserHandler(
+ create_user=create_user,
+ user_gateway=user_gateway,
+ permissions_gateway=permissions_gateway,
+ unit_of_work=unit_of_work,
+ password_manager=password_manager,
+ )
+
+ @provide
+ def create_movie(
+ self,
+ create_movie: CreateMovie,
+ movie_gateway: MovieGateway,
+ unit_of_work: UnitOfWork,
+ ) -> CreateMovieHandler:
+ return CreateMovieHandler(
+ create_movie=create_movie,
+ movie_gateway=movie_gateway,
+ unit_of_work=unit_of_work,
+ )
+
+ @provide
+ def delete_movie(
+ self,
+ movie_gateway: MovieGateway,
+ rating_gateway: RatingGateway,
+ review_gateway: ReviewGateway,
+ unit_of_work: UnitOfWork,
+ ) -> DeleteMovieHandler:
+ return DeleteMovieHandler(
+ movie_gateway=movie_gateway,
+ rating_gateway=rating_gateway,
+ review_gateway=review_gateway,
+ unit_of_work=unit_of_work,
+ )
+
+
+class CommandHandlerMakersProvider(Provider):
+ scope = Scope.REQUEST
+
+ @provide
+ def update_my_profile(
+ self,
+ update_profile: UpdateProfile,
+ user_gateway: UserGateway,
+ unit_of_work: UnitOfWork,
+ ) -> CreateHandler[UpdateMyProfileHandler]:
+ def create_handler(
+ identity_provider: IdentityProvider,
+ ) -> UpdateMyProfileHandler:
+ return UpdateMyProfileHandler(
+ update_profile=update_profile,
+ user_gateway=user_gateway,
+ unit_of_work=unit_of_work,
+ identity_provider=identity_provider,
+ )
+
+ return create_handler
+
+ @provide
+ def rate_movie_handler(
+ self,
+ access_concern: AccessConcern,
+ rate_movie: RateMovie,
+ permissions_gateway: PermissionsGateway,
+ user_gateway: UserGateway,
+ movie_gateway: MovieGateway,
+ rating_gateway: RatingGateway,
+ unit_of_work: UnitOfWork,
+ ) -> CreateHandler[RateMovieHandler]:
+ def create_handler(
+ identity_provider: IdentityProvider,
+ ) -> RateMovieHandler:
+ return RateMovieHandler(
+ access_concern=access_concern,
+ rate_movie=rate_movie,
+ permissions_gateway=permissions_gateway,
+ user_gateway=user_gateway,
+ movie_gateway=movie_gateway,
+ rating_gateway=rating_gateway,
+ unit_of_work=unit_of_work,
+ identity_provider=identity_provider,
+ )
+
+ return create_handler
+
+ @provide
+ def unrate_movie_handler(
+ self,
+ access_concern: AccessConcern,
+ unrate_movie: UnrateMovie,
+ permissions_gateway: PermissionsGateway,
+ movie_gateway: MovieGateway,
+ rating_gateway: RatingGateway,
+ unit_of_work: UnitOfWork,
+ ) -> CreateHandler[UnrateMovieHandler]:
+ def create_handler(
+ identity_provider: IdentityProvider,
+ ) -> UnrateMovieHandler:
+ return UnrateMovieHandler(
+ access_concern=access_concern,
+ unrate_movie=unrate_movie,
+ permissions_gateway=permissions_gateway,
+ movie_gateway=movie_gateway,
+ rating_gateway=rating_gateway,
+ unit_of_work=unit_of_work,
+ identity_provider=identity_provider,
+ )
+
+ return create_handler
+
+ @provide
+ def review_movie_handler(
+ self,
+ access_concern: AccessConcern,
+ review_movie: ReviewMovie,
+ permissions_gateway: PermissionsGateway,
+ user_gateway: UserGateway,
+ movie_gateway: MovieGateway,
+ review_gateway: ReviewGateway,
+ unit_of_work: UnitOfWork,
+ ) -> CreateHandler[ReviewMovieHandler]:
+ def create_handler(
+ identity_provider: IdentityProvider,
+ ) -> ReviewMovieHandler:
+ return ReviewMovieHandler(
+ access_concern=access_concern,
+ review_movie=review_movie,
+ permissions_gateway=permissions_gateway,
+ user_gateway=user_gateway,
+ movie_gateway=movie_gateway,
+ review_gateway=review_gateway,
+ unit_of_work=unit_of_work,
+ identity_provider=identity_provider,
+ )
+
+ return create_handler
diff --git a/src/amdb/main/providers/configs.py b/src/amdb/main/providers/configs.py
new file mode 100644
index 0000000..e64ff38
--- /dev/null
+++ b/src/amdb/main/providers/configs.py
@@ -0,0 +1,24 @@
+from dishka import Provider, Scope, provide
+
+from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
+from amdb.infrastructure.persistence.redis.config import RedisConfig
+
+
+class ConfigsProvider(Provider):
+ def __init__(
+ self,
+ *,
+ postgres_config: PostgresConfig,
+ redis_config: RedisConfig,
+ ) -> None:
+ super().__init__()
+ self._postgres_config = postgres_config
+ self._redis_config = redis_config
+
+ @provide(scope=Scope.APP)
+ def postgres_config(self) -> PostgresConfig:
+ return self._postgres_config
+
+ @provide(scope=Scope.APP)
+ def redis_config(self) -> RedisConfig:
+ return self._redis_config
diff --git a/src/amdb/main/providers/connections.py b/src/amdb/main/providers/connections.py
new file mode 100644
index 0000000..da814a0
--- /dev/null
+++ b/src/amdb/main/providers/connections.py
@@ -0,0 +1,33 @@
+from typing import Iterable, cast
+
+from dishka import Provider, Scope, provide
+from sqlalchemy import Connection, Engine, create_engine
+from redis import Redis
+
+from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
+from amdb.infrastructure.persistence.redis.config import RedisConfig
+
+
+class ConnectionsProvider(Provider):
+ @provide(scope=Scope.APP)
+ def sqlaclhemy_engine(
+ self,
+ postgres_config: PostgresConfig,
+ ) -> Engine:
+ return create_engine(postgres_config.url)
+
+ @provide(scope=Scope.REQUEST)
+ def sqlalchemy_connection(
+ self,
+ sqlalchemy_engine: Engine,
+ ) -> Iterable[Connection]:
+ with sqlalchemy_engine.connect() as conn:
+ yield conn
+
+ @provide(scope=Scope.APP)
+ def redis(self, redis_config: RedisConfig) -> Redis:
+ redis = Redis.from_url(
+ url=redis_config.url,
+ decode_responses=True,
+ )
+ return cast(Redis, redis)
diff --git a/src/amdb/main/providers/converting.py b/src/amdb/main/providers/converting.py
new file mode 100644
index 0000000..9ca69d4
--- /dev/null
+++ b/src/amdb/main/providers/converting.py
@@ -0,0 +1,17 @@
+from dishka import Provider, Scope, provide
+
+from amdb.application.common.converting.rating_for_export import (
+ RatingsForExportConverter,
+)
+from amdb.infrastructure.converting.ratings_for_export import (
+ RealRatingsForExportConverter,
+)
+
+
+class ConvertingAdaptersProvider(Provider):
+ scope = Scope.APP
+
+ ratings_for_export = provide(
+ RealRatingsForExportConverter,
+ provides=RatingsForExportConverter,
+ )
diff --git a/src/amdb/main/providers/data_mappers.py b/src/amdb/main/providers/data_mappers.py
new file mode 100644
index 0000000..3e33174
--- /dev/null
+++ b/src/amdb/main/providers/data_mappers.py
@@ -0,0 +1,97 @@
+from dishka import Provider, Scope, alias, provide
+from sqlalchemy import Connection
+from redis import Redis
+
+from amdb.application.common.gateways import (
+ UserGateway,
+ MovieGateway,
+ RatingGateway,
+ ReviewGateway,
+ PermissionsGateway,
+)
+from amdb.application.common.readers import (
+ DetailedMovieViewModelReader,
+ DetailedReviewViewModelsReader,
+ RatingForExportViewModelsReader,
+ MyDetailedRatingsViewModelReader,
+ NonDetailedMovieViewModelsReader,
+)
+from amdb.application.common.unit_of_work import UnitOfWork
+from amdb.infrastructure.password_manager.password_hash_gateway import (
+ PasswordHashGateway,
+)
+from amdb.infrastructure.persistence.sqlalchemy.mappers import (
+ UserMapper,
+ MovieMapper,
+ RatingMapper,
+ ReviewMapper,
+ PermissionsMapper,
+ PasswordHashMapper,
+ DetailedMovieViewModelMapper,
+ DetailedReviewViewModelsMapper,
+ RatingForExportViewModelMapper,
+ MyDetailedRatingsViewModelMapper,
+ NonDetailedMovieViewModelsMapper,
+)
+from amdb.infrastructure.persistence.redis.cache.permissions_mapper import (
+ PermissionsMapperCacheProvider,
+)
+from amdb.infrastructure.persistence.caching.permissions_mapper import (
+ CachingPermissionsMapper,
+)
+
+
+class EntityMappersProvider(Provider):
+ scope = Scope.REQUEST
+
+ user = provide(UserMapper, provides=UserGateway)
+ movie = provide(MovieMapper, provides=MovieGateway)
+ rating = provide(RatingMapper, provides=RatingGateway)
+ review = provide(ReviewMapper, provides=ReviewGateway)
+
+ unit_of_work = alias(source=Connection, provides=UnitOfWork)
+
+
+class ApplicationModelMappersProvider(Provider):
+ scope = Scope.REQUEST
+
+ password_hash = provide(
+ PasswordHashMapper,
+ provides=PasswordHashGateway,
+ )
+
+ @provide
+ def permissions(
+ self,
+ redis: Redis,
+ sqlalchemy_connection: Connection,
+ ) -> PermissionsGateway:
+ return CachingPermissionsMapper(
+ permissions_mapper=PermissionsMapper(sqlalchemy_connection),
+ cache_provider=PermissionsMapperCacheProvider(redis),
+ )
+
+
+class ViewModelMappersProvider(Provider):
+ scope = Scope.REQUEST
+
+ detailed_movie = provide(
+ DetailedMovieViewModelMapper,
+ provides=DetailedMovieViewModelReader,
+ )
+ detailed_review = provide(
+ DetailedReviewViewModelsMapper,
+ provides=DetailedReviewViewModelsReader,
+ )
+ rating_for_export = provide(
+ RatingForExportViewModelMapper,
+ provides=RatingForExportViewModelsReader,
+ )
+ my_detailed_ratings = provide(
+ MyDetailedRatingsViewModelMapper,
+ provides=MyDetailedRatingsViewModelReader,
+ )
+ non_detailed_movie = provide(
+ NonDetailedMovieViewModelsMapper,
+ provides=NonDetailedMovieViewModelsReader,
+ )
diff --git a/src/amdb/main/providers/domain_services.py b/src/amdb/main/providers/domain_services.py
new file mode 100644
index 0000000..35ccbfc
--- /dev/null
+++ b/src/amdb/main/providers/domain_services.py
@@ -0,0 +1,23 @@
+from dishka import Provider, Scope, provide
+
+from amdb.domain.services import (
+ AccessConcern,
+ CreateUser,
+ UpdateProfile,
+ CreateMovie,
+ RateMovie,
+ UnrateMovie,
+ ReviewMovie,
+)
+
+
+class DomainServicesProvider(Provider):
+ scope = Scope.APP
+
+ access_concern = provide(AccessConcern, provides=AccessConcern)
+ create_user = provide(CreateUser, provides=CreateUser)
+ create_movie = provide(CreateMovie, provides=CreateMovie)
+ update_profile = provide(UpdateProfile, provides=UpdateProfile)
+ rate_movie = provide(RateMovie, provides=RateMovie)
+ unrate_movie = provide(UnrateMovie, provides=UnrateMovie)
+ review_movie = provide(ReviewMovie, provides=ReviewMovie)
diff --git a/src/amdb/main/providers/domain_validators.py b/src/amdb/main/providers/domain_validators.py
new file mode 100644
index 0000000..373f5f6
--- /dev/null
+++ b/src/amdb/main/providers/domain_validators.py
@@ -0,0 +1,9 @@
+from dishka import Provider, Scope, provide
+
+from amdb.domain.validators.email import ValidateEmail
+
+
+class DomainValidatorsProvider(Provider):
+ scope = Scope.APP
+
+ email = provide(ValidateEmail, provides=ValidateEmail)
diff --git a/src/amdb/main/providers/password_manager.py b/src/amdb/main/providers/password_manager.py
new file mode 100644
index 0000000..d848a2c
--- /dev/null
+++ b/src/amdb/main/providers/password_manager.py
@@ -0,0 +1,22 @@
+from dishka import Provider, Scope, provide
+
+from amdb.application.common.password_manager import PasswordManager
+from amdb.infrastructure.password_manager.hash_computer import HashComputer
+from amdb.infrastructure.password_manager.password_hash_gateway import (
+ PasswordHashGateway,
+)
+from amdb.infrastructure.password_manager.password_manager import (
+ HashingPasswordManager,
+)
+
+
+class PasswordManagerProvider(Provider):
+ @provide(scope=Scope.REQUEST)
+ def password_manager(
+ self,
+ password_hash_gateway: PasswordHashGateway,
+ ) -> PasswordManager:
+ return HashingPasswordManager(
+ hash_computer=HashComputer(),
+ password_hash_gateway=password_hash_gateway,
+ )
diff --git a/src/amdb/main/providers/query_handlers.py b/src/amdb/main/providers/query_handlers.py
new file mode 100644
index 0000000..ed9b4cd
--- /dev/null
+++ b/src/amdb/main/providers/query_handlers.py
@@ -0,0 +1,170 @@
+from dishka import Provider, Scope, provide
+
+from amdb.domain.services.access_concern import AccessConcern
+from amdb.application.common.services import (
+ ConvertMyRatingsToFile,
+ EnsureCanUseSendingMethod,
+)
+from amdb.application.common.gateways import (
+ UserGateway,
+ MovieGateway,
+ PermissionsGateway,
+)
+from amdb.application.common.readers import (
+ DetailedMovieViewModelReader,
+ DetailedReviewViewModelsReader,
+ RatingForExportViewModelsReader,
+ NonDetailedMovieViewModelsReader,
+ MyDetailedRatingsViewModelReader,
+)
+from amdb.application.common.password_manager import PasswordManager
+from amdb.application.common.sending.email import SendEmail
+from amdb.application.common.task_queue.export_and_send_my_ratings import (
+ EnqueueExportAndSendingMyRatings,
+)
+from amdb.application.common.identity_provider import (
+ IdentityProvider,
+)
+from amdb.application.query_handlers import (
+ LoginHandler,
+ GetDetailedMovieHandler,
+ GetDetailedReviewsHandler,
+ ExportMyRatingsHandler,
+ RequestMyRatingsExportHandler,
+ ExportAndSendMyRatingsHandler,
+ GetMyDetailedRatingsHandler,
+ GetNonDetailedMoviesHandler,
+)
+from amdb.presentation.create_handler import CreateHandler
+
+
+class QueryHandlersProvider(Provider):
+ scope = Scope.REQUEST
+
+ @provide
+ def login(
+ self,
+ access_concern: AccessConcern,
+ user_gateway: UserGateway,
+ permissions_gateway: PermissionsGateway,
+ password_manager: PasswordManager,
+ ) -> LoginHandler:
+ return LoginHandler(
+ access_concern=access_concern,
+ user_gateway=user_gateway,
+ permissions_gateway=permissions_gateway,
+ password_manager=password_manager,
+ )
+
+ @provide
+ def get_detailed_reviews(
+ self,
+ movie_gateway: MovieGateway,
+ detailed_reviews_reader: DetailedReviewViewModelsReader,
+ ) -> GetDetailedReviewsHandler:
+ return GetDetailedReviewsHandler(
+ movie_gateway=movie_gateway,
+ detailed_reviews_reader=detailed_reviews_reader,
+ )
+
+ @provide
+ def export_and_send_my_ratings(
+ self,
+ ensure_can_use_sending_method: EnsureCanUseSendingMethod,
+ convert_my_ratings_to_file: ConvertMyRatingsToFile,
+ user_gateway: UserGateway,
+ ratings_for_export_reader: RatingForExportViewModelsReader,
+ send_email: SendEmail,
+ ) -> ExportAndSendMyRatingsHandler:
+ return ExportAndSendMyRatingsHandler(
+ ensure_can_use_sending_method=ensure_can_use_sending_method,
+ convert_my_ratings_to_file=convert_my_ratings_to_file,
+ user_gateway=user_gateway,
+ ratings_for_export_reader=ratings_for_export_reader,
+ send_email=send_email,
+ )
+
+
+class QueryHandlerMakersProvider(Provider):
+ scope = Scope.REQUEST
+
+ @provide
+ def get_detailed_movie(
+ self,
+ detailed_movie_reader: DetailedMovieViewModelReader,
+ ) -> CreateHandler[GetDetailedMovieHandler]:
+ def create_handler(
+ identity_provider: IdentityProvider,
+ ) -> GetDetailedMovieHandler:
+ return GetDetailedMovieHandler(
+ detailed_movie_reader=detailed_movie_reader,
+ identity_provider=identity_provider,
+ )
+
+ return create_handler
+
+ @provide
+ def get_non_detailed_movies(
+ self,
+ non_detailed_movies_reader: NonDetailedMovieViewModelsReader,
+ ) -> CreateHandler[GetNonDetailedMoviesHandler]:
+ def create_handler(
+ identity_provider: IdentityProvider,
+ ) -> GetNonDetailedMoviesHandler:
+ return GetNonDetailedMoviesHandler(
+ non_detailed_movies_reader=non_detailed_movies_reader,
+ identity_provider=identity_provider,
+ )
+
+ return create_handler
+
+ @provide
+ def get_my_detailed_ratings(
+ self,
+ my_detailed_ratings_reader: MyDetailedRatingsViewModelReader,
+ ) -> CreateHandler[GetMyDetailedRatingsHandler]:
+ def create_handler(
+ identity_provider: IdentityProvider,
+ ) -> GetMyDetailedRatingsHandler:
+ return GetMyDetailedRatingsHandler(
+ my_detailed_ratings_reader=my_detailed_ratings_reader,
+ identity_provider=identity_provider,
+ )
+
+ return create_handler
+
+ @provide
+ def export_my_ratings(
+ self,
+ convert_my_ratings_to_file: ConvertMyRatingsToFile,
+ ratings_for_export_reader: RatingForExportViewModelsReader,
+ ) -> CreateHandler[ExportMyRatingsHandler]:
+ def create_handler(
+ identity_provider: IdentityProvider,
+ ) -> ExportMyRatingsHandler:
+ return ExportMyRatingsHandler(
+ convert_my_ratings_to_file=convert_my_ratings_to_file,
+ ratings_for_export_reader=ratings_for_export_reader,
+ identity_provider=identity_provider,
+ )
+
+ return create_handler
+
+ @provide
+ def request_my_ratings_export(
+ self,
+ ensure_can_use_sending_method: EnsureCanUseSendingMethod,
+ user_gateway: UserGateway,
+ enuqueue_export_and_sending: EnqueueExportAndSendingMyRatings,
+ ) -> CreateHandler[RequestMyRatingsExportHandler]:
+ def create_handler(
+ identity_provider: IdentityProvider,
+ ) -> RequestMyRatingsExportHandler:
+ return RequestMyRatingsExportHandler(
+ ensure_can_use_sending_method=ensure_can_use_sending_method,
+ user_gateway=user_gateway,
+ enqueue_export_and_sending=enuqueue_export_and_sending,
+ identity_provider=identity_provider,
+ )
+
+ return create_handler
diff --git a/src/amdb/main/providers/sending.py b/src/amdb/main/providers/sending.py
new file mode 100644
index 0000000..51e5467
--- /dev/null
+++ b/src/amdb/main/providers/sending.py
@@ -0,0 +1,10 @@
+from dishka import Provider, Scope, provide
+
+from amdb.application.common.sending.email import SendEmail
+from amdb.infrastructure.sending.email import SendFakeEmail
+
+
+class SendingAdaptersProvider(Provider):
+ scope = Scope.APP
+
+ email = provide(SendFakeEmail, provides=SendEmail)
diff --git a/src/amdb/main/providers/task_queue.py b/src/amdb/main/providers/task_queue.py
new file mode 100644
index 0000000..1afa736
--- /dev/null
+++ b/src/amdb/main/providers/task_queue.py
@@ -0,0 +1,17 @@
+from dishka import Provider, Scope, provide
+
+from amdb.application.common.task_queue.export_and_send_my_ratings import (
+ EnqueueExportAndSendingMyRatings,
+)
+from amdb.infrastructure.task_queue.export_and_send_my_ratings import (
+ EnqueueFakeExportAndSendingMyRatings,
+)
+
+
+class TaskQueueAdaptersProvider(Provider):
+ scope = Scope.APP
+
+ export_and_send_ratings = provide(
+ EnqueueFakeExportAndSendingMyRatings,
+ provides=EnqueueExportAndSendingMyRatings,
+ )
diff --git a/src/amdb/main/web_api/app.py b/src/amdb/main/web_api/app.py
index 1902b52..b3932f4 100644
--- a/src/amdb/main/web_api/app.py
+++ b/src/amdb/main/web_api/app.py
@@ -13,10 +13,22 @@
setup_exception_handlers,
)
from amdb.main.providers import (
+ ConfigsProvider,
+ DomainValidatorsProvider,
+ DomainServicesProvider,
ConnectionsProvider,
- AdaptersProvider,
- HandlersProvider,
- HandlerCreatorsProvider,
+ EntityMappersProvider,
+ ViewModelMappersProvider,
+ ApplicationModelMappersProvider,
+ SendingAdaptersProvider,
+ TaskQueueAdaptersProvider,
+ ConvertingAdaptersProvider,
+ PasswordManagerProvider,
+ ApllicationServicesProvider,
+ CommandHandlersProvider,
+ CommandHandlerMakersProvider,
+ QueryHandlersProvider,
+ QueryHandlerMakersProvider,
)
from .providers import SessionAdaptersProvider
from .config import WebAPIConfig
@@ -42,16 +54,28 @@ def run_web_api() -> None:
setup_exception_handlers(app)
container = make_async_container(
- ConnectionsProvider(
+ ConfigsProvider(
postgres_config=postgres_config,
redis_config=redis_config,
),
- AdaptersProvider(),
+ DomainValidatorsProvider(),
+ ConnectionsProvider(),
+ DomainServicesProvider(),
+ EntityMappersProvider(),
+ ViewModelMappersProvider(),
+ ApplicationModelMappersProvider(),
+ SendingAdaptersProvider(),
+ TaskQueueAdaptersProvider(),
+ PasswordManagerProvider(),
+ ConvertingAdaptersProvider(),
+ ApllicationServicesProvider(),
+ CommandHandlersProvider(),
+ CommandHandlerMakersProvider(),
+ QueryHandlersProvider(),
+ QueryHandlerMakersProvider(),
SessionAdaptersProvider(
session_config=session_config,
),
- HandlersProvider(),
- HandlerCreatorsProvider(),
)
setup_dishka(container, app)
diff --git a/tests/unit/application/conftest.py b/tests/unit/application/conftest.py
index f4d68dc..eec4682 100644
--- a/tests/unit/application/conftest.py
+++ b/tests/unit/application/conftest.py
@@ -6,38 +6,18 @@
from redis.client import Redis
from amdb.infrastructure.persistence.sqlalchemy.models.base import Model
-from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.user import (
+from amdb.infrastructure.persistence.sqlalchemy.mappers import (
UserMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.movie import (
MovieMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.rating import (
RatingMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.entities.review import (
ReviewMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.password_hash import (
- PasswordHashMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.permissions import (
- PermissionsMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.non_detailed_movie import (
- NonDetailedMovieViewModelsMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.detailed_movie import (
DetailedMovieViewModelMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.detailed_review import (
DetailedReviewViewModelsMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.my_detailed_ratings import (
- MyDetailedRatingsViewModelMapper,
-)
-from amdb.infrastructure.persistence.sqlalchemy.mappers.view_models.rating_for_export import (
RatingForExportViewModelMapper,
+ MyDetailedRatingsViewModelMapper,
+ NonDetailedMovieViewModelsMapper,
+ PasswordHashMapper,
+ PermissionsMapper,
)
from amdb.infrastructure.persistence.redis.cache.permissions_mapper import (
PermissionsMapperCacheProvider,
diff --git a/tests/unit/application/query_handlers/test_export_my_ratings.py b/tests/unit/application/query_handlers/test_export_my_ratings.py
index 9cad525..d1d9231 100644
--- a/tests/unit/application/query_handlers/test_export_my_ratings.py
+++ b/tests/unit/application/query_handlers/test_export_my_ratings.py
@@ -18,7 +18,7 @@
from amdb.application.common.readers.rating_for_export import (
RatingForExportViewModelsReader,
)
-from amdb.infrastructure.converters.ratings_for_export import (
+from amdb.infrastructure.converting.ratings_for_export import (
RealRatingsForExportConverter,
)
from amdb.application.queries.export_my_ratings import ExportMyRatingsQuery
diff --git a/tests/unit/application/query_handlers/test_request_my_ratings_export.py b/tests/unit/application/query_handlers/test_request_my_ratings_export.py
index 87822a2..6cfec55 100644
--- a/tests/unit/application/query_handlers/test_request_my_ratings_export.py
+++ b/tests/unit/application/query_handlers/test_request_my_ratings_export.py
@@ -7,7 +7,7 @@
from amdb.domain.entities.user import User, UserId
from amdb.application.common.constants.export import ExportFormat
from amdb.application.common.constants.sending import SendingMethod
-from amdb.application.common.services.ensure_can_use_sending_method import (
+from amdb.application.common.services.ensure_can_use import (
EnsureCanUseSendingMethod,
)
from amdb.application.common.gateways.user import UserGateway
From e2e3ae2c4e39985e7a725e66bb0982100d7aa6c5 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Fri, 8 Mar 2024 11:43:50 +0400
Subject: [PATCH 29/39] Refactor http handlers
---
src/amdb/presentation/web_api/exports/my_ratings.py | 4 ++--
src/amdb/presentation/web_api/exports/request_my_ratings.py | 4 ++--
src/amdb/presentation/web_api/movies/get_detailed.py | 4 ++--
src/amdb/presentation/web_api/movies/get_non_detailed.py | 4 ++--
src/amdb/presentation/web_api/profiles/update_my.py | 4 ++--
src/amdb/presentation/web_api/ratings/get_my_detailed.py | 4 ++--
src/amdb/presentation/web_api/ratings/rate_movie.py | 4 ++--
src/amdb/presentation/web_api/ratings/unrate_movie.py | 4 ++--
8 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/src/amdb/presentation/web_api/exports/my_ratings.py b/src/amdb/presentation/web_api/exports/my_ratings.py
index 22796a9..f1c4148 100644
--- a/src/amdb/presentation/web_api/exports/my_ratings.py
+++ b/src/amdb/presentation/web_api/exports/my_ratings.py
@@ -19,13 +19,13 @@
from amdb.presentation.web_api.constants import SESSION_ID_COOKIE
-HandlerCreator = CreateHandler[ExportMyRatingsHandler]
+HandlerMaker = CreateHandler[ExportMyRatingsHandler]
@inject
async def export_my_ratings(
*,
- create_handler: Annotated[HandlerCreator, FromDishka()],
+ create_handler: Annotated[HandlerMaker, FromDishka()],
session_gateway: Annotated[SessionGateway, FromDishka()],
permissions_gateway: Annotated[PermissionsGateway, FromDishka()],
session_id: Annotated[
diff --git a/src/amdb/presentation/web_api/exports/request_my_ratings.py b/src/amdb/presentation/web_api/exports/request_my_ratings.py
index ece5de0..cba6c85 100644
--- a/src/amdb/presentation/web_api/exports/request_my_ratings.py
+++ b/src/amdb/presentation/web_api/exports/request_my_ratings.py
@@ -19,13 +19,13 @@
from amdb.presentation.web_api.constants import SESSION_ID_COOKIE
-HandlerCreator = CreateHandler[RequestMyRatingsExportHandler]
+HandlerMaker = CreateHandler[RequestMyRatingsExportHandler]
@inject
async def request_my_ratings_export(
*,
- create_handler: Annotated[HandlerCreator, FromDishka()],
+ create_handler: Annotated[HandlerMaker, FromDishka()],
session_gateway: Annotated[SessionGateway, FromDishka()],
permissions_gateway: Annotated[PermissionsGateway, FromDishka()],
session_id: Annotated[
diff --git a/src/amdb/presentation/web_api/movies/get_detailed.py b/src/amdb/presentation/web_api/movies/get_detailed.py
index 3b9a53f..e57c377 100644
--- a/src/amdb/presentation/web_api/movies/get_detailed.py
+++ b/src/amdb/presentation/web_api/movies/get_detailed.py
@@ -21,13 +21,13 @@
from amdb.presentation.web_api.constants import SESSION_ID_COOKIE
-HandlerCreator = CreateHandler[GetDetailedMovieHandler]
+HandlerMaker = CreateHandler[GetDetailedMovieHandler]
@inject
async def get_detailed_movie(
*,
- create_handler: Annotated[HandlerCreator, FromDishka()],
+ create_handler: Annotated[HandlerMaker, FromDishka()],
session_gateway: Annotated[SessionGateway, FromDishka()],
permissions_gateway: Annotated[PermissionsGateway, FromDishka()],
session_id: Annotated[
diff --git a/src/amdb/presentation/web_api/movies/get_non_detailed.py b/src/amdb/presentation/web_api/movies/get_non_detailed.py
index c2ce3d7..521a6f3 100644
--- a/src/amdb/presentation/web_api/movies/get_non_detailed.py
+++ b/src/amdb/presentation/web_api/movies/get_non_detailed.py
@@ -22,13 +22,13 @@
from amdb.presentation.web_api.constants import SESSION_ID_COOKIE
-HandlerCreator = CreateHandler[GetNonDetailedMoviesHandler]
+HandlerMaker = CreateHandler[GetNonDetailedMoviesHandler]
@inject
async def get_non_detailed_movies(
*,
- create_handler: Annotated[HandlerCreator, FromDishka()],
+ create_handler: Annotated[HandlerMaker, FromDishka()],
session_gateway: Annotated[SessionGateway, FromDishka()],
permissions_gateway: Annotated[PermissionsGateway, FromDishka()],
session_id: Annotated[
diff --git a/src/amdb/presentation/web_api/profiles/update_my.py b/src/amdb/presentation/web_api/profiles/update_my.py
index 9f87b3e..76be628 100644
--- a/src/amdb/presentation/web_api/profiles/update_my.py
+++ b/src/amdb/presentation/web_api/profiles/update_my.py
@@ -16,13 +16,13 @@
from amdb.presentation.web_api.constants import SESSION_ID_COOKIE
-HandlerCreator = CreateHandler[UpdateMyProfileHandler]
+HandlerMaker = CreateHandler[UpdateMyProfileHandler]
@inject
async def update_my_profile(
*,
- create_handler: Annotated[HandlerCreator, FromDishka()],
+ create_handler: Annotated[HandlerMaker, FromDishka()],
session_gateway: Annotated[SessionGateway, FromDishka()],
permissions_gateway: Annotated[PermissionsGateway, FromDishka()],
session_id: Annotated[
diff --git a/src/amdb/presentation/web_api/ratings/get_my_detailed.py b/src/amdb/presentation/web_api/ratings/get_my_detailed.py
index 285b22c..ee6d940 100644
--- a/src/amdb/presentation/web_api/ratings/get_my_detailed.py
+++ b/src/amdb/presentation/web_api/ratings/get_my_detailed.py
@@ -22,13 +22,13 @@
from amdb.presentation.web_api.constants import SESSION_ID_COOKIE
-HandlerCreator = CreateHandler[GetMyDetailedRatingsHandler]
+HandlerMaker = CreateHandler[GetMyDetailedRatingsHandler]
@inject
async def get_my_detailed_ratings(
*,
- create_handler: Annotated[HandlerCreator, FromDishka()],
+ create_handler: Annotated[HandlerMaker, FromDishka()],
session_gateway: Annotated[SessionGateway, FromDishka()],
permissions_gateway: Annotated[PermissionsGateway, FromDishka()],
session_id: Annotated[
diff --git a/src/amdb/presentation/web_api/ratings/rate_movie.py b/src/amdb/presentation/web_api/ratings/rate_movie.py
index 11a0e76..934570a 100644
--- a/src/amdb/presentation/web_api/ratings/rate_movie.py
+++ b/src/amdb/presentation/web_api/ratings/rate_movie.py
@@ -16,13 +16,13 @@
from amdb.presentation.web_api.constants import SESSION_ID_COOKIE
-HandlerCreator = CreateHandler[RateMovieHandler]
+HandlerMaker = CreateHandler[RateMovieHandler]
@inject
async def rate_movie(
*,
- create_handler: Annotated[HandlerCreator, FromDishka()],
+ create_handler: Annotated[HandlerMaker, FromDishka()],
session_gateway: Annotated[SessionGateway, FromDishka()],
permissions_gateway: Annotated[PermissionsGateway, FromDishka()],
session_id: Annotated[
diff --git a/src/amdb/presentation/web_api/ratings/unrate_movie.py b/src/amdb/presentation/web_api/ratings/unrate_movie.py
index 59e2c56..8bda81b 100644
--- a/src/amdb/presentation/web_api/ratings/unrate_movie.py
+++ b/src/amdb/presentation/web_api/ratings/unrate_movie.py
@@ -16,13 +16,13 @@
from amdb.presentation.web_api.constants import SESSION_ID_COOKIE
-HandlerCreator = CreateHandler[UnrateMovieHandler]
+HandlerMaker = CreateHandler[UnrateMovieHandler]
@inject
async def unrate_movie(
*,
- create_handler: Annotated[HandlerCreator, FromDishka()],
+ create_handler: Annotated[HandlerMaker, FromDishka()],
session_gateway: Annotated[SessionGateway, FromDishka()],
permissions_gateway: Annotated[PermissionsGateway, FromDishka()],
session_id: Annotated[
From 05cd1c41c00764e2b40be7604ee3d476f245432d Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Fri, 8 Mar 2024 14:08:55 +0400
Subject: [PATCH 30/39] Fix commands optional fields have no default value
---
src/amdb/application/commands/register_user.py | 2 +-
src/amdb/application/commands/update_my_profile.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/amdb/application/commands/register_user.py b/src/amdb/application/commands/register_user.py
index bb15bef..e50bdde 100644
--- a/src/amdb/application/commands/register_user.py
+++ b/src/amdb/application/commands/register_user.py
@@ -5,5 +5,5 @@
@dataclass(frozen=True, slots=True)
class RegisterUserCommand:
name: str
- email: Optional[str]
password: str
+ email: Optional[str] = None
diff --git a/src/amdb/application/commands/update_my_profile.py b/src/amdb/application/commands/update_my_profile.py
index ac62ee8..0fb1bb9 100644
--- a/src/amdb/application/commands/update_my_profile.py
+++ b/src/amdb/application/commands/update_my_profile.py
@@ -4,4 +4,4 @@
@dataclass(frozen=True, slots=True)
class UpdateMyProfileCommand:
- email: Optional[str]
+ email: Optional[str] = None
From a7de54e3cc07a518c2f9bd2adf0633a9cf44e87f Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Fri, 8 Mar 2024 16:00:08 +0400
Subject: [PATCH 31/39] Update latest migration: fill permissions table with
default data
---
.../alembic/migrations/versions/a2f7c2383ba8_.py | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/amdb/infrastructure/persistence/alembic/migrations/versions/a2f7c2383ba8_.py b/src/amdb/infrastructure/persistence/alembic/migrations/versions/a2f7c2383ba8_.py
index 90301ee..276fd5d 100644
--- a/src/amdb/infrastructure/persistence/alembic/migrations/versions/a2f7c2383ba8_.py
+++ b/src/amdb/infrastructure/persistence/alembic/migrations/versions/a2f7c2383ba8_.py
@@ -1,6 +1,7 @@
"""
Add permissions table,
-Make type column string to reviews table
+Fill permission table with default data,
+Make type column string to reviews table,
Add email column to users table
Revision ID: a2f7c2383ba8
@@ -25,7 +26,7 @@ def upgrade() -> None:
op.create_table(
"permissions",
sa.Column("user_id", sa.Uuid(), nullable=False),
- sa.Column("value", sa.Integer(), nullable=False),
+ sa.Column("value", sa.Integer(), nullable=False, default=30),
sa.PrimaryKeyConstraint("user_id"),
sa.ForeignKeyConstraint(
["user_id"],
@@ -33,6 +34,12 @@ def upgrade() -> None:
ondelete="CASCADE",
),
)
+ op.execute(
+ """
+ INSERT INTO permissions (user_id)
+ (SELECT u.id FROM users u)
+ """,
+ )
with op.batch_alter_table("reviews") as batch_op:
batch_op.alter_column(
"type",
From 8e7f795a6e33ab91c27fe9c49f2c21d9a0c0ae11 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Fri, 8 Mar 2024 16:05:01 +0400
Subject: [PATCH 32/39] Fix grammer
---
.../alembic/migrations/versions/65f8840f4494_.py | 8 ++++----
.../alembic/migrations/versions/85a348467b90_.py | 2 +-
.../alembic/migrations/versions/a2f7c2383ba8_.py | 4 ++--
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/amdb/infrastructure/persistence/alembic/migrations/versions/65f8840f4494_.py b/src/amdb/infrastructure/persistence/alembic/migrations/versions/65f8840f4494_.py
index 11c9645..aef0513 100644
--- a/src/amdb/infrastructure/persistence/alembic/migrations/versions/65f8840f4494_.py
+++ b/src/amdb/infrastructure/persistence/alembic/migrations/versions/65f8840f4494_.py
@@ -1,9 +1,9 @@
"""
Add uuid7 function,
-Add id column to ratings table,
-Add primary key constraint on id of ratings table,
-Add unique constraint on pair of user_id and movie_id of ratings table,
-Add unique constraint on pair of user_id and movie_id of reviews table
+Add id column in ratings table,
+Add primary key constraint on id in ratings table,
+Add unique constraint on pair of user_id and movie_id in ratings table,
+Add unique constraint on pair of user_id and movie_id in reviews table
Revision ID: 65f8840f4494
Revises: 85a348467b90
diff --git a/src/amdb/infrastructure/persistence/alembic/migrations/versions/85a348467b90_.py b/src/amdb/infrastructure/persistence/alembic/migrations/versions/85a348467b90_.py
index 77d5d02..48ca05d 100644
--- a/src/amdb/infrastructure/persistence/alembic/migrations/versions/85a348467b90_.py
+++ b/src/amdb/infrastructure/persistence/alembic/migrations/versions/85a348467b90_.py
@@ -1,5 +1,5 @@
"""
-Add release_date column to movies table,
+Add release_date column in movies table,
Add review table
Revision ID: 85a348467b90
diff --git a/src/amdb/infrastructure/persistence/alembic/migrations/versions/a2f7c2383ba8_.py b/src/amdb/infrastructure/persistence/alembic/migrations/versions/a2f7c2383ba8_.py
index 276fd5d..506ec52 100644
--- a/src/amdb/infrastructure/persistence/alembic/migrations/versions/a2f7c2383ba8_.py
+++ b/src/amdb/infrastructure/persistence/alembic/migrations/versions/a2f7c2383ba8_.py
@@ -1,8 +1,8 @@
"""
Add permissions table,
Fill permission table with default data,
-Make type column string to reviews table,
-Add email column to users table
+Make type column string in reviews table,
+Add email column in users table
Revision ID: a2f7c2383ba8
Revises: 65f8840f4494
From 30a92e32d9ffbd8ae7bdc5a05364f9f829fe7090 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Fri, 8 Mar 2024 17:25:16 +0400
Subject: [PATCH 33/39] Fix movie table race condition
---
.../persistence/sqlalchemy/mappers/entities/movie.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/movie.py b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/movie.py
index 5f43986..5a5a077 100644
--- a/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/movie.py
+++ b/src/amdb/infrastructure/persistence/sqlalchemy/mappers/entities/movie.py
@@ -11,7 +11,11 @@ def __init__(self, connection: Connection) -> None:
self._connection = connection
def with_id(self, movie_id: MovieId) -> Optional[Movie]:
- statement = select(MovieModel).where(MovieModel.id == movie_id)
+ statement = (
+ select(MovieModel)
+ .where(MovieModel.id == movie_id)
+ .with_for_update()
+ )
row = self._connection.execute(statement).one_or_none()
if row:
return self._to_entity(row) # type: ignore
From 0fc1c440f44e908b1847c20c40df4ee91bd2598d Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Sat, 9 Mar 2024 14:17:49 +0400
Subject: [PATCH 34/39] Add worker
---
pyproject.toml | 4 +-
.../redis}/task_queue/__init__.py | 0
.../task_queue/export_and_send_my_ratings.py | 26 +++++++
src/amdb/infrastructure/sending/email.py | 11 +--
.../task_queue/export_and_send_my_ratings.py | 14 ----
src/amdb/main/providers/task_queue.py | 6 +-
src/amdb/main/web_api/__main__.py | 5 --
src/amdb/main/web_api/app.py | 2 +-
...viders.py => session_adapters_provider.py} | 0
src/amdb/main/worker/__init__.py | 0
src/amdb/main/worker/app.py | 68 +++++++++++++++++++
src/amdb/presentation/worker/__init__.py | 0
.../worker/export_and_send_my_ratings.py | 28 ++++++++
src/amdb/presentation/worker/router.py | 10 +++
14 files changed, 146 insertions(+), 28 deletions(-)
rename src/amdb/infrastructure/{ => persistence/redis}/task_queue/__init__.py (100%)
create mode 100644 src/amdb/infrastructure/persistence/redis/task_queue/export_and_send_my_ratings.py
delete mode 100644 src/amdb/infrastructure/task_queue/export_and_send_my_ratings.py
delete mode 100644 src/amdb/main/web_api/__main__.py
rename src/amdb/main/web_api/{providers.py => session_adapters_provider.py} (100%)
create mode 100644 src/amdb/main/worker/__init__.py
create mode 100644 src/amdb/main/worker/app.py
create mode 100644 src/amdb/presentation/worker/__init__.py
create mode 100644 src/amdb/presentation/worker/export_and_send_my_ratings.py
create mode 100644 src/amdb/presentation/worker/router.py
diff --git a/pyproject.toml b/pyproject.toml
index 0e653d6..f91b680 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -31,6 +31,7 @@ dependencies = [
"psycopg2-binary==2.9.*",
"alembic==1.13.*",
"redis==5.0.*",
+ "faststream[redis]==0.4.*",
]
[project.optional-dependencies]
@@ -51,4 +52,5 @@ coverage = [
]
[project.scripts]
-amdb-web_api = "amdb.main.web_api.__main__:main"
+amdb-web_api = "amdb.main.web_api.app:run_web_api"
+amdb-worker = "amdb.main.worker.app:run_worker"
diff --git a/src/amdb/infrastructure/task_queue/__init__.py b/src/amdb/infrastructure/persistence/redis/task_queue/__init__.py
similarity index 100%
rename from src/amdb/infrastructure/task_queue/__init__.py
rename to src/amdb/infrastructure/persistence/redis/task_queue/__init__.py
diff --git a/src/amdb/infrastructure/persistence/redis/task_queue/export_and_send_my_ratings.py b/src/amdb/infrastructure/persistence/redis/task_queue/export_and_send_my_ratings.py
new file mode 100644
index 0000000..9165d15
--- /dev/null
+++ b/src/amdb/infrastructure/persistence/redis/task_queue/export_and_send_my_ratings.py
@@ -0,0 +1,26 @@
+import json
+
+from redis import Redis
+
+from amdb.domain.entities.user import UserId
+from amdb.application.common.constants.export import ExportFormat
+from amdb.application.common.constants.sending import SendingMethod
+
+
+class EnqueueExportAndSendingMyRatingsInRedis:
+ def __init__(self, redis: Redis) -> None:
+ self._redis = redis
+
+ def __call__(
+ self,
+ *,
+ user_id: UserId,
+ export_format: ExportFormat,
+ sending_method: SendingMethod,
+ ) -> None:
+ data = {
+ "user_id": user_id.hex,
+ "export_format": export_format.value,
+ "sending_method": sending_method.value,
+ }
+ self._redis.lpush("tasks", json.dumps(data))
diff --git a/src/amdb/infrastructure/sending/email.py b/src/amdb/infrastructure/sending/email.py
index db4d294..faa8484 100644
--- a/src/amdb/infrastructure/sending/email.py
+++ b/src/amdb/infrastructure/sending/email.py
@@ -1,9 +1,7 @@
-from typing import Protocol
-
from amdb.application.common.entities.file import File
-class SendFakeEmail(Protocol):
+class SendFakeEmail:
def __call__(
self,
*,
@@ -11,4 +9,9 @@ def __call__(
subject: str,
files: list[File],
) -> None:
- ...
+ print( # noqa
+ "Email has been sent. \n"
+ f"Address: {email} \n"
+ f"Subject: {subject} \n"
+ f"Number of file: {len(files)}",
+ )
diff --git a/src/amdb/infrastructure/task_queue/export_and_send_my_ratings.py b/src/amdb/infrastructure/task_queue/export_and_send_my_ratings.py
deleted file mode 100644
index b6a91ab..0000000
--- a/src/amdb/infrastructure/task_queue/export_and_send_my_ratings.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from amdb.domain.entities.user import UserId
-from amdb.application.common.constants.export import ExportFormat
-from amdb.application.common.constants.sending import SendingMethod
-
-
-class EnqueueFakeExportAndSendingMyRatings:
- def __call__(
- self,
- *,
- user_id: UserId,
- export_format: ExportFormat,
- sending_method: SendingMethod,
- ) -> None:
- ...
diff --git a/src/amdb/main/providers/task_queue.py b/src/amdb/main/providers/task_queue.py
index 1afa736..f4187e8 100644
--- a/src/amdb/main/providers/task_queue.py
+++ b/src/amdb/main/providers/task_queue.py
@@ -3,8 +3,8 @@
from amdb.application.common.task_queue.export_and_send_my_ratings import (
EnqueueExportAndSendingMyRatings,
)
-from amdb.infrastructure.task_queue.export_and_send_my_ratings import (
- EnqueueFakeExportAndSendingMyRatings,
+from amdb.infrastructure.persistence.redis.task_queue.export_and_send_my_ratings import (
+ EnqueueExportAndSendingMyRatingsInRedis,
)
@@ -12,6 +12,6 @@ class TaskQueueAdaptersProvider(Provider):
scope = Scope.APP
export_and_send_ratings = provide(
- EnqueueFakeExportAndSendingMyRatings,
+ EnqueueExportAndSendingMyRatingsInRedis,
provides=EnqueueExportAndSendingMyRatings,
)
diff --git a/src/amdb/main/web_api/__main__.py b/src/amdb/main/web_api/__main__.py
deleted file mode 100644
index 44388f6..0000000
--- a/src/amdb/main/web_api/__main__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from .app import run_web_api
-
-
-def main() -> None:
- run_web_api()
diff --git a/src/amdb/main/web_api/app.py b/src/amdb/main/web_api/app.py
index b3932f4..a1af0e2 100644
--- a/src/amdb/main/web_api/app.py
+++ b/src/amdb/main/web_api/app.py
@@ -30,7 +30,7 @@
QueryHandlersProvider,
QueryHandlerMakersProvider,
)
-from .providers import SessionAdaptersProvider
+from .session_adapters_provider import SessionAdaptersProvider
from .config import WebAPIConfig
diff --git a/src/amdb/main/web_api/providers.py b/src/amdb/main/web_api/session_adapters_provider.py
similarity index 100%
rename from src/amdb/main/web_api/providers.py
rename to src/amdb/main/web_api/session_adapters_provider.py
diff --git a/src/amdb/main/worker/__init__.py b/src/amdb/main/worker/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/amdb/main/worker/app.py b/src/amdb/main/worker/app.py
new file mode 100644
index 0000000..e67db4e
--- /dev/null
+++ b/src/amdb/main/worker/app.py
@@ -0,0 +1,68 @@
+import asyncio
+import os
+
+from faststream import FastStream
+from faststream.redis import RedisBroker
+from dishka import make_async_container
+from dishka.integrations.faststream import setup_dishka
+
+from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
+from amdb.infrastructure.persistence.redis.config import RedisConfig
+from amdb.presentation.worker.router import router
+from amdb.main.providers import (
+ ConfigsProvider,
+ DomainValidatorsProvider,
+ DomainServicesProvider,
+ ConnectionsProvider,
+ EntityMappersProvider,
+ ViewModelMappersProvider,
+ ApplicationModelMappersProvider,
+ SendingAdaptersProvider,
+ TaskQueueAdaptersProvider,
+ ConvertingAdaptersProvider,
+ PasswordManagerProvider,
+ ApllicationServicesProvider,
+ CommandHandlersProvider,
+ CommandHandlerMakersProvider,
+ QueryHandlersProvider,
+ QueryHandlerMakersProvider,
+)
+
+
+def run_worker() -> None:
+ path_to_config = os.getenv("CONFIG_PATH")
+ if not path_to_config:
+ message = "Path to config env var is not set"
+ raise ValueError(message)
+
+ postgres_config = PostgresConfig.from_toml(path_to_config)
+ redis_config = RedisConfig.from_toml(path_to_config)
+
+ broker = RedisBroker(url=redis_config.url)
+ broker.include_router(router)
+
+ app = FastStream(broker)
+ container = make_async_container(
+ ConfigsProvider(
+ postgres_config=postgres_config,
+ redis_config=redis_config,
+ ),
+ DomainValidatorsProvider(),
+ ConnectionsProvider(),
+ DomainServicesProvider(),
+ EntityMappersProvider(),
+ ViewModelMappersProvider(),
+ ApplicationModelMappersProvider(),
+ SendingAdaptersProvider(),
+ TaskQueueAdaptersProvider(),
+ PasswordManagerProvider(),
+ ConvertingAdaptersProvider(),
+ ApllicationServicesProvider(),
+ CommandHandlersProvider(),
+ CommandHandlerMakersProvider(),
+ QueryHandlersProvider(),
+ QueryHandlerMakersProvider(),
+ )
+ setup_dishka(container, app)
+
+ asyncio.run(app.run())
diff --git a/src/amdb/presentation/worker/__init__.py b/src/amdb/presentation/worker/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/amdb/presentation/worker/export_and_send_my_ratings.py b/src/amdb/presentation/worker/export_and_send_my_ratings.py
new file mode 100644
index 0000000..f673820
--- /dev/null
+++ b/src/amdb/presentation/worker/export_and_send_my_ratings.py
@@ -0,0 +1,28 @@
+from typing import Annotated
+
+from dishka.integrations.faststream import FromDishka, inject
+
+from amdb.domain.entities.user import UserId
+from amdb.application.common.constants.export import ExportFormat
+from amdb.application.common.constants.sending import SendingMethod
+from amdb.application.queries.export_and_send_my_ratings import (
+ ExportAndSendMyRatingsQuery,
+)
+from amdb.application.query_handlers.export_and_send_my_ratings import (
+ ExportAndSendMyRatingsHandler,
+)
+
+
+@inject
+async def export_and_send_my_ratings(
+ handler: Annotated[ExportAndSendMyRatingsHandler, FromDishka()],
+ user_id: UserId,
+ export_format: ExportFormat,
+ sending_method: SendingMethod,
+) -> None:
+ query = ExportAndSendMyRatingsQuery(
+ user_id=user_id,
+ format=export_format,
+ sending_method=sending_method,
+ )
+ handler.execute(query)
diff --git a/src/amdb/presentation/worker/router.py b/src/amdb/presentation/worker/router.py
new file mode 100644
index 0000000..f77340a
--- /dev/null
+++ b/src/amdb/presentation/worker/router.py
@@ -0,0 +1,10 @@
+from faststream.redis import RedisRoute, RedisRouter
+
+from .export_and_send_my_ratings import export_and_send_my_ratings
+
+
+router = RedisRouter(
+ handlers=[
+ RedisRoute(export_and_send_my_ratings, list="tasks"),
+ ],
+)
From 49cbb4f7e4786f177e924d46501430b5f2c09bee Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Sat, 9 Mar 2024 14:19:36 +0400
Subject: [PATCH 35/39] Rename export and send my ratings task queue name
---
.../redis/task_queue/export_and_send_my_ratings.py | 5 ++++-
src/amdb/presentation/worker/router.py | 5 ++++-
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/src/amdb/infrastructure/persistence/redis/task_queue/export_and_send_my_ratings.py b/src/amdb/infrastructure/persistence/redis/task_queue/export_and_send_my_ratings.py
index 9165d15..1adbef1 100644
--- a/src/amdb/infrastructure/persistence/redis/task_queue/export_and_send_my_ratings.py
+++ b/src/amdb/infrastructure/persistence/redis/task_queue/export_and_send_my_ratings.py
@@ -23,4 +23,7 @@ def __call__(
"export_format": export_format.value,
"sending_method": sending_method.value,
}
- self._redis.lpush("tasks", json.dumps(data))
+ self._redis.lpush(
+ "export_and_send_my_ratings_tasks",
+ json.dumps(data),
+ )
diff --git a/src/amdb/presentation/worker/router.py b/src/amdb/presentation/worker/router.py
index f77340a..d3456b6 100644
--- a/src/amdb/presentation/worker/router.py
+++ b/src/amdb/presentation/worker/router.py
@@ -5,6 +5,9 @@
router = RedisRouter(
handlers=[
- RedisRoute(export_and_send_my_ratings, list="tasks"),
+ RedisRoute(
+ export_and_send_my_ratings,
+ list="export_and_send_my_ratings_tasks",
+ ),
],
)
From 59a0e248203f3aaf2c65400706635716824e7321 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Sat, 9 Mar 2024 14:32:15 +0400
Subject: [PATCH 36/39] Update README
---
README.md | 54 +++++++++++-------------------------------------------
1 file changed, 11 insertions(+), 43 deletions(-)
diff --git a/README.md b/README.md
index dde7f6d..c531511 100644
--- a/README.md
+++ b/README.md
@@ -19,46 +19,14 @@
-## How to run:
-
-### Using docker-compose:
-
-1. Provide `.env` file with variables from `.env.template`
-
-2. Run docker-compose
-
-```sh
-docker-compose --env-file ./.env up web_api
-```
-
-### Manually:
-
-1. Install
-
-```sh
-pip install -e ".[web_api,cli]"
-```
-
-2. Provide env variables from `.env.template`
-
-3. Run server
-
-```sh
-amdb-web_api
-```
-
-4. Run cli
-
-```sh
-amdb-cli
-```
-
-## How to run migrations:
-
-1. Provide env variables for postgres from `.env.template`
-
-2. Run migrations:
-
-```
-amdb-cli migration alembic upgrade head
-```
+## Used technologies:
+
+* [Python 3](https://www.python.org/downloads/)
+ * [FastAPI](https://github.com/tiangolo/fastapi) - Web framework for building APIs
+ * [FastStream](https://github.com/airtai/faststream) - framework for building message queues
+ * [SQLAlchemy](https://github.com/sqlalchemy/sqlalchemy) - Toolkit for building high level db integrations
+ * [alembic](https://github.com/sqlalchemy/alembic) - Tool for writing db migrations
+ * [redis-py](https://github.com/redis/redis-py) - Redis python client
+ * [Dishka](https://github.com/reagento/dishka) - DI framework
+* [PostgreSQL](https://www.postgresql.org/)
+* [Redis](https://redis.io/)
From 1e7d68f288c65c00d50ab918ff8f5bb4da05084d Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Sun, 10 Mar 2024 19:06:05 +0400
Subject: [PATCH 37/39] Add CLI
---
README.md | 5 +-
pyproject.toml | 4 +-
src/amdb/main/cli/__init__.py | 0
src/amdb/main/cli/app.py | 92 ++++++++++++++++++++++++++
src/amdb/main/web_api/app.py | 8 +--
src/amdb/main/web_api/config.py | 20 ------
src/amdb/presentation/cli/__init__.py | 0
src/amdb/presentation/cli/movie.py | 95 +++++++++++++++++++++++++++
8 files changed, 195 insertions(+), 29 deletions(-)
create mode 100644 src/amdb/main/cli/__init__.py
create mode 100644 src/amdb/main/cli/app.py
delete mode 100644 src/amdb/main/web_api/config.py
create mode 100644 src/amdb/presentation/cli/__init__.py
create mode 100644 src/amdb/presentation/cli/movie.py
diff --git a/README.md b/README.md
index c531511..dfc65c1 100644
--- a/README.md
+++ b/README.md
@@ -22,8 +22,9 @@
## Used technologies:
* [Python 3](https://www.python.org/downloads/)
- * [FastAPI](https://github.com/tiangolo/fastapi) - Web framework for building APIs
- * [FastStream](https://github.com/airtai/faststream) - framework for building message queues
+ * [FastAPI](https://github.com/tiangolo/fastapi) - Framework for building WEB APIs
+ * [FastStream](https://github.com/airtai/faststream) - Framework for building message queues
+ * [Typer](https://github.com/tiangolo/typer) - Framework for buildig CLIs
* [SQLAlchemy](https://github.com/sqlalchemy/sqlalchemy) - Toolkit for building high level db integrations
* [alembic](https://github.com/sqlalchemy/alembic) - Tool for writing db migrations
* [redis-py](https://github.com/redis/redis-py) - Redis python client
diff --git a/pyproject.toml b/pyproject.toml
index f91b680..c1c7052 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -32,6 +32,7 @@ dependencies = [
"alembic==1.13.*",
"redis==5.0.*",
"faststream[redis]==0.4.*",
+ "typer[all]==0.9.*",
]
[project.optional-dependencies]
@@ -52,5 +53,4 @@ coverage = [
]
[project.scripts]
-amdb-web_api = "amdb.main.web_api.app:run_web_api"
-amdb-worker = "amdb.main.worker.app:run_worker"
+amdb = "amdb.main.cli.app:run_cli"
diff --git a/src/amdb/main/cli/__init__.py b/src/amdb/main/cli/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/amdb/main/cli/app.py b/src/amdb/main/cli/app.py
new file mode 100644
index 0000000..02ba000
--- /dev/null
+++ b/src/amdb/main/cli/app.py
@@ -0,0 +1,92 @@
+import os
+from typing import Annotated
+
+import typer
+from dishka import make_container
+from alembic import config
+
+from amdb.infrastructure.persistence.sqlalchemy.config import PostgresConfig
+from amdb.infrastructure.persistence.redis.config import RedisConfig
+from amdb.infrastructure.persistence.alembic.config import ALEMBIC_CONFIG
+from amdb.presentation.cli.movie import movie_commands
+from amdb.main.providers import (
+ ConfigsProvider,
+ DomainValidatorsProvider,
+ DomainServicesProvider,
+ ConnectionsProvider,
+ EntityMappersProvider,
+ ViewModelMappersProvider,
+ ApplicationModelMappersProvider,
+ SendingAdaptersProvider,
+ TaskQueueAdaptersProvider,
+ ConvertingAdaptersProvider,
+ PasswordManagerProvider,
+ ApllicationServicesProvider,
+ CommandHandlersProvider,
+ CommandHandlerMakersProvider,
+ QueryHandlersProvider,
+ QueryHandlerMakersProvider,
+)
+from amdb.main.web_api.app import run_web_api
+from amdb.main.worker.app import run_worker
+
+
+def run_cli() -> None:
+ path_to_config = os.getenv("CONFIG_PATH")
+ if not path_to_config:
+ message = "Path to config env var is not set"
+ raise ValueError(message)
+
+ postgres_config = PostgresConfig.from_toml(path_to_config)
+ redis_config = RedisConfig.from_toml(path_to_config)
+
+ container = make_container(
+ ConfigsProvider(
+ postgres_config=postgres_config,
+ redis_config=redis_config,
+ ),
+ DomainValidatorsProvider(),
+ ConnectionsProvider(),
+ DomainServicesProvider(),
+ EntityMappersProvider(),
+ ViewModelMappersProvider(),
+ ApplicationModelMappersProvider(),
+ SendingAdaptersProvider(),
+ TaskQueueAdaptersProvider(),
+ PasswordManagerProvider(),
+ ConvertingAdaptersProvider(),
+ ApllicationServicesProvider(),
+ CommandHandlersProvider(),
+ CommandHandlerMakersProvider(),
+ QueryHandlersProvider(),
+ QueryHandlerMakersProvider(),
+ )
+
+ app = typer.Typer(
+ rich_markup_mode="rich",
+ context_settings={"obj": {"container": container}},
+ )
+ app.add_typer(movie_commands)
+
+ @app.command()
+ def alembic(commands: Annotated[list[str], typer.Argument()]) -> None:
+ """
+ [green]Run[/green] alembic.
+ """
+ config.main(["-c", ALEMBIC_CONFIG, *commands])
+
+ @app.command()
+ def web_api() -> None:
+ """
+ [green]Run[/green] web api.
+ """
+ run_web_api()
+
+ @app.command()
+ def worker() -> None:
+ """
+ [green]Run[/green] worker.
+ """
+ run_worker()
+
+ app()
diff --git a/src/amdb/main/web_api/app.py b/src/amdb/main/web_api/app.py
index a1af0e2..8069cb7 100644
--- a/src/amdb/main/web_api/app.py
+++ b/src/amdb/main/web_api/app.py
@@ -31,7 +31,6 @@
QueryHandlerMakersProvider,
)
from .session_adapters_provider import SessionAdaptersProvider
-from .config import WebAPIConfig
def run_web_api() -> None:
@@ -40,14 +39,13 @@ def run_web_api() -> None:
message = "Path to config env var is not set"
raise ValueError(message)
- web_api_config = WebAPIConfig.from_toml(path_to_config)
postgres_config = PostgresConfig.from_toml(path_to_config)
redis_config = RedisConfig.from_toml(path_to_config)
session_config = SessionConfig.from_toml(path_to_config)
app = FastAPI(
title="Awesome Movie Database",
- version=web_api_config.version,
+ version="0.5.0",
swagger_ui_parameters={"defaultModelsExpandDepth": -1},
)
app.include_router(router)
@@ -81,6 +79,6 @@ def run_web_api() -> None:
uvicorn.run(
app=app,
- host=web_api_config.host,
- port=web_api_config.port,
+ host="0.0.0.0",
+ port=8000,
)
diff --git a/src/amdb/main/web_api/config.py b/src/amdb/main/web_api/config.py
deleted file mode 100644
index 050409c..0000000
--- a/src/amdb/main/web_api/config.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from dataclasses import dataclass
-
-import toml
-
-
-@dataclass(frozen=True, slots=True)
-class WebAPIConfig:
- version: str
- host: str
- port: int
-
- @classmethod
- def from_toml(cls, path: str) -> "WebAPIConfig":
- toml_as_dict = toml.load(path)
- web_api_section_as_dict = toml_as_dict["web-api"]
- return WebAPIConfig(
- version=web_api_section_as_dict["version"],
- host=web_api_section_as_dict["host"],
- port=web_api_section_as_dict["port"],
- )
diff --git a/src/amdb/presentation/cli/__init__.py b/src/amdb/presentation/cli/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/amdb/presentation/cli/movie.py b/src/amdb/presentation/cli/movie.py
new file mode 100644
index 0000000..d5d6600
--- /dev/null
+++ b/src/amdb/presentation/cli/movie.py
@@ -0,0 +1,95 @@
+from datetime import datetime
+from typing import Annotated
+from uuid import UUID
+
+import typer
+import rich
+from dishka import Container
+
+from amdb.domain.entities.movie import MovieId
+from amdb.application.commands.create_movie import CreateMovieCommand
+from amdb.application.commands.delete_movie import DeleteMovieCommand
+from amdb.application.command_handlers.create_movie import CreateMovieHandler
+from amdb.application.command_handlers.delete_movie import DeleteMovieHandler
+
+
+movie_commands = typer.Typer(
+ name="movie",
+ help="[yellow]Manage[/yellow] movies",
+)
+
+
+@movie_commands.command()
+def create(
+ ctx: typer.Context,
+ title: Annotated[
+ str,
+ typer.Option("--title", "-t", help="Movie title."),
+ ],
+ release_date: Annotated[
+ datetime,
+ typer.Option("--release_date", "-rd", help="Movie release date."),
+ ],
+ silently: Annotated[
+ bool,
+ typer.Option("--silently", "-s", help="Do not print movie id."),
+ ] = False,
+) -> None:
+ """
+ [green]Create[/green] movie.
+
+ If --silently is not used, will print movie id.
+ """
+ container: Container = ctx.obj["container"]
+
+ with container() as request_container:
+ handler = request_container.get(CreateMovieHandler)
+ command = CreateMovieCommand(
+ title=title,
+ release_date=release_date,
+ )
+ movie_id = handler.execute(command)
+
+ if not silently:
+ rich.print(movie_id)
+
+
+@movie_commands.command()
+def delete(
+ ctx: typer.Context,
+ movie_id: Annotated[
+ UUID,
+ typer.Argument(help="Movie id."),
+ ],
+ force: Annotated[
+ bool,
+ typer.Option("--force", "-f", help="Do not ask for confirmation."),
+ ] = False,
+ silently: Annotated[
+ bool,
+ typer.Option("--silently", "-s", help="Do not print movie id"),
+ ] = False,
+) -> None:
+ """
+ [red]Delete[/red] movie. Also [red]deletes[/red] ratings and
+ reviews related to movie.
+
+ If --force is not used, will ask for confirmation.
+ If --silently is not used, will print movie id.
+ """
+ if not force:
+ typer.confirm(
+ text="Are you sure you want to delete movie?",
+ default=True,
+ abort=True,
+ )
+
+ container: Container = ctx.obj["container"]
+
+ with container() as request_container:
+ handler = request_container.get(DeleteMovieHandler)
+ command = DeleteMovieCommand(movie_id=MovieId(movie_id))
+ handler.execute(command)
+
+ if not silently:
+ rich.print(movie_id)
From 46642932aab273b19087ce886884f28b05d7915a Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Sun, 10 Mar 2024 20:21:58 +0400
Subject: [PATCH 38/39] Refactor Dockerfile, docker-compose.yaml, config and
README
---
Dockerfile | 12 ++++++--
README.md | 51 ++++++++++++++++++++++++++++++++
config/prod_config.template.toml | 5 ----
docker-compose.yaml | 33 ++++++++++++++-------
4 files changed, 84 insertions(+), 17 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index 7939ab0..d198a54 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -18,6 +18,14 @@ FROM base AS web_api
COPY --from=builder ./app/dist ./
-RUN $(printf "pip install %s[web_api,cli]" amdb*.whl)
+RUN $(printf "pip install %s[web_api]" amdb*.whl)
-CMD ["amdb-web_api"]
+CMD ["amdb", "web-api"]
+
+FROM base AS worker
+
+COPY --from=builder ./app/dist ./
+
+RUN pip install amdb*.whl
+
+CMD ["amdb", "worker"]
diff --git a/README.md b/README.md
index dfc65c1..21565d4 100644
--- a/README.md
+++ b/README.md
@@ -31,3 +31,54 @@
* [Dishka](https://github.com/reagento/dishka) - DI framework
* [PostgreSQL](https://www.postgresql.org/)
* [Redis](https://redis.io/)
+
+
+## How to run:
+
+### Manually:
+
+1. Install
+
+```sh
+pip install -e ".[web_api]"
+```
+
+2. Create [config](./config/prod_config.template.toml) file
+
+3. Provide `CONFIG_PATH` env variable
+
+4. Run migrations
+
+```sh
+amdb alembic upgrade head
+```
+
+5. Run worker
+
+```sh
+amdb worker
+```
+
+6. Run server
+
+```sh
+amdb web_api
+```
+
+### Using docker-compose:
+
+1. Create [config](./config/prod_config.template.toml) file
+
+2. Provide `CONFIG_PATH`, `REDIS_PASSWORD`, `REDIS_PORT_NUMBER`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`, `SERVER_HOST`, `SERVER_PORT` env variables
+
+3. Run worker and server
+
+```sh
+docker-compose up web_api
+```
+
+4. Run migrations
+
+```sh
+docker exec amdb_backend.web_api amdb alembic upgrade head
+```
diff --git a/config/prod_config.template.toml b/config/prod_config.template.toml
index bc037b7..26b0e4c 100644
--- a/config/prod_config.template.toml
+++ b/config/prod_config.template.toml
@@ -6,8 +6,3 @@ url = "redis://:1234@127.0.0.1:6379/0"
[auth-session]
lifetime = 3600 # Minutes
-
-[web-api]
-version = "0.5.0"
-host = "127.0.0.1"
-port = 8000
diff --git a/docker-compose.yaml b/docker-compose.yaml
index a12f994..dc2ead2 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -2,10 +2,10 @@ version: "3"
services:
web_api:
- profiles: [web_api]
+ profiles: [web_api, worker]
container_name: amdb_backend.web_api
- depends_on: [postgres, redis]
- ports: ["${UVICORN_HOST}:${UVICORN_PORT}:${UVICORN_PORT}"]
+ depends_on: [postgres, redis, worker]
+ ports: ["${SERVER_HOST:-0.0.0.0}:${SERVER_PORT:-8000}:8000"]
restart: unless-stopped
build:
@@ -15,23 +15,36 @@ services:
environment:
- CONFIG_PATH
+ worker:
+ profiles: [web_api, worker]
+ container_name: amdb_backend.worker
+ depends_on: [postgres, redis]
+ restart: unless-stopped
+
+ build:
+ context: ./
+ dockerfile: ./Dockerfile
+ target: worker
+ environment:
+ - CONFIG_PATH
+
postgres:
- profiles: [web_api]
+ profiles: [web_api, worker]
container_name: amdb_backend.postgres
image: postgres:15-alpine
restart: unless-stopped
environment:
- - POSTGRES_USER
- - POSTGRES_PASSWORD
- - POSTGRES_DB
+ - POSTGRES_USER=${POSTGRES_USER:-postgres}
+ - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-1234}
+ - POSTGRES_DB=${POSTGRES_DB:-amdb}
redis:
- profiles: [web_api]
+ profiles: [web_api, worker]
container_name: amdb_backend.redis
image: bitnami/redis:7.2
restart: unless-stopped
environment:
- - REDIS_PASSWORD
- - REDIS_PORT_NUMBER=${REDIS_PORT}
+ - REDIS_PASSWORD=${REDIS_PASSWORD:-1234}
+ - REDIS_PORT_NUMBER=${REDIS_PORT:-6379}
From 7c0cf89a7590991d4feaa1f552f4be5acd93a773 Mon Sep 17 00:00:00 2001
From: Madnoberson
Date: Sun, 10 Mar 2024 20:45:35 +0400
Subject: [PATCH 39/39] Update CHANGELOG
---
docs/CHANGELOG.md | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 64b5825..f4dc7ad 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -1,5 +1,35 @@
# Changelog
+## [v1.0.0](https://github.com/Awesome-Movie-Database/amdb-backend/releases/tag/v1.0.0) (2024-03-10)
+
+### Added
+
+- `User` now can list `detailed reviews`
+- `User` now can get `detailed movie`
+- `User` now can list his `detailed ratings`
+- `User` now can get `non detailed movie`
+- `User` now can export his `ratings` in CSV format
+- `User` now can request export his `ratings` in CSV format
+
+### Changed
+
+- Now `Create movie` and `Delete movie` don't require permissions
+- [*Breaking change*] Now `Review` type is a string
+- [*Breaking change*] Removed ability to list `Movies`
+- [*Breaking change*] Removed ability to list `Ratings`
+- [*Breaking change*] Removed ability to get `Movie`
+- [*Breaking change*] Removed ability to get `Review`
+
+### Fixed
+
+- Race condition during rating `Movie` by many `Users` at the same time
+
+### Echancements
+
+- Added permissions table
+- Now you can run server and worker using CLI
+
+
## [v0.5.0](https://github.com/Awesome-Movie-Database/amdb-backend/releases/tag/v0.5.0) (2024-01-29)
### Added