2 Commits

Author SHA1 Message Date
21cf44cebc refactor: delete handlers imports 2026-05-28 15:44:44 +03:00
b73d5231f5 refactor: delete useless handlers 2026-05-28 14:36:59 +03:00
19 changed files with 6 additions and 230 deletions

View File

@@ -1,6 +1,6 @@
from __future__ import annotations
from typing import Protocol, runtime_checkable
from src.application.abstractions.repositories import IUserRepository, ISessionRepository, ILegalEntityRepository
from src.application.abstractions.repositories import IUserRepository, ISessionRepository
@runtime_checkable
@@ -17,6 +17,3 @@ class IUnitOfWork(Protocol):
@property
def session_repository(self) -> ISessionRepository: ...
@property
def legal_entity_repository(self) -> ILegalEntityRepository: ...

View File

@@ -1,3 +1,2 @@
from src.application.abstractions.repositories.i_user_repository import IUserRepository
from src.application.abstractions.repositories.i_session_repository import ISessionRepository
from src.application.abstractions.repositories.i_legal_entity_repository import ILegalEntityRepository
from src.application.abstractions.repositories.i_session_repository import ISessionRepository

View File

@@ -1,9 +0,0 @@
from abc import ABC, abstractmethod
from src.application.domain.entities.legal_entity import LegalEntityEntity
class ILegalEntityRepository(ABC):
@abstractmethod
async def get_by_user_id(self, user_id: str) -> LegalEntityEntity | None:
raise NotImplementedError

View File

@@ -1,7 +1,6 @@
from src.application.abstractions import IUnitOfWork
from src.application.contracts import ILogger, ICache
from src.application.domain.entities import UserEntity
from src.application.domain.enums.account_type import AccountType
from src.infrastructure.database.decorators import transactional
@@ -14,7 +13,5 @@ class GetMeCommand:
@transactional
async def __call__(self, user_id: str) -> UserEntity:
user = await self._unit_of_work.user_repository.get_user_by_id(user_id=user_id)
if user.account_type == AccountType.LEGAL_ENTITY.value:
user.legal_entity = await self._unit_of_work.legal_entity_repository.get_by_user_id(user_id)
self._logger.info(f'User ID: {user.id}')
return user

View File

@@ -1,24 +0,0 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from typing import Any
@dataclass(slots=True)
class LegalEntityEntity:
id: str
user_id: str
name: str
inn: str
status: str
short_name: str | None = None
ogrn: str | None = None
kpp: str | None = None
legal_address: str | None = None
actual_address: str | None = None
bank_details: dict[str, Any] | None = None
contact_person: str | None = None
contact_phone: str | None = None
kyc_verified: bool = True
kyc_verified_at: datetime | None = None

View File

@@ -2,8 +2,6 @@ from __future__ import annotations
from dataclasses import dataclass
from datetime import date, datetime
from src.application.domain.entities.legal_entity import LegalEntityEntity
@dataclass(slots=True)
class UserEntity:
@@ -30,6 +28,3 @@ class UserEntity:
created_at: datetime | None = None
updated_at: datetime | None = None
kyc_verified_at: datetime | None = None
account_type: str = 'individual'
legal_entity: LegalEntityEntity | None = None

View File

@@ -1,6 +0,0 @@
from enum import StrEnum
class AccountType(StrEnum):
INDIVIDUAL = 'individual'
LEGAL_ENTITY = 'legal_entity'

View File

@@ -1,7 +1,6 @@
from src.infrastructure.database.models.base import Base
from src.infrastructure.database.models.user import UserModel
from src.infrastructure.database.models.legal_entity import LegalEntityModel
from src.infrastructure.database.models.sessions import Session
__all__ = ['Base', 'UserModel', 'LegalEntityModel', 'Session']
__all__ = ['Base', 'UserModel', 'Session']

View File

@@ -1,32 +0,0 @@
from __future__ import annotations
from datetime import datetime
from typing import Any
from sqlalchemy import Boolean, DateTime, ForeignKey, String, Text
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column
from src.infrastructure.database.models.base import Base
from src.infrastructure.database.models.mixins import AuditTimestampsMixin, UlidPrimaryKeyMixin
class LegalEntityModel(Base, UlidPrimaryKeyMixin, AuditTimestampsMixin):
__tablename__ = 'legal_entities'
user_id: Mapped[str] = mapped_column(String(26), ForeignKey('users.id', ondelete='RESTRICT'), nullable=False, unique=True, index=True)
name: Mapped[str] = mapped_column(String(512), nullable=False)
short_name: Mapped[str | None] = mapped_column(String(256), nullable=True)
inn: Mapped[str] = mapped_column(String(12), nullable=False, index=True)
ogrn: Mapped[str | None] = mapped_column(String(15), nullable=True)
kpp: Mapped[str | None] = mapped_column(String(9), nullable=True)
legal_address: Mapped[str | None] = mapped_column(Text, nullable=True)
actual_address: Mapped[str | None] = mapped_column(Text, nullable=True)
bank_details: Mapped[dict[str, Any] | None] = mapped_column(JSONB, nullable=True)
contact_person: Mapped[str | None] = mapped_column(String(256), nullable=True)
contact_phone: Mapped[str | None] = mapped_column(String(16), nullable=True)
status: Mapped[str] = mapped_column(String(32), nullable=False, server_default='active', default='active')
kyc_verified: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default='true', default=True)
kyc_verified_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
encrypted_mnemonic: Mapped[str | None] = mapped_column(Text, nullable=True)
created_by: Mapped[str | None] = mapped_column(String(26), nullable=True)

View File

@@ -27,7 +27,3 @@ class UserModel(Base, UlidPrimaryKeyMixin, AuditTimestampsMixin, SoftDeleteMixin
kyc_verified: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default='false', default=False)
kyc_verified_at: Mapped[DateTime | None] = mapped_column(DateTime(timezone=True), nullable=True)
account_type: Mapped[str] = mapped_column(String(20), nullable=False, server_default='individual', default='individual')
provisioned_by: Mapped[str | None] = mapped_column(String(26), nullable=True)
provisioned_at: Mapped[DateTime | None] = mapped_column(DateTime(timezone=True), nullable=True)

View File

@@ -1,3 +1,2 @@
from src.infrastructure.database.repositories.user_repository import UserRepository
from src.infrastructure.database.repositories.session_repository import SessionRepository
from src.infrastructure.database.repositories.legal_entity_repository import LegalEntityRepository
from src.infrastructure.database.repositories.session_repository import SessionRepository

View File

@@ -1,49 +0,0 @@
from __future__ import annotations
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.exc import SQLAlchemyError
from src.application.abstractions.repositories.i_legal_entity_repository import ILegalEntityRepository
from src.application.contracts import ILogger
from src.application.domain.entities.legal_entity import LegalEntityEntity
from src.application.domain.exceptions import InternalException
from src.infrastructure.database.models.legal_entity import LegalEntityModel
class LegalEntityRepository(ILegalEntityRepository):
def __init__(self, session: AsyncSession, logger: ILogger):
self._session = session
self._logger = logger
@staticmethod
def _to_entity(model: LegalEntityModel) -> LegalEntityEntity:
return LegalEntityEntity(
id=model.id,
user_id=model.user_id,
name=model.name,
inn=model.inn,
status=model.status,
short_name=model.short_name,
ogrn=model.ogrn,
kpp=model.kpp,
legal_address=model.legal_address,
actual_address=model.actual_address,
bank_details=model.bank_details,
contact_person=model.contact_person,
contact_phone=model.contact_phone,
kyc_verified=model.kyc_verified,
kyc_verified_at=model.kyc_verified_at,
)
async def get_by_user_id(self, user_id: str) -> LegalEntityEntity | None:
try:
stmt = select(LegalEntityModel).where(LegalEntityModel.user_id == user_id)
result = await self._session.execute(stmt)
model = result.scalar_one_or_none()
if model is None:
return None
return self._to_entity(model)
except SQLAlchemyError as exc:
self._logger.exception(str(exc))
raise InternalException(message=f'Database error: {exc}') from exc

View File

@@ -50,7 +50,6 @@ class UserRepository(IUserRepository):
is_deleted=user.is_deleted,
created_at=user.created_at,
updated_at=user.updated_at,
account_type=user.account_type,
)
async def get_user_by_id(self, user_id: str) -> UserEntity:

View File

@@ -1,8 +1,8 @@
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from src.application.abstractions import IUnitOfWork
from src.application.abstractions.repositories import IUserRepository, ISessionRepository, ILegalEntityRepository
from src.application.abstractions.repositories import IUserRepository, ISessionRepository
from src.application.contracts import ILogger
from src.infrastructure.database.repositories import UserRepository, SessionRepository, LegalEntityRepository
from src.infrastructure.database.repositories import UserRepository, SessionRepository
@@ -12,14 +12,12 @@ class UnitOfWork(IUnitOfWork):
self._session: AsyncSession = None
self._user_repository: IUserRepository = None
self._session_repository: ISessionRepository = None
self._legal_entity_repository: ILegalEntityRepository = None
self._logger: ILogger = logger
async def __aenter__(self):
self._logger.debug('UnitOfWork enter')
self._user_repository = None
self._session_repository = None
self._legal_entity_repository = None
self._session = self.session_factory()
return self
@@ -46,9 +44,3 @@ class UnitOfWork(IUnitOfWork):
if self._session_repository is None:
self._session_repository = SessionRepository(session=self._session, logger=self._logger)
return self._session_repository
@property
def legal_entity_repository(self) -> ILegalEntityRepository:
if self._legal_entity_repository is None:
self._legal_entity_repository = LegalEntityRepository(session=self._session, logger=self._logger)
return self._legal_entity_repository

View File

@@ -7,8 +7,6 @@ from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
from fastapi.responses import HTMLResponse
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from starlette.middleware.cors import CORSMiddleware
from starlette.exceptions import HTTPException
from fastapi.exceptions import RequestValidationError
from src.application.domain.exceptions import ApplicationException, UnauthorizedException
from src.infrastructure.cache import create_redis_client
from src.infrastructure.vault import JwtKeyStore, start_jwt_keys_scheduler
@@ -17,9 +15,7 @@ from src.infrastructure.logger import logger
from src.infrastructure.config import settings
from src.presentation.handlers import (
application_exception_handler,
http_exception_handler,
unhandled_exception_handler,
validation_exception_handler,
)
from src.presentation.middleware import TraceIDMiddleware, SecurityHeadersMiddleware
from src.presentation.routing import me_router
@@ -84,8 +80,6 @@ app: FastAPI = FastAPI(
},
)
app.add_exception_handler(RequestValidationError, validation_exception_handler)
app.add_exception_handler(HTTPException, http_exception_handler)
app.add_exception_handler(ApplicationException, application_exception_handler)
app.add_exception_handler(Exception, unhandled_exception_handler)

View File

@@ -1,4 +1,2 @@
from src.presentation.handlers.unhandled_handler import unhandled_exception_handler
from src.presentation.handlers.application_handler import application_exception_handler
from src.presentation.handlers.http_exception_handler import http_exception_handler
from src.presentation.handlers.validation_handler import validation_exception_handler

View File

@@ -1,11 +0,0 @@
from fastapi import Request
from fastapi.responses import ORJSONResponse
from starlette.exceptions import HTTPException
async def http_exception_handler(_request: Request, exc: HTTPException) -> ORJSONResponse:
return ORJSONResponse(
status_code=exc.status_code,
content={'detail': exc.detail},
headers=dict(exc.headers) if exc.headers else None,
)

View File

@@ -1,10 +0,0 @@
from fastapi import Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import ORJSONResponse
async def validation_exception_handler(_request: Request, exc: RequestValidationError) -> ORJSONResponse:
return ORJSONResponse(
status_code=422,
content={'detail': exc.errors()},
)

View File

@@ -5,45 +5,6 @@ from datetime import date, datetime
from pydantic import BaseModel, ConfigDict, Field
from src.application.domain.entities import UserEntity
from src.application.domain.entities.legal_entity import LegalEntityEntity
class LegalEntityPublicResponse(BaseModel):
model_config = ConfigDict(from_attributes=False)
id: str
name: str
inn: str
status: str
short_name: str | None = None
ogrn: str | None = None
kpp: str | None = None
legal_address: str | None = None
actual_address: str | None = None
bank_details: dict | None = None
contact_person: str | None = None
contact_phone: str | None = None
kyc_verified: bool | None = None
kyc_verified_at: datetime | None = None
@classmethod
def from_entity(cls, entity: LegalEntityEntity) -> LegalEntityPublicResponse:
return cls(
id=entity.id,
name=entity.name,
inn=entity.inn,
status=entity.status,
short_name=entity.short_name,
ogrn=entity.ogrn,
kpp=entity.kpp,
legal_address=entity.legal_address,
actual_address=entity.actual_address,
bank_details=entity.bank_details,
contact_person=entity.contact_person,
contact_phone=entity.contact_phone,
kyc_verified=entity.kyc_verified,
kyc_verified_at=entity.kyc_verified_at,
)
class MeUserPublicResponse(BaseModel):
@@ -66,16 +27,9 @@ class MeUserPublicResponse(BaseModel):
created_at: datetime | None = Field(None, description='Время создания записи')
updated_at: datetime | None = Field(None, description='Время последнего обновления')
kyc_verified_at: datetime | None = Field(None, description='Время подтверждения KYC')
account_type: str | None = Field(None, description='individual | legal_entity')
legal_entity: LegalEntityPublicResponse | None = Field(None, description='Профиль юрлица')
@classmethod
def from_user(cls, user: UserEntity) -> MeUserPublicResponse:
legal_entity = (
LegalEntityPublicResponse.from_entity(user.legal_entity)
if user.legal_entity is not None
else None
)
return cls(
id=user.id,
email=user.email,
@@ -94,8 +48,6 @@ class MeUserPublicResponse(BaseModel):
created_at=user.created_at,
updated_at=user.updated_at,
kyc_verified_at=user.kyc_verified_at,
account_type=user.account_type,
legal_entity=legal_entity,
)