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 __future__ import annotations
from typing import Protocol, runtime_checkable 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 @runtime_checkable
@@ -17,6 +17,3 @@ class IUnitOfWork(Protocol):
@property @property
def session_repository(self) -> ISessionRepository: ... 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_user_repository import IUserRepository
from src.application.abstractions.repositories.i_session_repository import ISessionRepository from src.application.abstractions.repositories.i_session_repository import ISessionRepository
from src.application.abstractions.repositories.i_legal_entity_repository import ILegalEntityRepository

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.abstractions import IUnitOfWork
from src.application.contracts import ILogger, ICache from src.application.contracts import ILogger, ICache
from src.application.domain.entities import UserEntity from src.application.domain.entities import UserEntity
from src.application.domain.enums.account_type import AccountType
from src.infrastructure.database.decorators import transactional from src.infrastructure.database.decorators import transactional
@@ -14,7 +13,5 @@ class GetMeCommand:
@transactional @transactional
async def __call__(self, user_id: str) -> UserEntity: async def __call__(self, user_id: str) -> UserEntity:
user = await self._unit_of_work.user_repository.get_user_by_id(user_id=user_id) 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}') self._logger.info(f'User ID: {user.id}')
return user 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 dataclasses import dataclass
from datetime import date, datetime from datetime import date, datetime
from src.application.domain.entities.legal_entity import LegalEntityEntity
@dataclass(slots=True) @dataclass(slots=True)
class UserEntity: class UserEntity:
@@ -30,6 +28,3 @@ class UserEntity:
created_at: datetime | None = None created_at: datetime | None = None
updated_at: datetime | None = None updated_at: datetime | None = None
kyc_verified_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.base import Base
from src.infrastructure.database.models.user import UserModel 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 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: 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) 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.user_repository import UserRepository
from src.infrastructure.database.repositories.session_repository import SessionRepository from src.infrastructure.database.repositories.session_repository import SessionRepository
from src.infrastructure.database.repositories.legal_entity_repository import LegalEntityRepository

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, is_deleted=user.is_deleted,
created_at=user.created_at, created_at=user.created_at,
updated_at=user.updated_at, updated_at=user.updated_at,
account_type=user.account_type,
) )
async def get_user_by_id(self, user_id: str) -> UserEntity: 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 sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from src.application.abstractions import IUnitOfWork 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.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._session: AsyncSession = None
self._user_repository: IUserRepository = None self._user_repository: IUserRepository = None
self._session_repository: ISessionRepository = None self._session_repository: ISessionRepository = None
self._legal_entity_repository: ILegalEntityRepository = None
self._logger: ILogger = logger self._logger: ILogger = logger
async def __aenter__(self): async def __aenter__(self):
self._logger.debug('UnitOfWork enter') self._logger.debug('UnitOfWork enter')
self._user_repository = None self._user_repository = None
self._session_repository = None self._session_repository = None
self._legal_entity_repository = None
self._session = self.session_factory() self._session = self.session_factory()
return self return self
@@ -46,9 +44,3 @@ class UnitOfWork(IUnitOfWork):
if self._session_repository is None: if self._session_repository is None:
self._session_repository = SessionRepository(session=self._session, logger=self._logger) self._session_repository = SessionRepository(session=self._session, logger=self._logger)
return self._session_repository 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.responses import HTMLResponse
from fastapi.security import HTTPBasic, HTTPBasicCredentials from fastapi.security import HTTPBasic, HTTPBasicCredentials
from starlette.middleware.cors import CORSMiddleware 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.application.domain.exceptions import ApplicationException, UnauthorizedException
from src.infrastructure.cache import create_redis_client from src.infrastructure.cache import create_redis_client
from src.infrastructure.vault import JwtKeyStore, start_jwt_keys_scheduler 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.infrastructure.config import settings
from src.presentation.handlers import ( from src.presentation.handlers import (
application_exception_handler, application_exception_handler,
http_exception_handler,
unhandled_exception_handler, unhandled_exception_handler,
validation_exception_handler,
) )
from src.presentation.middleware import TraceIDMiddleware, SecurityHeadersMiddleware from src.presentation.middleware import TraceIDMiddleware, SecurityHeadersMiddleware
from src.presentation.routing import me_router 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(ApplicationException, application_exception_handler)
app.add_exception_handler(Exception, unhandled_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.unhandled_handler import unhandled_exception_handler
from src.presentation.handlers.application_handler import application_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 pydantic import BaseModel, ConfigDict, Field
from src.application.domain.entities import UserEntity 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): class MeUserPublicResponse(BaseModel):
@@ -66,16 +27,9 @@ class MeUserPublicResponse(BaseModel):
created_at: datetime | None = Field(None, description='Время создания записи') created_at: datetime | None = Field(None, description='Время создания записи')
updated_at: datetime | None = Field(None, description='Время последнего обновления') updated_at: datetime | None = Field(None, description='Время последнего обновления')
kyc_verified_at: datetime | None = Field(None, description='Время подтверждения KYC') 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 @classmethod
def from_user(cls, user: UserEntity) -> MeUserPublicResponse: 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( return cls(
id=user.id, id=user.id,
email=user.email, email=user.email,
@@ -94,8 +48,6 @@ class MeUserPublicResponse(BaseModel):
created_at=user.created_at, created_at=user.created_at,
updated_at=user.updated_at, updated_at=user.updated_at,
kyc_verified_at=user.kyc_verified_at, kyc_verified_at=user.kyc_verified_at,
account_type=user.account_type,
legal_entity=legal_entity,
) )