From d4b7c03053fd2ffc946b0af5443fd4f60a349ac0 Mon Sep 17 00:00:00 2001 From: Shahriyar Rzayev Date: Sun, 23 Oct 2022 12:15:52 +0400 Subject: [PATCH] feat: add users interface, restructure the ports --- src/jobboard/adapters/entrypoints/__init__.py | 2 +- .../adapters/entrypoints/api/v1/route_jobs.py | 4 +-- .../entrypoints/api/v1/route_login.py | 10 +++--- .../entrypoints/api/v1/route_users.py | 6 ++-- .../entrypoints/webapps/jobs/route_jobs.py | 5 +-- .../entrypoints/webapps/users/route_users.py | 6 ++-- src/jobboard/adapters/use_cases/jobs.py | 2 +- .../use_cases/users.py} | 11 +++--- src/jobboard/configurator/containers.py | 4 +-- .../domain/ports/{ => common}/messagebus.py | 0 .../domain/ports/{ => common}/responses.py | 0 src/jobboard/domain/ports/unit_of_work.py | 3 +- src/jobboard/domain/ports/use_cases/jobs.py | 11 +++--- src/jobboard/domain/ports/use_cases/users.py | 34 +++++++++++++++++++ tests/fake_container.py | 4 +-- tests/utils/users.py | 6 ++-- 16 files changed, 72 insertions(+), 36 deletions(-) rename src/jobboard/{domain/ports/user_service.py => adapters/use_cases/users.py} (88%) rename src/jobboard/domain/ports/{ => common}/messagebus.py (100%) rename src/jobboard/domain/ports/{ => common}/responses.py (100%) diff --git a/src/jobboard/adapters/entrypoints/__init__.py b/src/jobboard/adapters/entrypoints/__init__.py index 9e6e128..25c5b5f 100644 --- a/src/jobboard/adapters/entrypoints/__init__.py +++ b/src/jobboard/adapters/entrypoints/__init__.py @@ -1,4 +1,4 @@ -from src.jobboard.domain.ports.responses import ResponseTypes +from src.jobboard.domain.ports.common.responses import ResponseTypes STATUS_CODES = { ResponseTypes.SUCCESS: 200, diff --git a/src/jobboard/adapters/entrypoints/api/v1/route_jobs.py b/src/jobboard/adapters/entrypoints/api/v1/route_jobs.py index f2c2e51..3e15d81 100644 --- a/src/jobboard/adapters/entrypoints/api/v1/route_jobs.py +++ b/src/jobboard/adapters/entrypoints/api/v1/route_jobs.py @@ -10,11 +10,11 @@ from src.jobboard.adapters.entrypoints.api.v1.route_login import ( get_current_user_from_token, ) +from src.jobboard.configurator.containers import Container from src.jobboard.domain.model.model import User +from src.jobboard.domain.ports.common.responses import ResponseTypes from src.jobboard.domain.ports.use_cases.jobs import JobsServiceInterface -from src.jobboard.domain.ports.responses import ResponseTypes from src.jobboard.domain.schemas.jobs import JobCreateInputDto -from src.jobboard.configurator.containers import Container router = APIRouter() templates = Jinja2Templates(directory="src/jobboard/adapters/entrypoints/templates") diff --git a/src/jobboard/adapters/entrypoints/api/v1/route_login.py b/src/jobboard/adapters/entrypoints/api/v1/route_login.py index d44d100..7438415 100644 --- a/src/jobboard/adapters/entrypoints/api/v1/route_login.py +++ b/src/jobboard/adapters/entrypoints/api/v1/route_login.py @@ -6,12 +6,12 @@ from jose import JWTError, jwt from src.jobboard.adapters.entrypoints.api.utils import OAuth2PasswordBearerWithCookie -from src.jobboard.domain.ports.user_service import UserService -from src.jobboard.domain.schemas.tokens import Token -from src.jobboard.domain.schemas.users import UserLoginInputDto, UserOutputDto from src.jobboard.configurator.config import settings from src.jobboard.configurator.containers import Container from src.jobboard.configurator.security import create_access_token +from src.jobboard.domain.ports.use_cases.users import UsersServiceInterface +from src.jobboard.domain.schemas.tokens import Token +from src.jobboard.domain.schemas.users import UserLoginInputDto, UserOutputDto router = APIRouter() @@ -21,7 +21,7 @@ def login_for_access_token( response: Response, form_data: OAuth2PasswordRequestForm = Depends(), - user_service: UserService = Depends(Provide[Container.user_service]), + user_service: UsersServiceInterface = Depends(Provide[Container.user_service]), ): user = UserLoginInputDto(email=form_data.username, password=form_data.password) user = user_service.authenticate_user(user) @@ -46,7 +46,7 @@ def login_for_access_token( @inject def get_current_user_from_token( token: str = Depends(oauth2_scheme), - user_service: UserService = Depends(Provide[Container.user_service]), + user_service: UsersServiceInterface = Depends(Provide[Container.user_service]), ): credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/src/jobboard/adapters/entrypoints/api/v1/route_users.py b/src/jobboard/adapters/entrypoints/api/v1/route_users.py index d4b7a34..676f237 100644 --- a/src/jobboard/adapters/entrypoints/api/v1/route_users.py +++ b/src/jobboard/adapters/entrypoints/api/v1/route_users.py @@ -5,9 +5,9 @@ from fastapi.encoders import jsonable_encoder from src.jobboard.adapters.entrypoints import STATUS_CODES -from src.jobboard.domain.ports.user_service import UserService -from src.jobboard.domain.schemas.users import UserCreateInputDto from src.jobboard.configurator.containers import Container +from src.jobboard.domain.ports.use_cases.users import UsersServiceInterface +from src.jobboard.domain.schemas.users import UserCreateInputDto router = APIRouter() @@ -16,7 +16,7 @@ @inject def create_user( user: UserCreateInputDto, - user_service: UserService = Depends(Provide[Container.user_service]), + user_service: UsersServiceInterface = Depends(Provide[Container.user_service]), ): response = user_service.create(user=user) data = jsonable_encoder(response.value) diff --git a/src/jobboard/adapters/entrypoints/webapps/jobs/route_jobs.py b/src/jobboard/adapters/entrypoints/webapps/jobs/route_jobs.py index 43ae71f..4f6481d 100644 --- a/src/jobboard/adapters/entrypoints/webapps/jobs/route_jobs.py +++ b/src/jobboard/adapters/entrypoints/webapps/jobs/route_jobs.py @@ -9,10 +9,10 @@ get_current_user_from_token, ) from src.jobboard.adapters.entrypoints.webapps.jobs.forms import JobCreateForm +from src.jobboard.configurator.containers import Container from src.jobboard.domain.model.model import User from src.jobboard.domain.ports.use_cases.jobs import JobsServiceInterface from src.jobboard.domain.schemas.jobs import JobCreateInputDto -from src.jobboard.configurator.containers import Container templates = Jinja2Templates(directory="src/jobboard/adapters/entrypoints/templates") router = APIRouter(include_in_schema=False) @@ -81,7 +81,8 @@ async def create_job( @router.get("/delete-job/") @inject def show_jobs_to_delete( - request: Request, job_service: JobsServiceInterface = Depends(Provide[Container.job_service]) + request: Request, + job_service: JobsServiceInterface = Depends(Provide[Container.job_service]), ): jobs = job_service.list_jobs() return templates.TemplateResponse( diff --git a/src/jobboard/adapters/entrypoints/webapps/users/route_users.py b/src/jobboard/adapters/entrypoints/webapps/users/route_users.py index 928d5d1..0c23671 100644 --- a/src/jobboard/adapters/entrypoints/webapps/users/route_users.py +++ b/src/jobboard/adapters/entrypoints/webapps/users/route_users.py @@ -4,9 +4,9 @@ from sqlalchemy.exc import IntegrityError from src.jobboard.adapters.entrypoints.webapps.users.forms import UserCreateForm -from src.jobboard.domain.ports.user_service import UserService -from src.jobboard.domain.schemas.users import UserCreateInputDto from src.jobboard.configurator.containers import Container +from src.jobboard.domain.ports.use_cases.users import UsersServiceInterface +from src.jobboard.domain.schemas.users import UserCreateInputDto templates = Jinja2Templates(directory="src/jobboard/adapters/entrypoints/templates") router = APIRouter(include_in_schema=False) @@ -21,7 +21,7 @@ def register(request: Request): @inject async def register( request: Request, - user_service: UserService = Depends(Provide[Container.user_service]), + user_service: UsersServiceInterface = Depends(Provide[Container.user_service]), ): form = UserCreateForm(request) await form.load_data() diff --git a/src/jobboard/adapters/use_cases/jobs.py b/src/jobboard/adapters/use_cases/jobs.py index 261f9e5..fcfa9d9 100644 --- a/src/jobboard/adapters/use_cases/jobs.py +++ b/src/jobboard/adapters/use_cases/jobs.py @@ -1,7 +1,7 @@ from typing import Union from src.jobboard.domain.model.model import job_model_event_factory -from src.jobboard.domain.ports.responses import ( +from src.jobboard.domain.ports.common.responses import ( ResponseFailure, ResponseSuccess, ResponseTypes, diff --git a/src/jobboard/domain/ports/user_service.py b/src/jobboard/adapters/use_cases/users.py similarity index 88% rename from src/jobboard/domain/ports/user_service.py rename to src/jobboard/adapters/use_cases/users.py index d5719ca..1419423 100644 --- a/src/jobboard/domain/ports/user_service.py +++ b/src/jobboard/adapters/use_cases/users.py @@ -1,25 +1,26 @@ from typing import Union +from src.jobboard.configurator.hashing import Hasher from src.jobboard.domain.model.model import user_model_event_factory -from src.jobboard.domain.ports.responses import ( +from src.jobboard.domain.ports.common.responses import ( ResponseFailure, ResponseSuccess, ResponseTypes, ) from src.jobboard.domain.ports.unit_of_work import UserUnitOfWorkInterface +from src.jobboard.domain.ports.use_cases.users import UsersServiceInterface from src.jobboard.domain.schemas.users import ( UserCreateInputDto, UserLoginInputDto, UserOutputDto, ) -from src.jobboard.configurator.hashing import Hasher -class UserService: +class UsersService(UsersServiceInterface): def __init__(self, uow: UserUnitOfWorkInterface): self.uow = uow - def create( + def _create( self, user: UserCreateInputDto ) -> Union[ResponseSuccess, ResponseFailure]: try: @@ -48,7 +49,7 @@ def create( except Exception as exc: return ResponseFailure(ResponseTypes.SYSTEM_ERROR, exc) - def authenticate_user(self, user: UserLoginInputDto) -> Union[UserOutputDto, bool]: + def _authenticate_user(self, user: UserLoginInputDto) -> Union[UserOutputDto, bool]: with self.uow: user_ = self.uow.users.get_by_email(user.email) if not user_ or not Hasher.verify_password( diff --git a/src/jobboard/configurator/containers.py b/src/jobboard/configurator/containers.py index 5489858..801b278 100644 --- a/src/jobboard/configurator/containers.py +++ b/src/jobboard/configurator/containers.py @@ -7,7 +7,7 @@ UserSqlAlchemyUnitOfWork, ) from src.jobboard.adapters.use_cases.jobs import JobsService -from src.jobboard.domain.ports.user_service import UserService +from src.jobboard.adapters.use_cases.users import UsersService from src.jobboard.configurator import config @@ -35,7 +35,7 @@ class Container(containers.DeclarativeContainer): ) user_service = providers.Factory( - UserService, + UsersService, uow=user_uow, ) diff --git a/src/jobboard/domain/ports/messagebus.py b/src/jobboard/domain/ports/common/messagebus.py similarity index 100% rename from src/jobboard/domain/ports/messagebus.py rename to src/jobboard/domain/ports/common/messagebus.py diff --git a/src/jobboard/domain/ports/responses.py b/src/jobboard/domain/ports/common/responses.py similarity index 100% rename from src/jobboard/domain/ports/responses.py rename to src/jobboard/domain/ports/common/responses.py diff --git a/src/jobboard/domain/ports/unit_of_work.py b/src/jobboard/domain/ports/unit_of_work.py index dde331e..fe43e97 100644 --- a/src/jobboard/domain/ports/unit_of_work.py +++ b/src/jobboard/domain/ports/unit_of_work.py @@ -1,6 +1,7 @@ import abc -from src.jobboard.domain.ports import messagebus, repository +from src.jobboard.domain.ports import repository +from src.jobboard.domain.ports.common import messagebus class UserUnitOfWorkInterface(abc.ABC): diff --git a/src/jobboard/domain/ports/use_cases/jobs.py b/src/jobboard/domain/ports/use_cases/jobs.py index 5ef2cb1..bfa9ab1 100644 --- a/src/jobboard/domain/ports/use_cases/jobs.py +++ b/src/jobboard/domain/ports/use_cases/jobs.py @@ -1,19 +1,18 @@ import abc from typing import Union -from src.jobboard.domain.ports.responses import ResponseFailure, ResponseSuccess +from src.jobboard.domain.ports.common.responses import ResponseFailure, ResponseSuccess from src.jobboard.domain.ports.unit_of_work import JobUnitOfWorkInterface from src.jobboard.domain.schemas.jobs import JobCreateInputDto class JobsServiceInterface(abc.ABC): - @abc.abstractmethod def __init__(self, uow: JobUnitOfWorkInterface): self.uow = uow def create( - self, job: JobCreateInputDto, owner_id: int + self, job: JobCreateInputDto, owner_id: int ) -> Union[ResponseFailure, ResponseSuccess]: return self._create(job, owner_id) @@ -24,7 +23,7 @@ def list_jobs(self) -> ResponseSuccess: return self._list_jobs() def update_job_by_id( - self, id_: int, job: JobCreateInputDto, owner_id: int + self, id_: int, job: JobCreateInputDto, owner_id: int ) -> Union[ResponseFailure, ResponseSuccess]: return self._update_job_by_id(id_, job, owner_id) @@ -36,7 +35,7 @@ def search_job(self, query: str) -> ResponseSuccess: @abc.abstractmethod def _create( - self, job: JobCreateInputDto, owner_id: int + self, job: JobCreateInputDto, owner_id: int ) -> Union[ResponseFailure, ResponseSuccess]: raise NotImplementedError @@ -50,7 +49,7 @@ def _list_jobs(self) -> ResponseSuccess: @abc.abstractmethod def _update_job_by_id( - self, id_: int, job: JobCreateInputDto, owner_id: int + self, id_: int, job: JobCreateInputDto, owner_id: int ) -> Union[ResponseFailure, ResponseSuccess]: raise NotImplementedError diff --git a/src/jobboard/domain/ports/use_cases/users.py b/src/jobboard/domain/ports/use_cases/users.py index e69de29..52699a3 100644 --- a/src/jobboard/domain/ports/use_cases/users.py +++ b/src/jobboard/domain/ports/use_cases/users.py @@ -0,0 +1,34 @@ +import abc +from typing import Union + +from src.jobboard.domain.ports.common.responses import ResponseFailure, ResponseSuccess +from src.jobboard.domain.ports.unit_of_work import UserUnitOfWorkInterface +from src.jobboard.domain.schemas.users import ( + UserCreateInputDto, + UserLoginInputDto, + UserOutputDto, +) + + +class UsersServiceInterface(abc.ABC): + @abc.abstractmethod + def __init__(self, uow: UserUnitOfWorkInterface): + self.uow = uow + + def create( + self, user: UserCreateInputDto + ) -> Union[ResponseSuccess, ResponseFailure]: + return self._create(user) + + def authenticate_user(self, user: UserLoginInputDto) -> Union[UserOutputDto, bool]: + return self._authenticate_user(user) + + @abc.abstractmethod + def _create( + self, user: UserCreateInputDto + ) -> Union[ResponseSuccess, ResponseFailure]: + raise NotImplementedError + + @abc.abstractmethod + def _authenticate_user(self, user: UserLoginInputDto) -> Union[UserOutputDto, bool]: + raise NotImplementedError diff --git a/tests/fake_container.py b/tests/fake_container.py index 4f87481..2e2b1f4 100644 --- a/tests/fake_container.py +++ b/tests/fake_container.py @@ -7,7 +7,7 @@ UserSqlAlchemyUnitOfWork, ) from src.jobboard.adapters.use_cases.jobs import JobsService -from src.jobboard.domain.ports.user_service import UserService +from src.jobboard.adapters.use_cases.users import UsersService class Container(containers.DeclarativeContainer): @@ -35,7 +35,7 @@ class Container(containers.DeclarativeContainer): ) fake_user_service = providers.Factory( - UserService, + UsersService, uow=user_uow, ) diff --git a/tests/utils/users.py b/tests/utils/users.py index 57a0ec0..c02e2df 100644 --- a/tests/utils/users.py +++ b/tests/utils/users.py @@ -2,10 +2,10 @@ from fastapi import Depends from fastapi.testclient import TestClient +from src.jobboard.configurator.hashing import Hasher from src.jobboard.domain.model.model import user_model_factory -from src.jobboard.domain.ports.user_service import UserService +from src.jobboard.domain.ports.use_cases.users import UsersServiceInterface from src.jobboard.domain.schemas.users import UserCreateInputDto -from src.jobboard.configurator.hashing import Hasher from tests.fake_container import Container @@ -27,7 +27,7 @@ def user_authentication_headers( def authentication_token_from_email( client: TestClient, email: str, - user_service: UserService = Depends(Provide[Container.fake_user_service]), + user_service: UsersServiceInterface = Depends(Provide[Container.fake_user_service]), ): """ Return a valid token for the user with given email.