diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c6b68b5..9f9bfbe 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: rev: v3.15.2 hooks: - id: pyupgrade - args: [ --py310-plus ] + args: [ --py3-plus ] # Форматирует код под PEP8 - repo: https://github.com/pre-commit/mirrors-autopep8 @@ -46,7 +46,7 @@ repos: rev: 24.3.0 hooks: - id: black - language_version: python3.10 + language_version: python3 args: [ "--line-length=120" ] # Проверка статических типов с помощью mypy diff --git a/api/app_entrypoint/dependencies.py b/api/app_entrypoint/dependencies.py index 6f5da7d..7c1d5cc 100644 --- a/api/app_entrypoint/dependencies.py +++ b/api/app_entrypoint/dependencies.py @@ -1,6 +1,5 @@ from fastapi import FastAPI -from sqlalchemy.ext.asyncio import (AsyncEngine, AsyncSession, - async_sessionmaker) +from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker from api.application.abstractions.transaction import TransactionContextManager from api.application.protocols.date_time import DateTimeProvider @@ -9,27 +8,36 @@ from api.application.protocols.password_hasher import PasswordHasher from api.application.usecase.auth.auth_user import LoginUser from api.application.usecase.auth.create_user import CreateUser from api.application.usecase.company.create_company import CreateCompany -from api.application.usecase.company.get_users_company import \ - GetCompaniesByOwnerEmail +from api.application.usecase.company.get_users_company import GetCompaniesByOwnerEmail from api.domain.company.repository import CompanyRepository from api.domain.user.repository import UserRepository from api.infrastructure.auth.jwt_settings import JwtSettings -from api.infrastructure.dependencies.adapters import (create_engine, - create_session_maker, - get_transaction_context, - new_session) -from api.infrastructure.dependencies.configs import (app_settings, - get_db_settings, - get_jwt_settings) -from api.infrastructure.dependencies.protocols import (get_date_time_provider, - get_jwt_token_processor, - get_password_hasher, - get_user_login) +from api.infrastructure.dependencies.adapters import ( + create_engine, + create_session_maker, + get_transaction_context, + new_session, +) +from api.infrastructure.dependencies.configs import ( + app_settings, + get_db_settings, + get_jwt_settings, +) +from api.infrastructure.dependencies.protocols import ( + get_date_time_provider, + get_jwt_token_processor, + get_password_hasher, + get_user_login, +) from api.infrastructure.dependencies.repositories import ( - get_company_repository, get_user_repository) + get_company_repository, + get_user_repository, +) from api.infrastructure.dependencies.usecases import ( - provide_create_company, provide_create_user, - provide_get_companies_by_email) + provide_create_company, + provide_create_user, + provide_get_companies_by_email, +) from api.infrastructure.persistence.db_setings import DBSettings from api.infrastructure.settings import Settings diff --git a/api/app_entrypoint/error_handlers.py b/api/app_entrypoint/error_handlers.py index 2f0e643..3d24a16 100644 --- a/api/app_entrypoint/error_handlers.py +++ b/api/app_entrypoint/error_handlers.py @@ -2,27 +2,23 @@ from fastapi import FastAPI, Request from fastapi.responses import JSONResponse from api.domain.error import DomainValidationError -from api.domain.user.error import (UserAlreadyExistsError, - UserInvalidCredentialsError, - UserIsNotAuthorizedError) +from api.domain.user.error import ( + UserAlreadyExistsError, + UserInvalidCredentialsError, + UserIsNotAuthorizedError, +) from api.infrastructure.persistence.error import TransactionContextManagerError -async def transaction_error_exec_handler( - request: Request, exc: TransactionContextManagerError -) -> JSONResponse: +async def transaction_error_exec_handler(request: Request, exc: TransactionContextManagerError) -> JSONResponse: return JSONResponse(status_code=400, content={"detail": exc.message}) -async def validation_error_exc_handler( - request: Request, exc: DomainValidationError -) -> JSONResponse: +async def validation_error_exc_handler(request: Request, exc: DomainValidationError) -> JSONResponse: return JSONResponse(status_code=400, content={"detail": exc.message}) -async def user_authentication_error_exc_handler( - request: Request, exc: UserIsNotAuthorizedError -) -> JSONResponse: +async def user_authentication_error_exc_handler(request: Request, exc: UserIsNotAuthorizedError) -> JSONResponse: return JSONResponse( status_code=401, content={"detail": exc.message}, @@ -30,9 +26,7 @@ async def user_authentication_error_exc_handler( ) -async def user_already_exist_error_exc_handler( - request: Request, exc: UserAlreadyExistsError -) -> JSONResponse: +async def user_already_exist_error_exc_handler(request: Request, exc: UserAlreadyExistsError) -> JSONResponse: return JSONResponse(status_code=409, content={"detail": exc.message}) @@ -47,16 +41,8 @@ def init_exc_handlers(app: FastAPI) -> None: DomainValidationError, validation_error_exc_handler, ) - app.add_exception_handler( - UserIsNotAuthorizedError, user_authentication_error_exc_handler - ) - app.add_exception_handler( - UserAlreadyExistsError, user_already_exist_error_exc_handler - ) - app.add_exception_handler( - UserInvalidCredentialsError, user_invalid_credentials_error_exc_handler - ) + app.add_exception_handler(UserIsNotAuthorizedError, user_authentication_error_exc_handler) + app.add_exception_handler(UserAlreadyExistsError, user_already_exist_error_exc_handler) + app.add_exception_handler(UserInvalidCredentialsError, user_invalid_credentials_error_exc_handler) - app.add_exception_handler( - TransactionContextManagerError, transaction_error_exec_handler - ) + app.add_exception_handler(TransactionContextManagerError, transaction_error_exec_handler) diff --git a/api/app_entrypoint/main.py b/api/app_entrypoint/main.py index 96745d2..bad1e6b 100644 --- a/api/app_entrypoint/main.py +++ b/api/app_entrypoint/main.py @@ -8,9 +8,11 @@ from api.app_entrypoint.dependencies import init_dependencies from api.app_entrypoint.error_handlers import init_exc_handlers from api.infrastructure.auth.jwt_settings import JwtSettings from api.infrastructure.dependencies.adapters import create_engine -from api.infrastructure.dependencies.configs import (app_settings, - get_db_settings, - get_jwt_settings) +from api.infrastructure.dependencies.configs import ( + app_settings, + get_db_settings, + get_jwt_settings, +) from api.infrastructure.persistence.db_setings import DBSettings from api.infrastructure.persistence.models import Base from api.infrastructure.settings import Settings diff --git a/api/application/contracts/user/roles.py b/api/application/contracts/user/roles.py new file mode 100644 index 0000000..a36af64 --- /dev/null +++ b/api/application/contracts/user/roles.py @@ -0,0 +1,15 @@ +from enum import Enum + + +class UserRole(Enum): + # base roles + ADMIN = "Administrator" + + # service provider roles + SUPPLIER_EMPLOYER = "Director" + SUPPLIER = "Supplie of service" + SUPPLIER_EMPLOYEE = "Supplie employee" + + CLIENT = "Client" + EMPLOYER = "Client company employer" + EMPLOYEE = "Client company employee" diff --git a/api/application/usecase/company/create_company.py b/api/application/usecase/company/create_company.py index d02491f..de5a3d8 100644 --- a/api/application/usecase/company/create_company.py +++ b/api/application/usecase/company/create_company.py @@ -1,7 +1,6 @@ from api.application.abstractions.transaction import TransactionContextManager from api.application.contracts.company.company_request import CreateNewCompany -from api.application.contracts.company.company_response import \ - CompanyBaseResponse +from api.application.contracts.company.company_response import CompanyBaseResponse from api.domain.company.repository import CompanyRepository diff --git a/api/application/usecase/company/get_users_company.py b/api/application/usecase/company/get_users_company.py index 9c215a5..65e1737 100644 --- a/api/application/usecase/company/get_users_company.py +++ b/api/application/usecase/company/get_users_company.py @@ -1,8 +1,6 @@ from api.application.abstractions.transaction import TransactionContextManager -from api.application.contracts.company.company_request import \ - CompanyByOwnerEmail -from api.application.contracts.company.company_response import \ - CompanyBaseResponse +from api.application.contracts.company.company_request import CompanyByOwnerEmail +from api.application.contracts.company.company_response import CompanyBaseResponse from api.domain.company.repository import CompanyRepository @@ -17,10 +15,5 @@ class GetCompaniesByOwnerEmail: async def execute(self, request: CompanyByOwnerEmail) -> list[CompanyBaseResponse]: async with self.transaction: - companies = await self.company_repository.get_companies_by_owner_email( - filter={"email": request.email} - ) - return [ - CompanyBaseResponse(name=comp.name.value, email=comp.email.value) - for comp in companies - ] + companies = await self.company_repository.get_companies_by_owner_email(filter={"email": request.email}) + return [CompanyBaseResponse(name=comp.name.value, email=comp.email.value) for comp in companies] diff --git a/api/application/usecase/user/get_user_by_email.py b/api/application/usecase/user/get_user_by_email.py index 8933529..3ec9200 100644 --- a/api/application/usecase/user/get_user_by_email.py +++ b/api/application/usecase/user/get_user_by_email.py @@ -4,9 +4,7 @@ from api.domain.user.repository import UserRepository class GetUserByEmail: - def __init__( - self, transaction: TransactionContextManager, user_repository: UserRepository - ) -> None: + def __init__(self, transaction: TransactionContextManager, user_repository: UserRepository) -> None: self.transaction = transaction self.user_repository = user_repository diff --git a/api/domain/company/model.py b/api/domain/company/model.py index 1f4332b..fcbbe1e 100644 --- a/api/domain/company/model.py +++ b/api/domain/company/model.py @@ -16,9 +16,7 @@ class CompanyEmail(DomainValueObject): pattern = r"^[\w\.-]+@[a-zA-Z\d\.-]+\.[a-zA-Z]{2,}$" if not re.match(pattern, self.value): - raise DomainValidationError( - "Invalid email format. Email must be in the format 'example@example.com'." - ) + raise DomainValidationError("Invalid email format. Email must be in the format 'example@example.com'.") @dataclass(frozen=True) @@ -29,9 +27,7 @@ class CompanyName(DomainValueObject): if len(self.value) < 1: raise DomainValidationError("First name must be at least 1 character long.") if len(self.value) > 100: - raise DomainValidationError( - "First name must be at most 100 characters long." - ) + raise DomainValidationError("First name must be at most 100 characters long.") if not self.value.isalpha(): raise DomainValidationError("First name must only contain letters.") diff --git a/api/domain/user/model.py b/api/domain/user/model.py index bb4e899..d71d187 100644 --- a/api/domain/user/model.py +++ b/api/domain/user/model.py @@ -15,9 +15,7 @@ class UserEmail(DomainValueObject): pattern = r"^[\w\.-]+@[a-zA-Z\d\.-]+\.[a-zA-Z]{2,}$" if not re.match(pattern, self.value): - raise DomainValidationError( - "Invalid email format. Email must be in the format 'example@example.com'." - ) + raise DomainValidationError("Invalid email format. Email must be in the format 'example@example.com'.") @dataclass(frozen=True) @@ -28,9 +26,7 @@ class UserFirstName(DomainValueObject): if len(self.value) < 1: raise DomainValidationError("First name must be at least 1 character long.") if len(self.value) > 100: - raise DomainValidationError( - "First name must be at most 100 characters long." - ) + raise DomainValidationError("First name must be at most 100 characters long.") if not self.value.isalpha(): raise DomainValidationError("First name must only contain letters.") @@ -43,9 +39,7 @@ class UserLastName(DomainValueObject): if len(self.value) < 1: raise DomainValidationError("Last name must be at least 1 character long.") if len(self.value) > 100: - raise DomainValidationError( - "Last name must be at most 100 characters long." - ) + raise DomainValidationError("Last name must be at most 100 characters long.") if not self.value.isalpha(): raise DomainValidationError("Last name must only contain letters.") diff --git a/api/infrastructure/dependencies/adapters.py b/api/infrastructure/dependencies/adapters.py index 6d2211d..f6b56d7 100644 --- a/api/infrastructure/dependencies/adapters.py +++ b/api/infrastructure/dependencies/adapters.py @@ -2,19 +2,22 @@ from collections.abc import AsyncIterable from typing import Annotated from fastapi import Depends -from sqlalchemy.ext.asyncio import (AsyncEngine, AsyncSession, - async_sessionmaker, create_async_engine) +from sqlalchemy.ext.asyncio import ( + AsyncEngine, + AsyncSession, + async_sessionmaker, + create_async_engine, +) from api.application.abstractions.transaction import TransactionContextManager from api.infrastructure.dependencies.stub import Stub -from api.infrastructure.persistence.transaction import \ - SqlalchemyTransactionContextManager +from api.infrastructure.persistence.transaction import ( + SqlalchemyTransactionContextManager, +) from api.infrastructure.settings import Settings -def get_transaction_context( - session: Annotated[AsyncSession, Depends(Stub(AsyncSession))] -) -> TransactionContextManager: +def get_transaction_context(session: Annotated[AsyncSession, Depends(Stub(AsyncSession))]) -> TransactionContextManager: return SqlalchemyTransactionContextManager(session) diff --git a/api/infrastructure/dependencies/usecases.py b/api/infrastructure/dependencies/usecases.py index 735076a..ede5e7f 100644 --- a/api/infrastructure/dependencies/usecases.py +++ b/api/infrastructure/dependencies/usecases.py @@ -6,8 +6,7 @@ from api.application.abstractions.transaction import TransactionContextManager from api.application.protocols.password_hasher import PasswordHasher from api.application.usecase.auth.create_user import CreateUser from api.application.usecase.company.create_company import CreateCompany -from api.application.usecase.company.get_users_company import \ - GetCompaniesByOwnerEmail +from api.application.usecase.company.get_users_company import GetCompaniesByOwnerEmail from api.domain.company.repository import CompanyRepository from api.domain.user.repository import UserRepository from api.infrastructure.dependencies.stub import Stub @@ -15,9 +14,7 @@ from api.infrastructure.dependencies.stub import Stub def provide_create_user( user_repository: Annotated[UserRepository, Depends(Stub(UserRepository))], - transaction: Annotated[ - TransactionContextManager, Depends(Stub(TransactionContextManager)) - ], + transaction: Annotated[TransactionContextManager, Depends(Stub(TransactionContextManager))], password_hasher: Annotated[PasswordHasher, Depends(Stub(PasswordHasher))], ) -> CreateUser: return CreateUser( @@ -29,19 +26,13 @@ def provide_create_user( def provide_get_companies_by_email( company_repository: Annotated[CompanyRepository, Depends(Stub(CompanyRepository))], - transaction: Annotated[ - TransactionContextManager, Depends(Stub(TransactionContextManager)) - ], + transaction: Annotated[TransactionContextManager, Depends(Stub(TransactionContextManager))], ) -> GetCompaniesByOwnerEmail: - return GetCompaniesByOwnerEmail( - transaction=transaction, company_repository=company_repository - ) + return GetCompaniesByOwnerEmail(transaction=transaction, company_repository=company_repository) def provide_create_company( company_repository: Annotated[CompanyRepository, Depends(Stub(CompanyRepository))], - transaction: Annotated[ - TransactionContextManager, Depends(Stub(TransactionContextManager)) - ], + transaction: Annotated[TransactionContextManager, Depends(Stub(TransactionContextManager))], ) -> CreateCompany: return CreateCompany(transaction=transaction, company_repository=company_repository) diff --git a/api/infrastructure/persistence/models/company.py b/api/infrastructure/persistence/models/company.py index 05fe150..52be1d3 100644 --- a/api/infrastructure/persistence/models/company.py +++ b/api/infrastructure/persistence/models/company.py @@ -30,9 +30,7 @@ class DepartmentModel(Base): ) name: Mapped[str] address: Mapped[str] - company_id: Mapped[uuid.UUID] = mapped_column( - ForeignKey("company.id", ondelete="CASCADE") - ) + company_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("company.id", ondelete="CASCADE")) class CompanyDepartmentModel(Base): diff --git a/api/infrastructure/persistence/repositories/company_repository.py b/api/infrastructure/persistence/repositories/company_repository.py index 45213eb..6af5bee 100644 --- a/api/infrastructure/persistence/repositories/company_repository.py +++ b/api/infrastructure/persistence/repositories/company_repository.py @@ -1,8 +1,13 @@ from sqlalchemy import text from sqlalchemy.ext.asyncio import AsyncSession -from api.domain.company.model import (Company, CompanyAddress, CompanyEmail, - CompanyId, CompanyName) +from api.domain.company.model import ( + Company, + CompanyAddress, + CompanyEmail, + CompanyId, + CompanyName, +) from api.domain.company.repository import CompanyRepository from api.domain.user.model import UserId diff --git a/api/infrastructure/persistence/repositories/user_repository.py b/api/infrastructure/persistence/repositories/user_repository.py index 2d13d66..950d128 100644 --- a/api/infrastructure/persistence/repositories/user_repository.py +++ b/api/infrastructure/persistence/repositories/user_repository.py @@ -2,8 +2,7 @@ from sqlalchemy import text from sqlalchemy.ext.asyncio import AsyncSession from api.domain.user import User, UserRepository -from api.domain.user.model import (UserEmail, UserFirstName, UserId, - UserLastName) +from api.domain.user.model import UserEmail, UserFirstName, UserId, UserLastName class SqlAlchemyUserRepository(UserRepository): diff --git a/api/presentation/routers/company.py b/api/presentation/routers/company.py index 8cf2b03..7dd9623 100644 --- a/api/presentation/routers/company.py +++ b/api/presentation/routers/company.py @@ -28,16 +28,12 @@ company_router = APIRouter( async def get_my_companies( request: Request, token_processor: Annotated[JwtTokenProcessor, Depends(Stub(JwtTokenProcessor))], - usecase: Annotated[ - GetCompaniesByOwnerEmail, Depends(Stub(GetCompaniesByOwnerEmail)) - ], + usecase: Annotated[GetCompaniesByOwnerEmail, Depends(Stub(GetCompaniesByOwnerEmail))], ) -> list[CompanyBaseResponse]: token_data = token_processor.validate_token(request.scope["auth"]) if not token_data: raise UserValidationError("Login required") - companies = await usecase.execute( - request=CompanyByOwnerEmail(email=token_data[1].value) - ) + companies = await usecase.execute(request=CompanyByOwnerEmail(email=token_data[1].value)) return [ CompanyBaseResponse( name=c.name,