Compare commits
9 Commits
3432d95927
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67b65cbdaf | ||
| 73b5922c97 | |||
| bf8d778a9f | |||
| e85889eff8 | |||
| 37881a9da1 | |||
| d3f961f1d5 | |||
| 61a9e47f4b | |||
| 3c36ab2de0 | |||
| 5a0f1c8fd9 |
@@ -10,3 +10,6 @@ from src.application.commands.purchase_request_commands import (
|
|||||||
GetPurchaseRequestCommand,
|
GetPurchaseRequestCommand,
|
||||||
ListPurchaseRequestsCommand,
|
ListPurchaseRequestsCommand,
|
||||||
)
|
)
|
||||||
|
from src.application.commands.upload_docs_button import (
|
||||||
|
UploadDocsCommand,
|
||||||
|
)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class CreatePurchaseRequestCommand:
|
|||||||
self,
|
self,
|
||||||
user_id: str,
|
user_id: str,
|
||||||
*,
|
*,
|
||||||
usdt_amount: Decimal,
|
rub_amount: Decimal,
|
||||||
comment: str | None = None,
|
comment: str | None = None,
|
||||||
target_wallet_chain: str | None = None,
|
target_wallet_chain: str | None = None,
|
||||||
target_wallet_address: str | None = None,
|
target_wallet_address: str | None = None,
|
||||||
@@ -34,7 +34,7 @@ class CreatePurchaseRequestCommand:
|
|||||||
legal_entity = await require_legal_entity(user_id, self._unit_of_work)
|
legal_entity = await require_legal_entity(user_id, self._unit_of_work)
|
||||||
item = await self._unit_of_work.purchase_request_repository.create(
|
item = await self._unit_of_work.purchase_request_repository.create(
|
||||||
organization_id=legal_entity.id,
|
organization_id=legal_entity.id,
|
||||||
usdt_amount=usdt_amount,
|
rub_amount=rub_amount,
|
||||||
comment=comment,
|
comment=comment,
|
||||||
target_wallet_chain=target_wallet_chain,
|
target_wallet_chain=target_wallet_chain,
|
||||||
target_wallet_address=target_wallet_address,
|
target_wallet_address=target_wallet_address,
|
||||||
@@ -52,14 +52,14 @@ class CreatePurchaseRequestCommand:
|
|||||||
}
|
}
|
||||||
payload = {
|
payload = {
|
||||||
'id_client': legal_entity.id,
|
'id_client': legal_entity.id,
|
||||||
'usdt_amount': usdt_amount,
|
'rub_amount': rub_amount,
|
||||||
}
|
}
|
||||||
message = {
|
message = {
|
||||||
'event': 'purchase_created',
|
'event': 'purchase_created',
|
||||||
'payload': payload,
|
'payload': payload,
|
||||||
'metadata': metadata,
|
'metadata': metadata,
|
||||||
}
|
}
|
||||||
self._logger.info(f'Create purchase request for organization_id={legal_entity.id } with usdt_amount={usdt_amount}')
|
self._logger.info(f'Create purchase request for organization_id={legal_entity.id } with rub_amount={rub_amount}')
|
||||||
try:
|
try:
|
||||||
await self._messanger.publish_to_queue(
|
await self._messanger.publish_to_queue(
|
||||||
queue=settings.RABBIT_TELEGRAM_NOTIFY_QUEUE,
|
queue=settings.RABBIT_TELEGRAM_NOTIFY_QUEUE,
|
||||||
|
|||||||
@@ -22,11 +22,9 @@ class UploadDocsCommand:
|
|||||||
@transactional
|
@transactional
|
||||||
async def __call__(
|
async def __call__(
|
||||||
self,
|
self,
|
||||||
user_id: str,
|
|
||||||
*,
|
*,
|
||||||
comment: str | None = None,
|
comment: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
legal_entity = await require_legal_entity(user_id, self._unit_of_work)
|
|
||||||
message_id = str(ULID())
|
message_id = str(ULID())
|
||||||
now = datetime.now(timezone.utc).isoformat()
|
now = datetime.now(timezone.utc).isoformat()
|
||||||
metadata = {
|
metadata = {
|
||||||
@@ -34,15 +32,13 @@ class UploadDocsCommand:
|
|||||||
'timestamp': now,
|
'timestamp': now,
|
||||||
'message_id': message_id,
|
'message_id': message_id,
|
||||||
}
|
}
|
||||||
payload = {
|
payload = {}
|
||||||
'id_client': legal_entity.id,
|
|
||||||
}
|
|
||||||
message = {
|
message = {
|
||||||
'event': 'docs_upload',
|
'event': 'docs_upload',
|
||||||
'payload': payload,
|
'payload': payload,
|
||||||
'metadata': metadata,
|
'metadata': metadata,
|
||||||
}
|
}
|
||||||
self._logger.info(f'Upload docs for organization_id={legal_entity.id}')
|
self._logger.info(f'New client uploaded docs, sending notification to telegram')
|
||||||
try:
|
try:
|
||||||
await self._messanger.publish_to_queue(
|
await self._messanger.publish_to_queue(
|
||||||
queue=settings.RABBIT_TELEGRAM_NOTIFY_QUEUE,
|
queue=settings.RABBIT_TELEGRAM_NOTIFY_QUEUE,
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ class PurchaseRequestEntity:
|
|||||||
id: str
|
id: str
|
||||||
organization_id: str
|
organization_id: str
|
||||||
status: str
|
status: str
|
||||||
usdt_amount: Decimal
|
rub_amount: Decimal
|
||||||
rub_amount: Decimal | None
|
usdt_amount: Decimal | None
|
||||||
exchange_rate: Decimal | None
|
exchange_rate: Decimal | None
|
||||||
service_fee_percent: Decimal | None
|
service_fee_percent: Decimal | None
|
||||||
comment: str | None
|
comment: str | None
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ class Settings(BaseSettings):
|
|||||||
CSRF_COOKIE_PATH: str = '/'
|
CSRF_COOKIE_PATH: str = '/'
|
||||||
CSRF_COOKIE_DOMAIN: str | None = None
|
CSRF_COOKIE_DOMAIN: str | None = None
|
||||||
|
|
||||||
|
CORS_ALLOW_ORIGIN_REGEX: str = r'https?://([a-z0-9-]+\.)*elcsa\.ru(:\d+)?$'
|
||||||
|
|
||||||
DOCS_USERNAME: str = 'admin'
|
DOCS_USERNAME: str = 'admin'
|
||||||
DOCS_PASSWORD: str = 'admin'
|
DOCS_PASSWORD: str = 'admin'
|
||||||
|
|
||||||
@@ -237,7 +239,7 @@ class Settings(BaseSettings):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def EXCLUDED_PATHS(self) -> List[str]:
|
def EXCLUDED_PATHS(self) -> List[str]:
|
||||||
return ['/docs', '/redoc', '/openapi.json', '/ping', '/health']
|
return ['/docs', '/redoc', '/openapi.json', '/ping', '/health', '/healthcheck']
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=1)
|
@lru_cache(maxsize=1)
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ class PurchaseRequestModel(Base, UlidPrimaryKeyMixin, AuditTimestampsMixin):
|
|||||||
index=True,
|
index=True,
|
||||||
)
|
)
|
||||||
status: Mapped[str] = mapped_column(String(32), nullable=False, server_default='submitted', default='submitted')
|
status: Mapped[str] = mapped_column(String(32), nullable=False, server_default='submitted', default='submitted')
|
||||||
usdt_amount: Mapped[Decimal] = mapped_column(Numeric(18, 8), nullable=False)
|
rub_amount: Mapped[Decimal] = mapped_column(Numeric(18, 8), nullable=False)
|
||||||
rub_amount: Mapped[Decimal | None] = mapped_column(Numeric(18, 2), nullable=True)
|
usdt_amount: Mapped[Decimal | None] = mapped_column(Numeric(18, 2), nullable=True)
|
||||||
exchange_rate: Mapped[Decimal | None] = mapped_column(Numeric(18, 8), nullable=True)
|
exchange_rate: Mapped[Decimal | None] = mapped_column(Numeric(18, 8), nullable=True)
|
||||||
service_fee_percent: Mapped[Decimal | None] = mapped_column(Numeric(5, 2), nullable=True)
|
service_fee_percent: Mapped[Decimal | None] = mapped_column(Numeric(5, 2), nullable=True)
|
||||||
comment: Mapped[str | None] = mapped_column(Text, nullable=True)
|
comment: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class PurchaseRequestRepository(IPurchaseRequestRepository):
|
|||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
organization_id: str,
|
organization_id: str,
|
||||||
usdt_amount: Decimal,
|
rub_amount: Decimal,
|
||||||
comment: str | None,
|
comment: str | None,
|
||||||
target_wallet_chain: str | None,
|
target_wallet_chain: str | None,
|
||||||
target_wallet_address: str | None,
|
target_wallet_address: str | None,
|
||||||
@@ -59,7 +59,7 @@ class PurchaseRequestRepository(IPurchaseRequestRepository):
|
|||||||
id=str(ULID()),
|
id=str(ULID()),
|
||||||
organization_id=organization_id,
|
organization_id=organization_id,
|
||||||
status='submitted',
|
status='submitted',
|
||||||
usdt_amount=usdt_amount,
|
rub_amount=rub_amount,
|
||||||
comment=comment,
|
comment=comment,
|
||||||
target_wallet_chain=target_wallet_chain or 'ETH',
|
target_wallet_chain=target_wallet_chain or 'ETH',
|
||||||
target_wallet_address=target_wallet_address,
|
target_wallet_address=target_wallet_address,
|
||||||
|
|||||||
@@ -37,9 +37,7 @@ class RabbitClient(IQueueMessanger):
|
|||||||
correlation_id: str | None = None,
|
correlation_id: str | None = None,
|
||||||
message_id: str | None = None,
|
message_id: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
print("check2")
|
|
||||||
await self._ensure_connected()
|
await self._ensure_connected()
|
||||||
print("check3")
|
|
||||||
await self._broker.publish(
|
await self._broker.publish(
|
||||||
message,
|
message,
|
||||||
queue=queue,
|
queue=queue,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import time
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from decimal import Decimal, InvalidOperation
|
from decimal import Decimal, InvalidOperation
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -19,6 +20,8 @@ from src.infrastructure.wallet_balances.token_registry import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
TIMEOUT_SECONDS = 15
|
TIMEOUT_SECONDS = 15
|
||||||
|
RETRY_ATTEMPTS = 2
|
||||||
|
PRICE_CACHE_SECONDS = 30
|
||||||
BLOCKSTREAM_URL = 'https://blockstream.info/api'
|
BLOCKSTREAM_URL = 'https://blockstream.info/api'
|
||||||
TRONGRID_URL = 'https://api.trongrid.io'
|
TRONGRID_URL = 'https://api.trongrid.io'
|
||||||
COINGECKO_URL = 'https://api.coingecko.com/api/v3/simple/price'
|
COINGECKO_URL = 'https://api.coingecko.com/api/v3/simple/price'
|
||||||
@@ -49,6 +52,10 @@ class WalletBalance:
|
|||||||
|
|
||||||
|
|
||||||
class WalletBalanceService:
|
class WalletBalanceService:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._prices_cache: dict[tuple[tuple[str,str],...],tuple[float,dict[str,Decimal | None]]] = {}
|
||||||
|
|
||||||
|
|
||||||
async def get_wallet_balances(self, wallets) -> list[WalletBalance]:
|
async def get_wallet_balances(self, wallets) -> list[WalletBalance]:
|
||||||
prices = await self._get_prices(wallets)
|
prices = await self._get_prices(wallets)
|
||||||
results = await asyncio.gather(
|
results = await asyncio.gather(
|
||||||
@@ -193,10 +200,17 @@ class WalletBalanceService:
|
|||||||
for token in self._tokens_for_chain(chain):
|
for token in self._tokens_for_chain(chain):
|
||||||
coin_id = get_coingecko_id(chain, token['symbol'])
|
coin_id = get_coingecko_id(chain, token['symbol'])
|
||||||
if coin_id:
|
if coin_id:
|
||||||
pairs[f'{chain}:{token['symbol']}'] = coin_id
|
symbol = token['symbol']
|
||||||
|
pairs[f'{chain}:{symbol}'] = coin_id
|
||||||
if not pairs:
|
if not pairs:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
cache_key = tuple(sorted(pairs.items()))
|
||||||
|
cached = self._prices_cache.get(cache_key)
|
||||||
|
now = time.monotonic()
|
||||||
|
if cached is not None and now - cached[0] < PRICE_CACHE_SECONDS:
|
||||||
|
return dict(cached[1])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = await self._get_json(
|
response = await self._get_json(
|
||||||
COINGECKO_URL,
|
COINGECKO_URL,
|
||||||
@@ -210,10 +224,12 @@ class WalletBalanceService:
|
|||||||
except Exception:
|
except Exception:
|
||||||
return {key: None for key in pairs}
|
return {key: None for key in pairs}
|
||||||
|
|
||||||
return {
|
prices = {
|
||||||
key: self._decimal_or_none((response.get(coin_id) or {}).get('usd'))
|
key: self._decimal_or_none((response.get(coin_id) or {}).get('usd'))
|
||||||
for key, coin_id in pairs.items()
|
for key, coin_id in pairs.items()
|
||||||
}
|
}
|
||||||
|
self._prices_cache[cache_key] = (now,prices)
|
||||||
|
return prices
|
||||||
|
|
||||||
def _token_amounts(
|
def _token_amounts(
|
||||||
self,
|
self,
|
||||||
@@ -287,9 +303,16 @@ class WalletBalanceService:
|
|||||||
timeout: int = TIMEOUT_SECONDS,
|
timeout: int = TIMEOUT_SECONDS,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
async with httpx.AsyncClient(timeout=timeout) as client:
|
async with httpx.AsyncClient(timeout=timeout) as client:
|
||||||
|
for attempt in range(RETRY_ATTEMPTS + 1):
|
||||||
|
try:
|
||||||
response = await client.get(url, params=params, headers=headers)
|
response = await client.get(url, params=params, headers=headers)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return response.json()
|
return response.json()
|
||||||
|
except (httpx.RequestError,httpx.HTTPStatusError,ValueError):
|
||||||
|
if attempt >= RETRY_ATTEMPTS:
|
||||||
|
raise
|
||||||
|
await asyncio.sleep(0.2 * (attempt + 1))
|
||||||
|
raise RuntimeError('HTTP GET failed')
|
||||||
|
|
||||||
async def _post_json(
|
async def _post_json(
|
||||||
self,
|
self,
|
||||||
@@ -300,9 +323,16 @@ class WalletBalanceService:
|
|||||||
timeout: int = TIMEOUT_SECONDS,
|
timeout: int = TIMEOUT_SECONDS,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
async with httpx.AsyncClient(timeout=timeout) as client:
|
async with httpx.AsyncClient(timeout=timeout) as client:
|
||||||
|
for attempt in range(RETRY_ATTEMPTS + 1):
|
||||||
|
try:
|
||||||
response = await client.post(url, json=payload, headers=headers)
|
response = await client.post(url, json=payload, headers=headers)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return response.json()
|
return response.json()
|
||||||
|
except (httpx.RequestError,httpx.HTTPStatusError,ValueError):
|
||||||
|
if attempt >= RETRY_ATTEMPTS:
|
||||||
|
raise
|
||||||
|
await asyncio.sleep(0.2 * (attempt + 1))
|
||||||
|
raise RuntimeError('HTTP POST failed')
|
||||||
|
|
||||||
def _tokens_for_chain(self, chain: str) -> list[dict]:
|
def _tokens_for_chain(self, chain: str) -> list[dict]:
|
||||||
if chain in {'ETH', 'BSC'}:
|
if chain in {'ETH', 'BSC'}:
|
||||||
|
|||||||
16
src/main.py
16
src/main.py
@@ -74,7 +74,12 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
|||||||
path=settings.VAULT_CRYPTO_MASTER_KEY_PATH,
|
path=settings.VAULT_CRYPTO_MASTER_KEY_PATH,
|
||||||
)
|
)
|
||||||
logger.info('Crypto master key loaded from Vault')
|
logger.info('Crypto master key loaded from Vault')
|
||||||
|
try:
|
||||||
yield
|
yield
|
||||||
|
finally:
|
||||||
|
sched = getattr(app.state,'jwt_keys_scheduler',None)
|
||||||
|
if sched:
|
||||||
|
sched.shutdown(wait=False)
|
||||||
logger.info(f'B2B service instance ended with id {instance_id}')
|
logger.info(f'B2B service instance ended with id {instance_id}')
|
||||||
|
|
||||||
|
|
||||||
@@ -82,7 +87,7 @@ app: FastAPI = FastAPI(
|
|||||||
redoc_url=None,
|
redoc_url=None,
|
||||||
docs_url=None,
|
docs_url=None,
|
||||||
lifespan=lifespan,
|
lifespan=lifespan,
|
||||||
title='B2B Service',
|
title='Elcsa. B2B Service',
|
||||||
version='1.0.0',
|
version='1.0.0',
|
||||||
description='Purchase requests API for legal entity client users.',
|
description='Purchase requests API for legal entity client users.',
|
||||||
license_info={
|
license_info={
|
||||||
@@ -111,7 +116,7 @@ app.add_middleware(
|
|||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=[],
|
allow_origins=[],
|
||||||
allow_origin_regex='.*',
|
allow_origin_regex=settings.CORS_ALLOW_ORIGIN_REGEX,
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=['*'],
|
allow_methods=['*'],
|
||||||
allow_headers=['*'],
|
allow_headers=['*'],
|
||||||
@@ -146,3 +151,10 @@ async def ping() -> dict[str, str]:
|
|||||||
'message': 'pong',
|
'message': 'pong',
|
||||||
'status': 'ok',
|
'status': 'ok',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@app.get('/healthcheck')
|
||||||
|
@app.get('/health')
|
||||||
|
async def health() -> dict[str, str]:
|
||||||
|
return {
|
||||||
|
'status': 'ok',
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from functools import lru_cache
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
from src.application.abstractions import IUnitOfWork
|
from src.application.abstractions import IUnitOfWork
|
||||||
from src.application.commands import (
|
from src.application.commands import (
|
||||||
@@ -9,6 +10,7 @@ from src.application.commands import (
|
|||||||
GetPurchaseRequestCommand,
|
GetPurchaseRequestCommand,
|
||||||
ListOrganizationWalletsCommand,
|
ListOrganizationWalletsCommand,
|
||||||
ListPurchaseRequestsCommand,
|
ListPurchaseRequestsCommand,
|
||||||
|
UploadDocsCommand,
|
||||||
)
|
)
|
||||||
from src.application.contracts import ILogger, IQueueMessanger
|
from src.application.contracts import ILogger, IQueueMessanger
|
||||||
from src.infrastructure.wallet_balances import WalletBalanceService
|
from src.infrastructure.wallet_balances import WalletBalanceService
|
||||||
@@ -53,13 +55,18 @@ def get_list_organization_wallets_command(
|
|||||||
return ListOrganizationWalletsCommand(unit_of_work=unit_of_work, logger=logger)
|
return ListOrganizationWalletsCommand(unit_of_work=unit_of_work, logger=logger)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=1)
|
||||||
|
def _wallet_balance_service() -> WalletBalanceService:
|
||||||
|
return WalletBalanceService()
|
||||||
|
|
||||||
|
|
||||||
def get_get_organization_wallet_balances_command(
|
def get_get_organization_wallet_balances_command(
|
||||||
logger: ILogger = Depends(get_logger),
|
logger: ILogger = Depends(get_logger),
|
||||||
unit_of_work: IUnitOfWork = Depends(get_unit_of_work),
|
unit_of_work: IUnitOfWork = Depends(get_unit_of_work),
|
||||||
) -> GetOrganizationWalletBalancesCommand:
|
) -> GetOrganizationWalletBalancesCommand:
|
||||||
return GetOrganizationWalletBalancesCommand(
|
return GetOrganizationWalletBalancesCommand(
|
||||||
unit_of_work=unit_of_work,
|
unit_of_work=unit_of_work,
|
||||||
balance_service=WalletBalanceService(),
|
balance_service=_wallet_balance_service(),
|
||||||
logger=logger,
|
logger=logger,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -76,3 +83,10 @@ def get_get_organization_secret_keys_command(
|
|||||||
unit_of_work: IUnitOfWork = Depends(get_unit_of_work),
|
unit_of_work: IUnitOfWork = Depends(get_unit_of_work),
|
||||||
) -> GetOrganizationSecretKeysCommand:
|
) -> GetOrganizationSecretKeysCommand:
|
||||||
return GetOrganizationSecretKeysCommand(unit_of_work=unit_of_work, logger=logger)
|
return GetOrganizationSecretKeysCommand(unit_of_work=unit_of_work, logger=logger)
|
||||||
|
|
||||||
|
def get_upload_docs_command(
|
||||||
|
logger: ILogger = Depends(get_logger),
|
||||||
|
unit_of_work: IUnitOfWork = Depends(get_unit_of_work),
|
||||||
|
messanger: IQueueMessanger = Depends(get_queue_messanger),
|
||||||
|
) -> UploadDocsCommand:
|
||||||
|
return UploadDocsCommand(unit_of_work=unit_of_work, logger=logger, messanger=messanger)
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ from fastapi import APIRouter
|
|||||||
|
|
||||||
from src.presentation.routing.organizations import organizations_router
|
from src.presentation.routing.organizations import organizations_router
|
||||||
from src.presentation.routing.purchase_requests import purchase_requests_router
|
from src.presentation.routing.purchase_requests import purchase_requests_router
|
||||||
|
from src.presentation.routing.documents import upload_docs_router
|
||||||
|
|
||||||
v1_router = APIRouter(prefix='/v1')
|
v1_router = APIRouter(prefix='/v1')
|
||||||
v1_router.include_router(organizations_router)
|
v1_router.include_router(organizations_router)
|
||||||
|
v1_router.include_router(upload_docs_router)
|
||||||
|
|
||||||
__all__ = ['purchase_requests_router', 'organizations_router', 'v1_router']
|
__all__ = ['purchase_requests_router', 'organizations_router', 'upload_docs_router', 'v1_router']
|
||||||
|
|||||||
17
src/presentation/routing/documents.py
Normal file
17
src/presentation/routing/documents.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
|
||||||
|
from src.application.commands import (
|
||||||
|
UploadDocsCommand,
|
||||||
|
)
|
||||||
|
from src.presentation.dependencies.commands import (
|
||||||
|
get_upload_docs_command,
|
||||||
|
)
|
||||||
|
|
||||||
|
upload_docs_router = APIRouter(prefix='/upload-docs', tags=['upload-docs'])
|
||||||
|
|
||||||
|
@upload_docs_router.post('')
|
||||||
|
async def upload_docs(
|
||||||
|
command: UploadDocsCommand = Depends(get_upload_docs_command),
|
||||||
|
):
|
||||||
|
await command()
|
||||||
|
return {'message': 'Documents uploaded successfully.'}
|
||||||
@@ -33,7 +33,7 @@ async def create_purchase_request(
|
|||||||
):
|
):
|
||||||
item = await command(
|
item = await command(
|
||||||
auth.user_id,
|
auth.user_id,
|
||||||
usdt_amount=body.usdt_amount,
|
rub_amount=body.rub_amount,
|
||||||
comment=body.comment,
|
comment=body.comment,
|
||||||
target_wallet_chain=body.target_wallet_chain,
|
target_wallet_chain=body.target_wallet_chain,
|
||||||
target_wallet_address=body.target_wallet_address,
|
target_wallet_address=body.target_wallet_address,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from pydantic import BaseModel, Field
|
|||||||
|
|
||||||
|
|
||||||
class CreatePurchaseRequestBody(BaseModel):
|
class CreatePurchaseRequestBody(BaseModel):
|
||||||
usdt_amount: Decimal = Field(gt=0)
|
rub_amount: Decimal = Field(gt=0)
|
||||||
comment: str | None = None
|
comment: str | None = None
|
||||||
target_wallet_chain: str | None = Field(default='ETH', max_length=16)
|
target_wallet_chain: str | None = Field(default='ETH', max_length=16)
|
||||||
target_wallet_address: str | None = Field(default=None, max_length=128)
|
target_wallet_address: str | None = Field(default=None, max_length=128)
|
||||||
@@ -16,8 +16,8 @@ class PurchaseRequestResponse(BaseModel):
|
|||||||
id: str
|
id: str
|
||||||
organization_id: str
|
organization_id: str
|
||||||
status: str
|
status: str
|
||||||
usdt_amount: str
|
rub_amount: str
|
||||||
rub_amount: str | None
|
usdt_amount: str | None
|
||||||
exchange_rate: str | None
|
exchange_rate: str | None
|
||||||
service_fee_percent: str | None
|
service_fee_percent: str | None
|
||||||
comment: str | None
|
comment: str | None
|
||||||
|
|||||||
Reference in New Issue
Block a user