Compare commits
2 Commits
99714ac476
...
admin_dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 4aae631c73 | |||
| e0f044b455 |
@@ -9,6 +9,7 @@ from src.application.abstractions.repositories import (
|
|||||||
IOrganizationWalletRepository,
|
IOrganizationWalletRepository,
|
||||||
IPurchaseRequestRepository,
|
IPurchaseRequestRepository,
|
||||||
IUserRepository,
|
IUserRepository,
|
||||||
|
IWalletRepository,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -37,3 +38,6 @@ class IUnitOfWork(Protocol):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def purchase_request_repository(self) -> IPurchaseRequestRepository: ...
|
def purchase_request_repository(self) -> IPurchaseRequestRepository: ...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def wallet_repository(self) -> IWalletRepository: ...
|
||||||
@@ -4,3 +4,4 @@ from src.application.abstractions.repositories.i_admin_session_repository import
|
|||||||
from src.application.abstractions.repositories.i_legal_entity_repository import ILegalEntityRepository
|
from src.application.abstractions.repositories.i_legal_entity_repository import ILegalEntityRepository
|
||||||
from src.application.abstractions.repositories.i_organization_wallet_repository import IOrganizationWalletRepository
|
from src.application.abstractions.repositories.i_organization_wallet_repository import IOrganizationWalletRepository
|
||||||
from src.application.abstractions.repositories.i_purchase_request_repository import IPurchaseRequestRepository
|
from src.application.abstractions.repositories.i_purchase_request_repository import IPurchaseRequestRepository
|
||||||
|
from src.application.abstractions.repositories.i_wallets_repository import IWalletRepository
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ class IUserRepository(ABC):
|
|||||||
kyc_verified_at: datetime,
|
kyc_verified_at: datetime,
|
||||||
) -> UserEntity:
|
) -> UserEntity:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def list_all(self, *, limit: int, offset: int, search: str | None = None) -> list[UserEntity]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def get_by_id(self, user_id: str) -> UserEntity:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def get_user_by_email(self, email: str) -> UserEntity:
|
async def get_user_by_email(self, email: str) -> UserEntity:
|
||||||
@@ -35,6 +43,10 @@ class IUserRepository(ABC):
|
|||||||
async def count_individuals(self, *, query: str) -> int:
|
async def count_individuals(self, *, query: str) -> int:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def count_all(self, *, search: str) -> int:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def set_password(self, user_id: str, password_hash: str) -> UserEntity:
|
async def set_password(self, user_id: str, password_hash: str) -> UserEntity:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
from src.application.domain.entities.user import WalletEntity
|
||||||
|
|
||||||
|
|
||||||
|
class IWalletRepository(ABC):
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def list_by_user(self, user_id: str) -> list[WalletEntity]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def exists_for_user(self, user_id: str) -> bool:
|
||||||
|
raise NotImplementedError
|
||||||
@@ -29,6 +29,15 @@ from src.application.commands.purchase_request_commands import (
|
|||||||
UpdatePurchaseRequestStatusCommand,
|
UpdatePurchaseRequestStatusCommand,
|
||||||
SetPurchaseRequestQuoteCommand,
|
SetPurchaseRequestQuoteCommand,
|
||||||
)
|
)
|
||||||
|
from src.application.commands.user_commands import (
|
||||||
|
ListUsersCommand,
|
||||||
|
GetUserCommand,
|
||||||
|
)
|
||||||
|
from src.application.commands.user_wallet_commands import (
|
||||||
|
GetUserMnemonicCommand,
|
||||||
|
GetUserSecretKeysCommand,
|
||||||
|
ListUserWalletsCommand,
|
||||||
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AdminLoginCommand',
|
'AdminLoginCommand',
|
||||||
@@ -54,4 +63,9 @@ __all__ = [
|
|||||||
'GetOrganizationMnemonicCommand',
|
'GetOrganizationMnemonicCommand',
|
||||||
'GetOrganizationSecretKeysCommand',
|
'GetOrganizationSecretKeysCommand',
|
||||||
'SetPasswordCommand',
|
'SetPasswordCommand',
|
||||||
|
'ListUsersCommand',
|
||||||
|
'GetUserCommand',
|
||||||
|
'ListUserWalletsCommand',
|
||||||
|
'GetUserMnemonicCommand',
|
||||||
|
'GetUserSecretKeysCommand',
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -16,14 +16,14 @@ class SetPasswordCommand:
|
|||||||
self._logger = logger
|
self._logger = logger
|
||||||
|
|
||||||
@transactional
|
@transactional
|
||||||
async def __call__(self, email: str, password: str) -> bool:
|
async def __call__(self, user_id: str, password: str) -> bool:
|
||||||
try:
|
try:
|
||||||
password_hash = await self._hash_service.hash(password)
|
password_hash = await self._hash_service.hash(password)
|
||||||
await self._unit_of_work.user_repository.set_password(
|
await self._unit_of_work.user_repository.set_password(
|
||||||
user_email=email,
|
user_id=user_id,
|
||||||
password_hash=password_hash,
|
password_hash=password_hash,
|
||||||
)
|
)
|
||||||
self._logger.info(f'Set password for user {email}')
|
self._logger.info(f'Set password for user {user_id}')
|
||||||
return True
|
return True
|
||||||
except ApplicationException:
|
except ApplicationException:
|
||||||
raise
|
raise
|
||||||
|
|||||||
42
src/application/commands/user_commands.py
Normal file
42
src/application/commands/user_commands.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from src.application.abstractions import IUnitOfWork
|
||||||
|
from src.application.contracts import ILogger
|
||||||
|
from src.application.domain.entities.organization import PartySearchEntity
|
||||||
|
from src.application.domain.entities.user import UserEntity
|
||||||
|
from src.application.domain.enums.account_type import AccountType
|
||||||
|
from src.infrastructure.database.decorators import transactional
|
||||||
|
|
||||||
|
|
||||||
|
class ListUsersCommand:
|
||||||
|
def __init__(self, unit_of_work: IUnitOfWork, logger: ILogger):
|
||||||
|
self._unit_of_work = unit_of_work
|
||||||
|
self._logger = logger
|
||||||
|
|
||||||
|
@transactional
|
||||||
|
async def __call__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
limit: int = 50,
|
||||||
|
offset: int = 0,
|
||||||
|
search: str | None = None,
|
||||||
|
) -> tuple[list[UserEntity], int]:
|
||||||
|
items = await self._unit_of_work.user_repository.list_all(
|
||||||
|
limit=limit,
|
||||||
|
offset=offset,
|
||||||
|
search=search,
|
||||||
|
)
|
||||||
|
total = await self._unit_of_work.user_repository.count_all(search=search)
|
||||||
|
return items, total
|
||||||
|
|
||||||
|
|
||||||
|
class GetUserCommand:
|
||||||
|
def __init__(self, unit_of_work: IUnitOfWork, logger: ILogger):
|
||||||
|
self._unit_of_work = unit_of_work
|
||||||
|
self._logger = logger
|
||||||
|
|
||||||
|
@transactional
|
||||||
|
async def __call__(self, user_id: str) -> UserEntity:
|
||||||
|
return await self._unit_of_work.user_repository.get_by_id(user_id)
|
||||||
63
src/application/commands/user_wallet_commands.py
Normal file
63
src/application/commands/user_wallet_commands.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from src.application.abstractions import IUnitOfWork
|
||||||
|
from src.application.contracts import ILogger
|
||||||
|
from src.application.domain.entities.user import UserEntity, WalletEntity
|
||||||
|
from src.application.domain.exceptions import ApplicationException
|
||||||
|
from src.infrastructure.crypto.wallet_crypto import (
|
||||||
|
DerivedSecretKey,
|
||||||
|
decrypt_mnemonic,
|
||||||
|
derive_all_private_keys,
|
||||||
|
is_crypto_ready,
|
||||||
|
)
|
||||||
|
from src.infrastructure.database.decorators import transactional
|
||||||
|
|
||||||
|
|
||||||
|
class ListUserWalletsCommand:
|
||||||
|
def __init__(self, unit_of_work: IUnitOfWork, logger: ILogger):
|
||||||
|
self._unit_of_work = unit_of_work
|
||||||
|
self._logger = logger
|
||||||
|
|
||||||
|
@transactional
|
||||||
|
async def __call__(self, user_id: str) -> list[WalletEntity]:
|
||||||
|
await self._unit_of_work.user_repository.get_by_id(user_id)
|
||||||
|
return await self._unit_of_work.wallet_repository.list_by_user(user_id)
|
||||||
|
|
||||||
|
|
||||||
|
class GetUserMnemonicCommand:
|
||||||
|
def __init__(self, unit_of_work: IUnitOfWork, logger: ILogger):
|
||||||
|
self._unit_of_work = unit_of_work
|
||||||
|
self._logger = logger
|
||||||
|
|
||||||
|
@transactional
|
||||||
|
async def __call__(self, user_id: str) -> str:
|
||||||
|
user = await self._require_wallets_user(user_id)
|
||||||
|
if not is_crypto_ready():
|
||||||
|
raise ApplicationException(status_code=503, message='Crypto service not ready')
|
||||||
|
return decrypt_mnemonic(user.encrypted_mnemonic or '')
|
||||||
|
|
||||||
|
async def _require_wallets_user(self, user_id: str) -> UserEntity:
|
||||||
|
user = await self._unit_of_work.user_repository.get_by_id(user_id)
|
||||||
|
if not user.encrypted_mnemonic:
|
||||||
|
raise ApplicationException(status_code=404, message='Wallets not created for user')
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
class GetUserSecretKeysCommand:
|
||||||
|
def __init__(self, unit_of_work: IUnitOfWork, logger: ILogger):
|
||||||
|
self._unit_of_work = unit_of_work
|
||||||
|
self._logger = logger
|
||||||
|
|
||||||
|
@transactional
|
||||||
|
async def __call__(self, user_id: str) -> list[DerivedSecretKey]:
|
||||||
|
user = await self._require_wallets_user(user_id)
|
||||||
|
if not is_crypto_ready():
|
||||||
|
raise ApplicationException(status_code=503, message='Crypto service not ready')
|
||||||
|
mnemonic = decrypt_mnemonic(user.encrypted_mnemonic or '')
|
||||||
|
return derive_all_private_keys(mnemonic)
|
||||||
|
|
||||||
|
async def _require_wallets_user(self, user_id: str) -> UserEntity:
|
||||||
|
user = await self._unit_of_work.user_repository.get_by_id(user_id)
|
||||||
|
if not user.encrypted_mnemonic:
|
||||||
|
raise ApplicationException(status_code=404, message='Wallets not created for user')
|
||||||
|
return user
|
||||||
@@ -32,3 +32,12 @@ class UserEntity:
|
|||||||
account_type: str | None = None
|
account_type: str | None = None
|
||||||
provisioned_by: str | None = None
|
provisioned_by: str | None = None
|
||||||
provisioned_at: datetime | None = None
|
provisioned_at: datetime | None = None
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class WalletEntity:
|
||||||
|
id: str
|
||||||
|
user_id: str
|
||||||
|
chain: str
|
||||||
|
address: str
|
||||||
|
derivation_path: str
|
||||||
|
created_at: datetime | None = None
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from src.infrastructure.database.models.admin_session import AdminSessionModel
|
|||||||
from src.infrastructure.database.models.legal_entity import LegalEntityModel
|
from src.infrastructure.database.models.legal_entity import LegalEntityModel
|
||||||
from src.infrastructure.database.models.organization_wallet import OrganizationWalletModel
|
from src.infrastructure.database.models.organization_wallet import OrganizationWalletModel
|
||||||
from src.infrastructure.database.models.purchase_request import PurchaseRequestModel
|
from src.infrastructure.database.models.purchase_request import PurchaseRequestModel
|
||||||
|
from src.infrastructure.database.models.wallets import WalletModel
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'Base',
|
'Base',
|
||||||
@@ -14,4 +15,5 @@ __all__ = [
|
|||||||
'LegalEntityModel',
|
'LegalEntityModel',
|
||||||
'OrganizationWalletModel',
|
'OrganizationWalletModel',
|
||||||
'PurchaseRequestModel',
|
'PurchaseRequestModel',
|
||||||
|
'WalletModel',
|
||||||
]
|
]
|
||||||
|
|||||||
28
src/infrastructure/database/models/wallets.py
Normal file
28
src/infrastructure/database/models/wallets.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from sqlalchemy import DateTime, ForeignKey, String, func
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
|
from src.infrastructure.database.models.base import Base
|
||||||
|
from src.infrastructure.database.models.mixins import UlidPrimaryKeyMixin
|
||||||
|
|
||||||
|
|
||||||
|
class WalletModel(Base, UlidPrimaryKeyMixin):
|
||||||
|
__tablename__ = 'wallets'
|
||||||
|
|
||||||
|
user_id: Mapped[str] = mapped_column(
|
||||||
|
String(26),
|
||||||
|
ForeignKey('users.id', ondelete='RESTRICT'),
|
||||||
|
nullable=False,
|
||||||
|
index=True,
|
||||||
|
)
|
||||||
|
chain: Mapped[str] = mapped_column(String(16), nullable=False)
|
||||||
|
address: Mapped[str] = mapped_column(String(128), nullable=False, index=True)
|
||||||
|
derivation_path: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||||
|
created_at: Mapped[datetime] = mapped_column(
|
||||||
|
DateTime(timezone=True),
|
||||||
|
nullable=False,
|
||||||
|
server_default=func.now(),
|
||||||
|
)
|
||||||
@@ -4,6 +4,7 @@ from src.infrastructure.database.repositories.user_repository import UserReposit
|
|||||||
from src.infrastructure.database.repositories.legal_entity_repository import LegalEntityRepository
|
from src.infrastructure.database.repositories.legal_entity_repository import LegalEntityRepository
|
||||||
from src.infrastructure.database.repositories.organization_wallet_repository import OrganizationWalletRepository
|
from src.infrastructure.database.repositories.organization_wallet_repository import OrganizationWalletRepository
|
||||||
from src.infrastructure.database.repositories.purchase_request_repository import PurchaseRequestRepository
|
from src.infrastructure.database.repositories.purchase_request_repository import PurchaseRequestRepository
|
||||||
|
from src.infrastructure.database.repositories.wallets_repository import WalletRepository
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AdminUserRepository',
|
'AdminUserRepository',
|
||||||
@@ -12,4 +13,5 @@ __all__ = [
|
|||||||
'LegalEntityRepository',
|
'LegalEntityRepository',
|
||||||
'OrganizationWalletRepository',
|
'OrganizationWalletRepository',
|
||||||
'PurchaseRequestRepository',
|
'PurchaseRequestRepository',
|
||||||
|
'WalletRepository',
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -45,6 +45,16 @@ class UserRepository(IUserRepository):
|
|||||||
provisioned_by=user.provisioned_by,
|
provisioned_by=user.provisioned_by,
|
||||||
provisioned_at=user.provisioned_at,
|
provisioned_at=user.provisioned_at,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def get_by_id(self, user_id: str) -> UserEntity:
|
||||||
|
try:
|
||||||
|
user = await self._get_user(user_id)
|
||||||
|
return self._to_entity(user)
|
||||||
|
except ApplicationException:
|
||||||
|
raise
|
||||||
|
except SQLAlchemyError as exception:
|
||||||
|
self._logger.exception(str(exception))
|
||||||
|
raise InternalServerException(message=f'Database error: {str(exception)}')
|
||||||
|
|
||||||
async def create_legal_entity_user(
|
async def create_legal_entity_user(
|
||||||
self,
|
self,
|
||||||
@@ -104,9 +114,8 @@ class UserRepository(IUserRepository):
|
|||||||
self._logger.exception(str(exception))
|
self._logger.exception(str(exception))
|
||||||
raise InternalServerException(message=f'Database error: {str(exception)}')
|
raise InternalServerException(message=f'Database error: {str(exception)}')
|
||||||
|
|
||||||
async def set_password(self, user_email: str, password_hash: str) -> UserEntity:
|
async def set_password(self, user_id: str, password_hash: str) -> UserEntity:
|
||||||
user = await self.get_user_by_email(user_email)
|
return await self._update_field(user_id, password_hash=password_hash)
|
||||||
return await self._update_field(user.id, password_hash=password_hash)
|
|
||||||
|
|
||||||
async def get_user_by_email(self, email: str) -> UserEntity:
|
async def get_user_by_email(self, email: str) -> UserEntity:
|
||||||
try:
|
try:
|
||||||
@@ -126,6 +135,41 @@ class UserRepository(IUserRepository):
|
|||||||
stmt = select(UserModel.id).where(UserModel.email == email, UserModel.is_deleted.is_(False)).limit(1)
|
stmt = select(UserModel.id).where(UserModel.email == email, UserModel.is_deleted.is_(False)).limit(1)
|
||||||
result = await self._session.execute(stmt)
|
result = await self._session.execute(stmt)
|
||||||
return result.scalar_one_or_none() is not None
|
return result.scalar_one_or_none() is not None
|
||||||
|
|
||||||
|
def _search_filter(self, search: str | None):
|
||||||
|
if not search or not search.strip():
|
||||||
|
return None
|
||||||
|
|
||||||
|
pattern = f'%{search.strip()}%'
|
||||||
|
return or_(
|
||||||
|
UserModel.id.ilike(pattern),
|
||||||
|
UserModel.email.ilike(pattern),
|
||||||
|
UserModel.last_name.ilike(pattern),
|
||||||
|
UserModel.first_name.ilike(pattern),
|
||||||
|
UserModel.middle_name.ilike(pattern),
|
||||||
|
UserModel.phone.ilike(pattern),
|
||||||
|
UserModel.inn.ilike(pattern),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def list_all(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
limit: int,
|
||||||
|
offset: int,
|
||||||
|
search: str | None = None,
|
||||||
|
) -> list[UserEntity]:
|
||||||
|
stmt = (
|
||||||
|
select(UserModel)
|
||||||
|
.where(UserModel.is_deleted.is_(False))
|
||||||
|
.order_by(UserModel.created_at.desc())
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset)
|
||||||
|
)
|
||||||
|
search_filter = self._search_filter(search)
|
||||||
|
if search_filter is not None:
|
||||||
|
stmt = stmt.where(search_filter)
|
||||||
|
res = await self._session.execute(stmt)
|
||||||
|
return [self._to_entity(m) for m in res.scalars().all()]
|
||||||
|
|
||||||
def _individual_search_filter(self, query: str):
|
def _individual_search_filter(self, query: str):
|
||||||
pattern = f'%{query.strip()}%'
|
pattern = f'%{query.strip()}%'
|
||||||
@@ -192,3 +236,14 @@ class UserRepository(IUserRepository):
|
|||||||
)
|
)
|
||||||
result = await self._session.execute(stmt)
|
result = await self._session.execute(stmt)
|
||||||
return int(result.scalar_one())
|
return int(result.scalar_one())
|
||||||
|
|
||||||
|
async def count_all(self, *, search: str | None = None) -> int:
|
||||||
|
stmt = (
|
||||||
|
select(func.count(UserModel.id))
|
||||||
|
.where(UserModel.is_deleted.is_(False))
|
||||||
|
)
|
||||||
|
search_filter = self._search_filter(search)
|
||||||
|
if search_filter is not None:
|
||||||
|
stmt = stmt.where(search_filter)
|
||||||
|
res = await self._session.execute(stmt)
|
||||||
|
return int(res.scalar_one())
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
|
||||||
|
|
||||||
|
from src.application.abstractions.repositories import IWalletRepository
|
||||||
|
from src.application.contracts import ILogger
|
||||||
|
from src.application.domain.entities.user import WalletEntity
|
||||||
|
from src.application.domain.exceptions import ApplicationException
|
||||||
|
from src.infrastructure.database.models import WalletModel
|
||||||
|
|
||||||
|
|
||||||
|
class WalletRepository(IWalletRepository):
|
||||||
|
def __init__(self, session: AsyncSession, logger: ILogger):
|
||||||
|
self._session = session
|
||||||
|
self._logger = logger
|
||||||
|
|
||||||
|
def _to_entity(self, m: WalletModel) -> WalletEntity:
|
||||||
|
return WalletEntity(
|
||||||
|
id=m.id,
|
||||||
|
user_id=m.user_id,
|
||||||
|
chain=m.chain,
|
||||||
|
address=m.address,
|
||||||
|
derivation_path=m.derivation_path,
|
||||||
|
created_at=m.created_at,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def list_by_user(self, user_id: str) -> list[WalletEntity]:
|
||||||
|
res = await self._session.execute(
|
||||||
|
select(WalletModel)
|
||||||
|
.where(WalletModel.user_id == user_id)
|
||||||
|
.order_by(WalletModel.chain)
|
||||||
|
)
|
||||||
|
return [self._to_entity(m) for m in res.scalars().all()]
|
||||||
|
|
||||||
|
async def exists_for_user(self, user_id: str) -> bool:
|
||||||
|
res = await self._session.execute(
|
||||||
|
select(WalletModel.id)
|
||||||
|
.where(WalletModel.user_id == user_id)
|
||||||
|
.limit(1)
|
||||||
|
)
|
||||||
|
return res.scalar_one_or_none() is not None
|
||||||
@@ -8,6 +8,7 @@ from src.application.abstractions.repositories import (
|
|||||||
IOrganizationWalletRepository,
|
IOrganizationWalletRepository,
|
||||||
IPurchaseRequestRepository,
|
IPurchaseRequestRepository,
|
||||||
IUserRepository,
|
IUserRepository,
|
||||||
|
IWalletRepository,
|
||||||
)
|
)
|
||||||
from src.application.contracts import ILogger
|
from src.application.contracts import ILogger
|
||||||
from src.application.domain.exceptions import RefreshConcurrentException
|
from src.application.domain.exceptions import RefreshConcurrentException
|
||||||
@@ -18,6 +19,7 @@ from src.infrastructure.database.repositories import (
|
|||||||
OrganizationWalletRepository,
|
OrganizationWalletRepository,
|
||||||
PurchaseRequestRepository,
|
PurchaseRequestRepository,
|
||||||
UserRepository,
|
UserRepository,
|
||||||
|
WalletRepository,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -31,6 +33,7 @@ class UnitOfWork(IUnitOfWork):
|
|||||||
self._legal_entity_repository: ILegalEntityRepository | None = None
|
self._legal_entity_repository: ILegalEntityRepository | None = None
|
||||||
self._organization_wallet_repository: IOrganizationWalletRepository | None = None
|
self._organization_wallet_repository: IOrganizationWalletRepository | None = None
|
||||||
self._purchase_request_repository: IPurchaseRequestRepository | None = None
|
self._purchase_request_repository: IPurchaseRequestRepository | None = None
|
||||||
|
self._wallet_repository: IWalletRepository | None = None
|
||||||
self._logger: ILogger = logger
|
self._logger: ILogger = logger
|
||||||
|
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
@@ -84,6 +87,14 @@ class UnitOfWork(IUnitOfWork):
|
|||||||
session=self._session, logger=self._logger
|
session=self._session, logger=self._logger
|
||||||
)
|
)
|
||||||
return self._organization_wallet_repository
|
return self._organization_wallet_repository
|
||||||
|
|
||||||
|
@property
|
||||||
|
def wallet_repository(self) -> IWalletRepository:
|
||||||
|
if self._wallet_repository is None:
|
||||||
|
self._wallet_repository = WalletRepository(
|
||||||
|
session=self._session, logger=self._logger
|
||||||
|
)
|
||||||
|
return self._wallet_repository
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def purchase_request_repository(self) -> IPurchaseRequestRepository:
|
def purchase_request_repository(self) -> IPurchaseRequestRepository:
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ from src.application.commands import (
|
|||||||
UpdatePurchaseRequestStatusCommand,
|
UpdatePurchaseRequestStatusCommand,
|
||||||
PutOrganizationDocumentCommand,
|
PutOrganizationDocumentCommand,
|
||||||
SetPasswordCommand,
|
SetPasswordCommand,
|
||||||
|
ListUsersCommand,
|
||||||
|
GetUserCommand,
|
||||||
|
ListUserWalletsCommand,
|
||||||
|
GetUserMnemonicCommand,
|
||||||
|
GetUserSecretKeysCommand,
|
||||||
)
|
)
|
||||||
from src.application.contracts import IHashService, IJwtService, ILogger
|
from src.application.contracts import IHashService, IJwtService, ILogger
|
||||||
from src.infrastructure.config import settings
|
from src.infrastructure.config import settings
|
||||||
@@ -215,3 +220,33 @@ def get_set_password_command(
|
|||||||
logger: ILogger = Depends(get_logger),
|
logger: ILogger = Depends(get_logger),
|
||||||
) -> SetPasswordCommand:
|
) -> SetPasswordCommand:
|
||||||
return SetPasswordCommand(uow, hash_service, logger)
|
return SetPasswordCommand(uow, hash_service, logger)
|
||||||
|
|
||||||
|
def get_list_users_command(
|
||||||
|
uow: IUnitOfWork = Depends(get_unit_of_work),
|
||||||
|
logger: ILogger = Depends(get_logger),
|
||||||
|
) -> ListUsersCommand:
|
||||||
|
return ListUsersCommand(uow, logger)
|
||||||
|
|
||||||
|
def get_get_user_command(
|
||||||
|
uow: IUnitOfWork = Depends(get_unit_of_work),
|
||||||
|
logger: ILogger = Depends(get_logger),
|
||||||
|
) -> GetUserCommand:
|
||||||
|
return GetUserCommand(uow, logger)
|
||||||
|
|
||||||
|
def get_list_user_wallets_command(
|
||||||
|
uow: IUnitOfWork = Depends(get_unit_of_work),
|
||||||
|
logger: ILogger = Depends(get_logger),
|
||||||
|
) -> ListUserWalletsCommand:
|
||||||
|
return ListUserWalletsCommand(uow, logger)
|
||||||
|
|
||||||
|
def get_get_user_mnemonic_command(
|
||||||
|
uow: IUnitOfWork = Depends(get_unit_of_work),
|
||||||
|
logger: ILogger = Depends(get_logger),
|
||||||
|
) -> GetUserMnemonicCommand:
|
||||||
|
return GetUserMnemonicCommand(uow, logger)
|
||||||
|
|
||||||
|
def get_get_user_secret_keys_command(
|
||||||
|
uow: IUnitOfWork = Depends(get_unit_of_work),
|
||||||
|
logger: ILogger = Depends(get_logger),
|
||||||
|
) -> GetUserSecretKeysCommand:
|
||||||
|
return GetUserSecretKeysCommand(uow, logger)
|
||||||
@@ -34,13 +34,19 @@ from src.presentation.schemas.mappers import (
|
|||||||
)
|
)
|
||||||
from src.presentation.schemas.organization import (
|
from src.presentation.schemas.organization import (
|
||||||
CreateOrganizationRequest,
|
CreateOrganizationRequest,
|
||||||
|
OrganizationListResponse,
|
||||||
|
DocumentResponse,
|
||||||
|
OrganizationResponse,
|
||||||
|
UpdateOrganizationRequest,
|
||||||
|
)
|
||||||
|
from src.presentation.schemas.entity import (
|
||||||
CreateWalletsResponse,
|
CreateWalletsResponse,
|
||||||
MnemonicResponse,
|
MnemonicResponse,
|
||||||
OrganizationListResponse,
|
PartySearchResponse,
|
||||||
OrganizationResponse,
|
|
||||||
PartySearchListResponse,
|
PartySearchListResponse,
|
||||||
|
PurchaseRequestResponse,
|
||||||
|
PurchaseRequestListResponse,
|
||||||
SecretKeyResponse,
|
SecretKeyResponse,
|
||||||
UpdateOrganizationRequest,
|
|
||||||
WalletResponse,
|
WalletResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from src.presentation.dependencies.commands import (
|
|||||||
get_update_purchase_request_status_command,
|
get_update_purchase_request_status_command,
|
||||||
)
|
)
|
||||||
from src.presentation.schemas.mappers import purchase_request_to_response
|
from src.presentation.schemas.mappers import purchase_request_to_response
|
||||||
from src.presentation.schemas.organization import (
|
from src.presentation.schemas.entity import (
|
||||||
CreatePurchaseRequestBody,
|
CreatePurchaseRequestBody,
|
||||||
PurchaseRequestListResponse,
|
PurchaseRequestListResponse,
|
||||||
PurchaseRequestResponse,
|
PurchaseRequestResponse,
|
||||||
|
|||||||
@@ -1,17 +1,116 @@
|
|||||||
from fastapi import APIRouter, Depends, Request
|
from fastapi import APIRouter, Depends, Request, Query, status
|
||||||
from fastapi.responses import ORJSONResponse
|
from fastapi.responses import ORJSONResponse
|
||||||
from starlette import status
|
from starlette import status
|
||||||
from src.presentation.dependencies.commands import get_set_password_command
|
|
||||||
|
from src.application.domain.dto import AdminAuthContext
|
||||||
|
from src.presentation.decorators.admin_auth import require_admin_access, require_admin_role
|
||||||
|
from src.presentation.dependencies.commands import (
|
||||||
|
get_set_password_command,
|
||||||
|
get_list_users_command,
|
||||||
|
get_get_user_command,
|
||||||
|
get_list_user_wallets_command,
|
||||||
|
get_get_user_mnemonic_command,
|
||||||
|
get_get_user_secret_keys_command,
|
||||||
|
)
|
||||||
|
from src.presentation.schemas.mappers import (
|
||||||
|
mnemonic_to_response,
|
||||||
|
user_to_response,
|
||||||
|
party_search_to_response,
|
||||||
|
secret_key_to_response,
|
||||||
|
wallet_to_response,
|
||||||
|
)
|
||||||
|
from src.presentation.schemas.user import (
|
||||||
|
UserResponse,
|
||||||
|
UserListResponse,
|
||||||
|
)
|
||||||
|
from src.presentation.schemas.entity import (
|
||||||
|
PartySearchResponse,
|
||||||
|
MnemonicResponse,
|
||||||
|
PartySearchListResponse,
|
||||||
|
SecretKeyResponse,
|
||||||
|
WalletResponse,
|
||||||
|
)
|
||||||
from src.application.commands.set_password import SetPasswordCommand
|
from src.application.commands.set_password import SetPasswordCommand
|
||||||
|
from src.application.commands.user_commands import (
|
||||||
|
ListUsersCommand,
|
||||||
|
GetUserCommand,
|
||||||
|
)
|
||||||
|
from src.application.commands.user_wallet_commands import (
|
||||||
|
ListUserWalletsCommand,
|
||||||
|
GetUserMnemonicCommand,
|
||||||
|
GetUserSecretKeysCommand
|
||||||
|
)
|
||||||
from src.presentation.schemas.password import SetPasswordRequest
|
from src.presentation.schemas.password import SetPasswordRequest
|
||||||
|
|
||||||
users_router = APIRouter(prefix='/users', tags=['users'])
|
users_router = APIRouter(prefix='/users', tags=['users'])
|
||||||
|
|
||||||
@users_router.patch(path='/password', response_class=ORJSONResponse, status_code=status.HTTP_200_OK)
|
@users_router.get('', response_model=UserListResponse)
|
||||||
|
async def list_users(
|
||||||
|
limit: int = Query(default=50, ge=1, le=200),
|
||||||
|
offset: int = Query(default=0, ge=0),
|
||||||
|
q: str | None = Query(default=None, min_length=1, max_length=255),
|
||||||
|
auth: AdminAuthContext = Depends(require_admin_access),
|
||||||
|
command: ListUsersCommand = Depends(get_list_users_command),
|
||||||
|
):
|
||||||
|
items, total = await command(limit=limit, offset=offset, search=q)
|
||||||
|
return UserListResponse(
|
||||||
|
items=[user_to_response(x) for x in items],
|
||||||
|
total=total,
|
||||||
|
)
|
||||||
|
|
||||||
|
@users_router.get('/{user_id}', response_model=UserResponse)
|
||||||
|
async def get_user(
|
||||||
|
user_id: str,
|
||||||
|
auth: AdminAuthContext = Depends(require_admin_access),
|
||||||
|
command: GetUserCommand = Depends(get_get_user_command),
|
||||||
|
):
|
||||||
|
user = await command(user_id)
|
||||||
|
return user_to_response(user)
|
||||||
|
|
||||||
|
@users_router.get('/{user_id}/wallets', response_model=list[WalletResponse])
|
||||||
|
async def list_user_wallets(
|
||||||
|
user_id: str,
|
||||||
|
auth: AdminAuthContext = Depends(require_admin_access),
|
||||||
|
command: ListUserWalletsCommand = Depends(get_list_user_wallets_command),
|
||||||
|
):
|
||||||
|
wallets = await command(user_id)
|
||||||
|
return [wallet_to_response(w) for w in wallets]
|
||||||
|
|
||||||
|
|
||||||
|
@users_router.get('/{user_id}/wallets/mnemonic', response_model=MnemonicResponse)
|
||||||
|
async def get_user_mnemonic(
|
||||||
|
user_id: str,
|
||||||
|
auth: AdminAuthContext = Depends(require_admin_role('compliance', 'superadmin')),
|
||||||
|
command: GetUserMnemonicCommand = Depends(get_get_user_mnemonic_command),
|
||||||
|
):
|
||||||
|
mnemonic = await command(user_id)
|
||||||
|
return mnemonic_to_response(mnemonic)
|
||||||
|
|
||||||
|
|
||||||
|
@users_router.get('/{user_id}/wallets/secret-keys', response_model=list[SecretKeyResponse])
|
||||||
|
async def get_user_secret_keys(
|
||||||
|
user_id: str,
|
||||||
|
auth: AdminAuthContext = Depends(require_admin_role('compliance', 'superadmin')),
|
||||||
|
command: GetUserSecretKeysCommand = Depends(get_get_user_secret_keys_command),
|
||||||
|
):
|
||||||
|
keys = await command(user_id)
|
||||||
|
return [
|
||||||
|
secret_key_to_response(
|
||||||
|
chain=k.chain,
|
||||||
|
address=k.address,
|
||||||
|
derivation_path=k.derivation_path,
|
||||||
|
private_key=k.private_key,
|
||||||
|
)
|
||||||
|
for k in keys
|
||||||
|
]
|
||||||
|
|
||||||
|
@users_router.patch(path='/{user_id}/set_password', response_class=ORJSONResponse, status_code=status.HTTP_200_OK)
|
||||||
async def set_password(
|
async def set_password(
|
||||||
request: Request,
|
user_id: str,
|
||||||
|
request: Request,
|
||||||
body: SetPasswordRequest,
|
body: SetPasswordRequest,
|
||||||
|
auth: AdminAuthContext = Depends(require_admin_role('compliance', 'superadmin')),
|
||||||
command: SetPasswordCommand = Depends(get_set_password_command),
|
command: SetPasswordCommand = Depends(get_set_password_command),
|
||||||
):
|
):
|
||||||
await command(email=body.email, password=body.password)
|
await command(user_id=user_id, password=body.password)
|
||||||
return ORJSONResponse(content={'message': 'Password updated successfully'})
|
return ORJSONResponse(content={'message': 'Password updated successfully'})
|
||||||
115
src/presentation/schemas/entity.py
Normal file
115
src/presentation/schemas/entity.py
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class PartySearchResponse(BaseModel):
|
||||||
|
id: str
|
||||||
|
account_type: str
|
||||||
|
user_id: str
|
||||||
|
email: str | None
|
||||||
|
name: str | None
|
||||||
|
inn: str | None
|
||||||
|
phone: str | None
|
||||||
|
status: str | None
|
||||||
|
kyc_verified: bool | None
|
||||||
|
created_at: str | None
|
||||||
|
|
||||||
|
|
||||||
|
class PartySearchListResponse(BaseModel):
|
||||||
|
items: list[PartySearchResponse]
|
||||||
|
total: int
|
||||||
|
|
||||||
|
|
||||||
|
class WalletResponse(BaseModel):
|
||||||
|
id: str
|
||||||
|
chain: str
|
||||||
|
address: str
|
||||||
|
derivation_path: str
|
||||||
|
created_at: str | None
|
||||||
|
|
||||||
|
|
||||||
|
class CreateWalletsResponse(BaseModel):
|
||||||
|
wallets: list[WalletResponse]
|
||||||
|
mnemonic: str
|
||||||
|
|
||||||
|
|
||||||
|
class MnemonicResponse(BaseModel):
|
||||||
|
mnemonic: str
|
||||||
|
|
||||||
|
|
||||||
|
class SecretKeyResponse(BaseModel):
|
||||||
|
chain: str
|
||||||
|
address: str
|
||||||
|
derivation_path: str
|
||||||
|
private_key: str
|
||||||
|
|
||||||
|
|
||||||
|
class CreatePurchaseRequestBody(BaseModel):
|
||||||
|
organization_id: str
|
||||||
|
usdt_amount: Decimal = Field(gt=0)
|
||||||
|
status: str = 'submitted'
|
||||||
|
rub_amount: Decimal | None = Field(default=None, gt=0)
|
||||||
|
exchange_rate: Decimal | None = Field(default=None, gt=0)
|
||||||
|
service_fee_percent: Decimal | None = Field(default=None, ge=0)
|
||||||
|
comment: str | None = None
|
||||||
|
admin_comment: str | None = None
|
||||||
|
target_wallet_chain: str | None = Field(default='ETH', max_length=16)
|
||||||
|
target_wallet_address: str | None = Field(default=None, max_length=128)
|
||||||
|
tx_hash: str | None = Field(default=None, max_length=128)
|
||||||
|
assigned_to: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class UpdatePurchaseRequestStatusBody(BaseModel):
|
||||||
|
status: str
|
||||||
|
admin_comment: str | None = None
|
||||||
|
assigned_to: str | None = None
|
||||||
|
tx_hash: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class UpdatePurchaseRequestBody(BaseModel):
|
||||||
|
status: str | None = None
|
||||||
|
usdt_amount: Decimal | None = Field(default=None, gt=0)
|
||||||
|
rub_amount: Decimal | None = Field(default=None, gt=0)
|
||||||
|
exchange_rate: Decimal | None = Field(default=None, gt=0)
|
||||||
|
service_fee_percent: Decimal | None = Field(default=None, ge=0)
|
||||||
|
comment: str | None = None
|
||||||
|
admin_comment: str | None = None
|
||||||
|
target_wallet_chain: str | None = Field(default=None, max_length=16)
|
||||||
|
target_wallet_address: str | None = Field(default=None, max_length=128)
|
||||||
|
tx_hash: str | None = Field(default=None, max_length=128)
|
||||||
|
assigned_to: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class SetPurchaseRequestQuoteBody(BaseModel):
|
||||||
|
rub_amount: Decimal = Field(gt=0)
|
||||||
|
exchange_rate: Decimal = Field(gt=0)
|
||||||
|
service_fee_percent: Decimal | None = None
|
||||||
|
admin_comment: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class PurchaseRequestResponse(BaseModel):
|
||||||
|
id: str
|
||||||
|
organization_id: str
|
||||||
|
status: str
|
||||||
|
usdt_amount: str
|
||||||
|
rub_amount: str | None
|
||||||
|
exchange_rate: str | None
|
||||||
|
service_fee_percent: str | None
|
||||||
|
comment: str | None
|
||||||
|
admin_comment: str | None
|
||||||
|
target_wallet_chain: str | None
|
||||||
|
target_wallet_address: str | None
|
||||||
|
tx_hash: str | None
|
||||||
|
assigned_to: str | None
|
||||||
|
created_at: str | None
|
||||||
|
updated_at: str | None
|
||||||
|
completed_at: str | None
|
||||||
|
|
||||||
|
|
||||||
|
class PurchaseRequestListResponse(BaseModel):
|
||||||
|
items: list[PurchaseRequestResponse]
|
||||||
|
total: int
|
||||||
@@ -7,11 +7,19 @@ from src.application.domain.entities.organization import (
|
|||||||
PartySearchEntity,
|
PartySearchEntity,
|
||||||
PurchaseRequestEntity,
|
PurchaseRequestEntity,
|
||||||
)
|
)
|
||||||
|
from src.application.domain.entities.user import (
|
||||||
|
UserEntity,
|
||||||
|
)
|
||||||
|
from src.presentation.schemas.user import (
|
||||||
|
UserResponse,
|
||||||
|
)
|
||||||
from src.presentation.schemas.organization import (
|
from src.presentation.schemas.organization import (
|
||||||
CreateWalletsResponse,
|
|
||||||
DocumentResponse,
|
DocumentResponse,
|
||||||
MnemonicResponse,
|
|
||||||
OrganizationResponse,
|
OrganizationResponse,
|
||||||
|
)
|
||||||
|
from src.presentation.schemas.entity import (
|
||||||
|
CreateWalletsResponse,
|
||||||
|
MnemonicResponse,
|
||||||
PartySearchResponse,
|
PartySearchResponse,
|
||||||
PurchaseRequestResponse,
|
PurchaseRequestResponse,
|
||||||
SecretKeyResponse,
|
SecretKeyResponse,
|
||||||
@@ -129,3 +137,23 @@ def purchase_request_to_response(entity: PurchaseRequestEntity) -> PurchaseReque
|
|||||||
updated_at=entity.updated_at.isoformat() if entity.updated_at else None,
|
updated_at=entity.updated_at.isoformat() if entity.updated_at else None,
|
||||||
completed_at=entity.completed_at.isoformat() if entity.completed_at else None,
|
completed_at=entity.completed_at.isoformat() if entity.completed_at else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def user_to_response(entity: UserEntity) -> UserResponse:
|
||||||
|
return UserResponse(
|
||||||
|
id=entity.id,
|
||||||
|
email=entity.email,
|
||||||
|
first_name=entity.first_name,
|
||||||
|
middle_name=entity.middle_name,
|
||||||
|
last_name=entity.last_name,
|
||||||
|
birth_date=entity.birth_date,
|
||||||
|
phone=entity.phone,
|
||||||
|
passport_data=entity.passport_data,
|
||||||
|
inn=entity.inn,
|
||||||
|
avatar_link=entity.avatar_link,
|
||||||
|
account_type=entity.account_type,
|
||||||
|
kyc_verified=entity.kyc_verified,
|
||||||
|
is_deleted=entity.is_deleted,
|
||||||
|
created_at=entity.created_at.isoformat() if entity.created_at else None,
|
||||||
|
updated_at=entity.updated_at.isoformat() if entity.updated_at else None,
|
||||||
|
kyc_verified_at=entity.kyc_verified_at.isoformat() if entity.kyc_verified_at else None,
|
||||||
|
)
|
||||||
@@ -62,48 +62,6 @@ class OrganizationListResponse(BaseModel):
|
|||||||
total: int
|
total: int
|
||||||
|
|
||||||
|
|
||||||
class PartySearchResponse(BaseModel):
|
|
||||||
id: str
|
|
||||||
account_type: str
|
|
||||||
user_id: str
|
|
||||||
email: str | None
|
|
||||||
name: str | None
|
|
||||||
inn: str | None
|
|
||||||
phone: str | None
|
|
||||||
status: str | None
|
|
||||||
kyc_verified: bool | None
|
|
||||||
created_at: str | None
|
|
||||||
|
|
||||||
|
|
||||||
class PartySearchListResponse(BaseModel):
|
|
||||||
items: list[PartySearchResponse]
|
|
||||||
total: int
|
|
||||||
|
|
||||||
|
|
||||||
class WalletResponse(BaseModel):
|
|
||||||
id: str
|
|
||||||
chain: str
|
|
||||||
address: str
|
|
||||||
derivation_path: str
|
|
||||||
created_at: str | None
|
|
||||||
|
|
||||||
|
|
||||||
class CreateWalletsResponse(BaseModel):
|
|
||||||
wallets: list[WalletResponse]
|
|
||||||
mnemonic: str
|
|
||||||
|
|
||||||
|
|
||||||
class MnemonicResponse(BaseModel):
|
|
||||||
mnemonic: str
|
|
||||||
|
|
||||||
|
|
||||||
class SecretKeyResponse(BaseModel):
|
|
||||||
chain: str
|
|
||||||
address: str
|
|
||||||
derivation_path: str
|
|
||||||
private_key: str
|
|
||||||
|
|
||||||
|
|
||||||
class DocumentResponse(BaseModel):
|
class DocumentResponse(BaseModel):
|
||||||
organization_id: str
|
organization_id: str
|
||||||
document_type: str
|
document_type: str
|
||||||
@@ -112,70 +70,3 @@ class DocumentResponse(BaseModel):
|
|||||||
content_type: str | None = None
|
content_type: str | None = None
|
||||||
file_size_bytes: int | None = None
|
file_size_bytes: int | None = None
|
||||||
download_url: str | None = None
|
download_url: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class CreatePurchaseRequestBody(BaseModel):
|
|
||||||
organization_id: str
|
|
||||||
usdt_amount: Decimal = Field(gt=0)
|
|
||||||
status: str = 'submitted'
|
|
||||||
rub_amount: Decimal | None = Field(default=None, gt=0)
|
|
||||||
exchange_rate: Decimal | None = Field(default=None, gt=0)
|
|
||||||
service_fee_percent: Decimal | None = Field(default=None, ge=0)
|
|
||||||
comment: str | None = None
|
|
||||||
admin_comment: str | None = None
|
|
||||||
target_wallet_chain: str | None = Field(default='ETH', max_length=16)
|
|
||||||
target_wallet_address: str | None = Field(default=None, max_length=128)
|
|
||||||
tx_hash: str | None = Field(default=None, max_length=128)
|
|
||||||
assigned_to: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class UpdatePurchaseRequestStatusBody(BaseModel):
|
|
||||||
status: str
|
|
||||||
admin_comment: str | None = None
|
|
||||||
assigned_to: str | None = None
|
|
||||||
tx_hash: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class UpdatePurchaseRequestBody(BaseModel):
|
|
||||||
status: str | None = None
|
|
||||||
usdt_amount: Decimal | None = Field(default=None, gt=0)
|
|
||||||
rub_amount: Decimal | None = Field(default=None, gt=0)
|
|
||||||
exchange_rate: Decimal | None = Field(default=None, gt=0)
|
|
||||||
service_fee_percent: Decimal | None = Field(default=None, ge=0)
|
|
||||||
comment: str | None = None
|
|
||||||
admin_comment: str | None = None
|
|
||||||
target_wallet_chain: str | None = Field(default=None, max_length=16)
|
|
||||||
target_wallet_address: str | None = Field(default=None, max_length=128)
|
|
||||||
tx_hash: str | None = Field(default=None, max_length=128)
|
|
||||||
assigned_to: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class SetPurchaseRequestQuoteBody(BaseModel):
|
|
||||||
rub_amount: Decimal = Field(gt=0)
|
|
||||||
exchange_rate: Decimal = Field(gt=0)
|
|
||||||
service_fee_percent: Decimal | None = None
|
|
||||||
admin_comment: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
class PurchaseRequestResponse(BaseModel):
|
|
||||||
id: str
|
|
||||||
organization_id: str
|
|
||||||
status: str
|
|
||||||
usdt_amount: str
|
|
||||||
rub_amount: str | None
|
|
||||||
exchange_rate: str | None
|
|
||||||
service_fee_percent: str | None
|
|
||||||
comment: str | None
|
|
||||||
admin_comment: str | None
|
|
||||||
target_wallet_chain: str | None
|
|
||||||
target_wallet_address: str | None
|
|
||||||
tx_hash: str | None
|
|
||||||
assigned_to: str | None
|
|
||||||
created_at: str | None
|
|
||||||
updated_at: str | None
|
|
||||||
completed_at: str | None
|
|
||||||
|
|
||||||
|
|
||||||
class PurchaseRequestListResponse(BaseModel):
|
|
||||||
items: list[PurchaseRequestResponse]
|
|
||||||
total: int
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from src.application.domain.password_policy import validate_password_strength
|
|||||||
|
|
||||||
|
|
||||||
class SetPasswordRequest(BaseModel):
|
class SetPasswordRequest(BaseModel):
|
||||||
email: str
|
|
||||||
password: str
|
password: str
|
||||||
|
|
||||||
@field_validator('password')
|
@field_validator('password')
|
||||||
|
|||||||
32
src/presentation/schemas/user.py
Normal file
32
src/presentation/schemas/user.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from datetime import date, datetime
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class UserResponse(BaseModel):
|
||||||
|
id: str | None = Field(None, description='Идентификатор пользователя')
|
||||||
|
email: str | None = Field(None, description='Email')
|
||||||
|
first_name: str | None = Field(None, description='Имя')
|
||||||
|
middle_name: str | None = Field(None, description='Отчество')
|
||||||
|
last_name: str | None = Field(None, description='Фамилия')
|
||||||
|
birth_date: date | None = Field(None, description='Дата рождения')
|
||||||
|
phone: str | None = Field(None, description='Телефон')
|
||||||
|
passport_data: str | None = Field(None, description='Паспортные данные')
|
||||||
|
inn: str | None = Field(None, description='ИНН')
|
||||||
|
avatar_link: str | None = Field(None, description='HTTPS-ссылка на текущий аватар в хранилище')
|
||||||
|
account_type: str | None = Field(None, description='Тип аккаунта')
|
||||||
|
kyc_verified: bool | None = Field(None, description='Признак пройденного KYC')
|
||||||
|
is_deleted: bool | None = Field(None, description='Удалён ли аккаунт')
|
||||||
|
created_at: datetime | None = Field(None, description='Время создания записи')
|
||||||
|
updated_at: datetime | None = Field(None, description='Время последнего обновления')
|
||||||
|
kyc_verified_at: datetime | None = Field(None, description='Время подтверждения KYC')
|
||||||
|
|
||||||
|
|
||||||
|
class UserListResponse(BaseModel):
|
||||||
|
items: list[UserResponse]
|
||||||
|
total: int
|
||||||
Reference in New Issue
Block a user