main
Сергей Ванюшкин 2024-04-23 09:08:56 +00:00
parent ad92682eda
commit 76cf6950ae
16 changed files with 98 additions and 115 deletions

View File

@ -23,7 +23,7 @@ repos:
rev: v3.15.2 rev: v3.15.2
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [ --py310-plus ] args: [ --py3-plus ]
# Форматирует код под PEP8 # Форматирует код под PEP8
- repo: https://github.com/pre-commit/mirrors-autopep8 - repo: https://github.com/pre-commit/mirrors-autopep8
@ -46,7 +46,7 @@ repos:
rev: 24.3.0 rev: 24.3.0
hooks: hooks:
- id: black - id: black
language_version: python3.10 language_version: python3
args: [ "--line-length=120" ] args: [ "--line-length=120" ]
# Проверка статических типов с помощью mypy # Проверка статических типов с помощью mypy

View File

@ -1,6 +1,5 @@
from fastapi import FastAPI from fastapi import FastAPI
from sqlalchemy.ext.asyncio import (AsyncEngine, AsyncSession, from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker
async_sessionmaker)
from api.application.abstractions.transaction import TransactionContextManager from api.application.abstractions.transaction import TransactionContextManager
from api.application.protocols.date_time import DateTimeProvider 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.auth_user import LoginUser
from api.application.usecase.auth.create_user import CreateUser from api.application.usecase.auth.create_user import CreateUser
from api.application.usecase.company.create_company import CreateCompany from api.application.usecase.company.create_company import CreateCompany
from api.application.usecase.company.get_users_company import \ from api.application.usecase.company.get_users_company import GetCompaniesByOwnerEmail
GetCompaniesByOwnerEmail
from api.domain.company.repository import CompanyRepository from api.domain.company.repository import CompanyRepository
from api.domain.user.repository import UserRepository from api.domain.user.repository import UserRepository
from api.infrastructure.auth.jwt_settings import JwtSettings from api.infrastructure.auth.jwt_settings import JwtSettings
from api.infrastructure.dependencies.adapters import (create_engine, from api.infrastructure.dependencies.adapters import (
create_session_maker, create_engine,
get_transaction_context, create_session_maker,
new_session) get_transaction_context,
from api.infrastructure.dependencies.configs import (app_settings, new_session,
get_db_settings, )
get_jwt_settings) from api.infrastructure.dependencies.configs import (
from api.infrastructure.dependencies.protocols import (get_date_time_provider, app_settings,
get_jwt_token_processor, get_db_settings,
get_password_hasher, get_jwt_settings,
get_user_login) )
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 ( from api.infrastructure.dependencies.repositories import (
get_company_repository, get_user_repository) get_company_repository,
get_user_repository,
)
from api.infrastructure.dependencies.usecases import ( from api.infrastructure.dependencies.usecases import (
provide_create_company, provide_create_user, provide_create_company,
provide_get_companies_by_email) provide_create_user,
provide_get_companies_by_email,
)
from api.infrastructure.persistence.db_setings import DBSettings from api.infrastructure.persistence.db_setings import DBSettings
from api.infrastructure.settings import Settings from api.infrastructure.settings import Settings

View File

@ -2,27 +2,23 @@ from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from api.domain.error import DomainValidationError from api.domain.error import DomainValidationError
from api.domain.user.error import (UserAlreadyExistsError, from api.domain.user.error import (
UserInvalidCredentialsError, UserAlreadyExistsError,
UserIsNotAuthorizedError) UserInvalidCredentialsError,
UserIsNotAuthorizedError,
)
from api.infrastructure.persistence.error import TransactionContextManagerError from api.infrastructure.persistence.error import TransactionContextManagerError
async def transaction_error_exec_handler( async def transaction_error_exec_handler(request: Request, exc: TransactionContextManagerError) -> JSONResponse:
request: Request, exc: TransactionContextManagerError
) -> JSONResponse:
return JSONResponse(status_code=400, content={"detail": exc.message}) return JSONResponse(status_code=400, content={"detail": exc.message})
async def validation_error_exc_handler( async def validation_error_exc_handler(request: Request, exc: DomainValidationError) -> JSONResponse:
request: Request, exc: DomainValidationError
) -> JSONResponse:
return JSONResponse(status_code=400, content={"detail": exc.message}) return JSONResponse(status_code=400, content={"detail": exc.message})
async def user_authentication_error_exc_handler( async def user_authentication_error_exc_handler(request: Request, exc: UserIsNotAuthorizedError) -> JSONResponse:
request: Request, exc: UserIsNotAuthorizedError
) -> JSONResponse:
return JSONResponse( return JSONResponse(
status_code=401, status_code=401,
content={"detail": exc.message}, content={"detail": exc.message},
@ -30,9 +26,7 @@ async def user_authentication_error_exc_handler(
) )
async def user_already_exist_error_exc_handler( async def user_already_exist_error_exc_handler(request: Request, exc: UserAlreadyExistsError) -> JSONResponse:
request: Request, exc: UserAlreadyExistsError
) -> JSONResponse:
return JSONResponse(status_code=409, content={"detail": exc.message}) return JSONResponse(status_code=409, content={"detail": exc.message})
@ -47,16 +41,8 @@ def init_exc_handlers(app: FastAPI) -> None:
DomainValidationError, DomainValidationError,
validation_error_exc_handler, validation_error_exc_handler,
) )
app.add_exception_handler( app.add_exception_handler(UserIsNotAuthorizedError, user_authentication_error_exc_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(
UserAlreadyExistsError, user_already_exist_error_exc_handler
)
app.add_exception_handler(
UserInvalidCredentialsError, user_invalid_credentials_error_exc_handler
)
app.add_exception_handler( app.add_exception_handler(TransactionContextManagerError, transaction_error_exec_handler)
TransactionContextManagerError, transaction_error_exec_handler
)

View File

@ -8,9 +8,11 @@ from api.app_entrypoint.dependencies import init_dependencies
from api.app_entrypoint.error_handlers import init_exc_handlers from api.app_entrypoint.error_handlers import init_exc_handlers
from api.infrastructure.auth.jwt_settings import JwtSettings from api.infrastructure.auth.jwt_settings import JwtSettings
from api.infrastructure.dependencies.adapters import create_engine from api.infrastructure.dependencies.adapters import create_engine
from api.infrastructure.dependencies.configs import (app_settings, from api.infrastructure.dependencies.configs import (
get_db_settings, app_settings,
get_jwt_settings) get_db_settings,
get_jwt_settings,
)
from api.infrastructure.persistence.db_setings import DBSettings from api.infrastructure.persistence.db_setings import DBSettings
from api.infrastructure.persistence.models import Base from api.infrastructure.persistence.models import Base
from api.infrastructure.settings import Settings from api.infrastructure.settings import Settings

View File

@ -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"

View File

@ -1,7 +1,6 @@
from api.application.abstractions.transaction import TransactionContextManager from api.application.abstractions.transaction import TransactionContextManager
from api.application.contracts.company.company_request import CreateNewCompany from api.application.contracts.company.company_request import CreateNewCompany
from api.application.contracts.company.company_response import \ from api.application.contracts.company.company_response import CompanyBaseResponse
CompanyBaseResponse
from api.domain.company.repository import CompanyRepository from api.domain.company.repository import CompanyRepository

View File

@ -1,8 +1,6 @@
from api.application.abstractions.transaction import TransactionContextManager from api.application.abstractions.transaction import TransactionContextManager
from api.application.contracts.company.company_request import \ from api.application.contracts.company.company_request import CompanyByOwnerEmail
CompanyByOwnerEmail 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 from api.domain.company.repository import CompanyRepository
@ -17,10 +15,5 @@ class GetCompaniesByOwnerEmail:
async def execute(self, request: CompanyByOwnerEmail) -> list[CompanyBaseResponse]: async def execute(self, request: CompanyByOwnerEmail) -> list[CompanyBaseResponse]:
async with self.transaction: async with self.transaction:
companies = await self.company_repository.get_companies_by_owner_email( companies = await self.company_repository.get_companies_by_owner_email(filter={"email": request.email})
filter={"email": request.email} return [CompanyBaseResponse(name=comp.name.value, email=comp.email.value) for comp in companies]
)
return [
CompanyBaseResponse(name=comp.name.value, email=comp.email.value)
for comp in companies
]

View File

@ -4,9 +4,7 @@ from api.domain.user.repository import UserRepository
class GetUserByEmail: class GetUserByEmail:
def __init__( def __init__(self, transaction: TransactionContextManager, user_repository: UserRepository) -> None:
self, transaction: TransactionContextManager, user_repository: UserRepository
) -> None:
self.transaction = transaction self.transaction = transaction
self.user_repository = user_repository self.user_repository = user_repository

View File

@ -16,9 +16,7 @@ class CompanyEmail(DomainValueObject):
pattern = r"^[\w\.-]+@[a-zA-Z\d\.-]+\.[a-zA-Z]{2,}$" pattern = r"^[\w\.-]+@[a-zA-Z\d\.-]+\.[a-zA-Z]{2,}$"
if not re.match(pattern, self.value): if not re.match(pattern, self.value):
raise DomainValidationError( raise DomainValidationError("Invalid email format. Email must be in the format 'example@example.com'.")
"Invalid email format. Email must be in the format 'example@example.com'."
)
@dataclass(frozen=True) @dataclass(frozen=True)
@ -29,9 +27,7 @@ class CompanyName(DomainValueObject):
if len(self.value) < 1: if len(self.value) < 1:
raise DomainValidationError("First name must be at least 1 character long.") raise DomainValidationError("First name must be at least 1 character long.")
if len(self.value) > 100: if len(self.value) > 100:
raise DomainValidationError( raise DomainValidationError("First name must be at most 100 characters long.")
"First name must be at most 100 characters long."
)
if not self.value.isalpha(): if not self.value.isalpha():
raise DomainValidationError("First name must only contain letters.") raise DomainValidationError("First name must only contain letters.")

View File

@ -15,9 +15,7 @@ class UserEmail(DomainValueObject):
pattern = r"^[\w\.-]+@[a-zA-Z\d\.-]+\.[a-zA-Z]{2,}$" pattern = r"^[\w\.-]+@[a-zA-Z\d\.-]+\.[a-zA-Z]{2,}$"
if not re.match(pattern, self.value): if not re.match(pattern, self.value):
raise DomainValidationError( raise DomainValidationError("Invalid email format. Email must be in the format 'example@example.com'.")
"Invalid email format. Email must be in the format 'example@example.com'."
)
@dataclass(frozen=True) @dataclass(frozen=True)
@ -28,9 +26,7 @@ class UserFirstName(DomainValueObject):
if len(self.value) < 1: if len(self.value) < 1:
raise DomainValidationError("First name must be at least 1 character long.") raise DomainValidationError("First name must be at least 1 character long.")
if len(self.value) > 100: if len(self.value) > 100:
raise DomainValidationError( raise DomainValidationError("First name must be at most 100 characters long.")
"First name must be at most 100 characters long."
)
if not self.value.isalpha(): if not self.value.isalpha():
raise DomainValidationError("First name must only contain letters.") raise DomainValidationError("First name must only contain letters.")
@ -43,9 +39,7 @@ class UserLastName(DomainValueObject):
if len(self.value) < 1: if len(self.value) < 1:
raise DomainValidationError("Last name must be at least 1 character long.") raise DomainValidationError("Last name must be at least 1 character long.")
if len(self.value) > 100: if len(self.value) > 100:
raise DomainValidationError( raise DomainValidationError("Last name must be at most 100 characters long.")
"Last name must be at most 100 characters long."
)
if not self.value.isalpha(): if not self.value.isalpha():
raise DomainValidationError("Last name must only contain letters.") raise DomainValidationError("Last name must only contain letters.")

View File

@ -2,19 +2,22 @@ from collections.abc import AsyncIterable
from typing import Annotated from typing import Annotated
from fastapi import Depends from fastapi import Depends
from sqlalchemy.ext.asyncio import (AsyncEngine, AsyncSession, from sqlalchemy.ext.asyncio import (
async_sessionmaker, create_async_engine) AsyncEngine,
AsyncSession,
async_sessionmaker,
create_async_engine,
)
from api.application.abstractions.transaction import TransactionContextManager from api.application.abstractions.transaction import TransactionContextManager
from api.infrastructure.dependencies.stub import Stub from api.infrastructure.dependencies.stub import Stub
from api.infrastructure.persistence.transaction import \ from api.infrastructure.persistence.transaction import (
SqlalchemyTransactionContextManager SqlalchemyTransactionContextManager,
)
from api.infrastructure.settings import Settings from api.infrastructure.settings import Settings
def get_transaction_context( def get_transaction_context(session: Annotated[AsyncSession, Depends(Stub(AsyncSession))]) -> TransactionContextManager:
session: Annotated[AsyncSession, Depends(Stub(AsyncSession))]
) -> TransactionContextManager:
return SqlalchemyTransactionContextManager(session) return SqlalchemyTransactionContextManager(session)

View File

@ -6,8 +6,7 @@ from api.application.abstractions.transaction import TransactionContextManager
from api.application.protocols.password_hasher import PasswordHasher from api.application.protocols.password_hasher import PasswordHasher
from api.application.usecase.auth.create_user import CreateUser from api.application.usecase.auth.create_user import CreateUser
from api.application.usecase.company.create_company import CreateCompany from api.application.usecase.company.create_company import CreateCompany
from api.application.usecase.company.get_users_company import \ from api.application.usecase.company.get_users_company import GetCompaniesByOwnerEmail
GetCompaniesByOwnerEmail
from api.domain.company.repository import CompanyRepository from api.domain.company.repository import CompanyRepository
from api.domain.user.repository import UserRepository from api.domain.user.repository import UserRepository
from api.infrastructure.dependencies.stub import Stub from api.infrastructure.dependencies.stub import Stub
@ -15,9 +14,7 @@ from api.infrastructure.dependencies.stub import Stub
def provide_create_user( def provide_create_user(
user_repository: Annotated[UserRepository, Depends(Stub(UserRepository))], user_repository: Annotated[UserRepository, Depends(Stub(UserRepository))],
transaction: Annotated[ transaction: Annotated[TransactionContextManager, Depends(Stub(TransactionContextManager))],
TransactionContextManager, Depends(Stub(TransactionContextManager))
],
password_hasher: Annotated[PasswordHasher, Depends(Stub(PasswordHasher))], password_hasher: Annotated[PasswordHasher, Depends(Stub(PasswordHasher))],
) -> CreateUser: ) -> CreateUser:
return CreateUser( return CreateUser(
@ -29,19 +26,13 @@ def provide_create_user(
def provide_get_companies_by_email( def provide_get_companies_by_email(
company_repository: Annotated[CompanyRepository, Depends(Stub(CompanyRepository))], company_repository: Annotated[CompanyRepository, Depends(Stub(CompanyRepository))],
transaction: Annotated[ transaction: Annotated[TransactionContextManager, Depends(Stub(TransactionContextManager))],
TransactionContextManager, Depends(Stub(TransactionContextManager))
],
) -> GetCompaniesByOwnerEmail: ) -> GetCompaniesByOwnerEmail:
return GetCompaniesByOwnerEmail( return GetCompaniesByOwnerEmail(transaction=transaction, company_repository=company_repository)
transaction=transaction, company_repository=company_repository
)
def provide_create_company( def provide_create_company(
company_repository: Annotated[CompanyRepository, Depends(Stub(CompanyRepository))], company_repository: Annotated[CompanyRepository, Depends(Stub(CompanyRepository))],
transaction: Annotated[ transaction: Annotated[TransactionContextManager, Depends(Stub(TransactionContextManager))],
TransactionContextManager, Depends(Stub(TransactionContextManager))
],
) -> CreateCompany: ) -> CreateCompany:
return CreateCompany(transaction=transaction, company_repository=company_repository) return CreateCompany(transaction=transaction, company_repository=company_repository)

View File

@ -30,9 +30,7 @@ class DepartmentModel(Base):
) )
name: Mapped[str] name: Mapped[str]
address: Mapped[str] address: Mapped[str]
company_id: Mapped[uuid.UUID] = mapped_column( company_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("company.id", ondelete="CASCADE"))
ForeignKey("company.id", ondelete="CASCADE")
)
class CompanyDepartmentModel(Base): class CompanyDepartmentModel(Base):

View File

@ -1,8 +1,13 @@
from sqlalchemy import text from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from api.domain.company.model import (Company, CompanyAddress, CompanyEmail, from api.domain.company.model import (
CompanyId, CompanyName) Company,
CompanyAddress,
CompanyEmail,
CompanyId,
CompanyName,
)
from api.domain.company.repository import CompanyRepository from api.domain.company.repository import CompanyRepository
from api.domain.user.model import UserId from api.domain.user.model import UserId

View File

@ -2,8 +2,7 @@ from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from api.domain.user import User, UserRepository from api.domain.user import User, UserRepository
from api.domain.user.model import (UserEmail, UserFirstName, UserId, from api.domain.user.model import UserEmail, UserFirstName, UserId, UserLastName
UserLastName)
class SqlAlchemyUserRepository(UserRepository): class SqlAlchemyUserRepository(UserRepository):

View File

@ -28,16 +28,12 @@ company_router = APIRouter(
async def get_my_companies( async def get_my_companies(
request: Request, request: Request,
token_processor: Annotated[JwtTokenProcessor, Depends(Stub(JwtTokenProcessor))], token_processor: Annotated[JwtTokenProcessor, Depends(Stub(JwtTokenProcessor))],
usecase: Annotated[ usecase: Annotated[GetCompaniesByOwnerEmail, Depends(Stub(GetCompaniesByOwnerEmail))],
GetCompaniesByOwnerEmail, Depends(Stub(GetCompaniesByOwnerEmail))
],
) -> list[CompanyBaseResponse]: ) -> list[CompanyBaseResponse]:
token_data = token_processor.validate_token(request.scope["auth"]) token_data = token_processor.validate_token(request.scope["auth"])
if not token_data: if not token_data:
raise UserValidationError("Login required") raise UserValidationError("Login required")
companies = await usecase.execute( companies = await usecase.execute(request=CompanyByOwnerEmail(email=token_data[1].value))
request=CompanyByOwnerEmail(email=token_data[1].value)
)
return [ return [
CompanyBaseResponse( CompanyBaseResponse(
name=c.name, name=c.name,