feat: add view order history and filter by date

This commit is contained in:
2026-06-17 13:01:26 +03:00
parent 4bdf295007
commit 909f4d9298
20 changed files with 611 additions and 7 deletions

View File

@@ -10,6 +10,7 @@ from src.application.abstractions.repositories import (
IPurchaseRequestRepository, IPurchaseRequestRepository,
IUserRepository, IUserRepository,
IWalletRepository, IWalletRepository,
IOrderRepository,
) )
@@ -40,4 +41,7 @@ class IUnitOfWork(Protocol):
def purchase_request_repository(self) -> IPurchaseRequestRepository: ... def purchase_request_repository(self) -> IPurchaseRequestRepository: ...
@property @property
def wallet_repository(self) -> IWalletRepository: ... def wallet_repository(self) -> IWalletRepository: ...
@property
def order_repository(self) -> IOrderRepository: ...

View File

@@ -5,3 +5,4 @@ from src.application.abstractions.repositories.i_legal_entity_repository import
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 from src.application.abstractions.repositories.i_wallets_repository import IWalletRepository
from src.application.abstractions.repositories.i_order_repository import IOrderRepository

View File

@@ -0,0 +1,40 @@
from abc import ABC,abstractmethod
from src.application.domain.entities.order import OrderEntity
from src.application.domain.enums import OrderStatus
class IOrderRepository(ABC):
@abstractmethod
async def get_by_id(self,order_id: str) -> OrderEntity | None:
raise NotImplementedError
@abstractmethod
async def list_by_user_id(self,*,user_id: str,limit: int,offset: int) -> list[OrderEntity]:
raise NotImplementedError
@abstractmethod
async def list_all(self,*,limit: int,offset: int, search: str | None = None) -> list[OrderEntity]:
raise NotImplementedError
@abstractmethod
async def list_by_date(self,*,year: int,month: int,limit: int,offset: int) -> list[OrderEntity]:
raise NotImplementedError
@abstractmethod
async def count_all(self,*,search: str | None = None) -> int:
raise NotImplementedError
@abstractmethod
async def count_by_date(self,*,year: int,month: int | None = None) -> int:
raise NotImplementedError
@abstractmethod
async def count_by_user_id(self,*,user_id: str) -> int:
raise NotImplementedError

View File

@@ -38,6 +38,12 @@ from src.application.commands.user_wallet_commands import (
GetUserSecretKeysCommand, GetUserSecretKeysCommand,
ListUserWalletsCommand, ListUserWalletsCommand,
) )
from src.application.commands.orders_commands import (
ListOrdersCommand,
ListUserOrdersCommand,
ListOrdersByDateCommand,
GetOrderCommand,
)
__all__ = [ __all__ = [
'AdminLoginCommand', 'AdminLoginCommand',
@@ -68,4 +74,8 @@ __all__ = [
'ListUserWalletsCommand', 'ListUserWalletsCommand',
'GetUserMnemonicCommand', 'GetUserMnemonicCommand',
'GetUserSecretKeysCommand', 'GetUserSecretKeysCommand',
'ListOrdersCommand',
'ListUserOrdersCommand',
'ListOrdersByDateCommand',
'GetOrderCommand',
] ]

View File

@@ -0,0 +1,80 @@
from __future__ import annotations
from dataclasses import dataclass
from decimal import Decimal
from src.application.abstractions import IUnitOfWork
from src.application.contracts import ILogger
from src.application.domain.entities import OrderEntity
from src.application.domain.exceptions import NotFoundException
from src.infrastructure.database.decorators import transactional
class ListUserOrdersCommand:
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, limit: int, offset: int) -> tuple[list[OrderEntity], int]:
orders = await self._unit_of_work.order_repository.list_by_user_id(
user_id=user_id,
limit=limit,
offset=offset,
)
total = await self._unit_of_work.order_repository.count_by_user_id(user_id=user_id)
return orders, total
class ListOrdersCommand:
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, offset: int, search: str | None = None) -> tuple[list[OrderEntity], int]:
orders = await self._unit_of_work.order_repository.list_all(
limit=limit,
offset=offset,
search=search
)
total = await self._unit_of_work.order_repository.count_all(search=search)
return orders, total
class ListOrdersByDateCommand:
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, offset: int, year: int, month: int | None = None) -> tuple[list[OrderEntity], int]:
orders = await self._unit_of_work.order_repository.list_by_date(
year=year,
month=month,
limit=limit,
offset=offset
)
total = await self._unit_of_work.order_repository.count_by_date(year=year, month=month)
return orders, total
class GetOrderCommand:
def __init__(self, unit_of_work: IUnitOfWork, logger: ILogger):
self._unit_of_work = unit_of_work
self._logger = logger
@transactional
async def __call__(self, *, order_id: str) -> OrderEntity:
order = await self._unit_of_work.order_repository.get_by_id(order_id=order_id)
if order is None:
raise NotFoundException(message='Order not found')
return order

View File

@@ -1,5 +1,6 @@
from src.application.domain.entities.user import UserEntity from src.application.domain.entities.user import UserEntity
from src.application.domain.entities.admin_user import AdminUserEntity from src.application.domain.entities.admin_user import AdminUserEntity
from src.application.domain.entities.admin_session import AdminSessionEntity from src.application.domain.entities.admin_session import AdminSessionEntity
from src.application.domain.entities.order import OrderEntity
__all__ = ['UserEntity', 'AdminUserEntity', 'AdminSessionEntity'] __all__ = ['UserEntity', 'AdminUserEntity', 'AdminSessionEntity', 'OrderEntity']

View File

@@ -0,0 +1,36 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from decimal import Decimal
from src.application.domain.enums import OrderStatus
@dataclass(slots=True)
class OrderEntity:
id: str | None = None
created_at: datetime | None = None
updated_at: datetime | None = None
user_id: str | None = None
usdt_amount: Decimal | None = None
usdt_exchange_rate: Decimal | None = None
gas_fee: Decimal | None = None
total_price: Decimal | None = None
service_fee: Decimal | None = None
status: OrderStatus | None = None
client_payment_id: str | None = None
itpay_payment_qr_url_desktop: str | None = None
itpay_payment_qr_url_android: str | None = None
itpay_payment_qr_url_ios: str | None = None
itpay_payment_qr_image_desktop: str | None = None
itpay_payment_qr_image_android: str | None = None
itpay_payment_qr_image_ios: str | None = None
itpay_id: str | None = None
itpay_qr_id: str | None = None
itpay_amount: Decimal | None = None
itpay_created_at: datetime | None = None

View File

@@ -1,2 +1,3 @@
from src.application.domain.enums.log_level import LogLevel from src.application.domain.enums.log_level import LogLevel
from src.application.domain.enums.log_format import LogFormat from src.application.domain.enums.log_format import LogFormat
from src.application.domain.enums.order_status import OrderStatus

View File

@@ -0,0 +1,9 @@
from enum import Enum
class OrderStatus(str, Enum):
PENDING = 'pending'
REJECTED = 'rejected'
COMPLETED = 'completed'
CANCELLED = 'cancelled'
ERROR = 'error'

View File

@@ -6,6 +6,7 @@ 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 from src.infrastructure.database.models.wallets import WalletModel
from src.infrastructure.database.models.order import OrderModel
__all__ = [ __all__ = [
'Base', 'Base',
@@ -16,4 +17,5 @@ __all__ = [
'OrganizationWalletModel', 'OrganizationWalletModel',
'PurchaseRequestModel', 'PurchaseRequestModel',
'WalletModel', 'WalletModel',
'OrderModel',
] ]

View File

@@ -0,0 +1,54 @@
from __future__ import annotations
from datetime import datetime
from decimal import Decimal
from sqlalchemy import DateTime, Enum as SAEnum, ForeignKey, Numeric, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from src.application.domain.enums import OrderStatus
from src.infrastructure.database.models.base import Base
from src.infrastructure.database.models.mixins import AuditTimestampsMixin, UlidPrimaryKeyMixin
class OrderModel(Base, UlidPrimaryKeyMixin, AuditTimestampsMixin):
__tablename__ = 'orders'
user_id: Mapped[str] = mapped_column(
String(26),
ForeignKey('users.id', ondelete='RESTRICT'),
nullable=False,
index=True,
)
usdt_amount: Mapped[Decimal] = mapped_column(Numeric(38, 2), nullable=False)
usdt_exchange_rate: Mapped[Decimal] = mapped_column(Numeric(38, 2), nullable=False)
gas_fee: Mapped[Decimal] = mapped_column(Numeric(38, 2), nullable=False)
total_price: Mapped[Decimal] = mapped_column(Numeric(38, 2), nullable=False)
service_fee: Mapped[Decimal] = mapped_column(Numeric(38, 2), nullable=False)
status: Mapped[OrderStatus] = mapped_column(
SAEnum(OrderStatus,name='order_status_enum',values_callable=lambda x:[e.value for e in x]),
nullable=False,
index=True,
default=OrderStatus.PENDING,
)
client_payment_id: Mapped[str] = mapped_column(
String(26),
nullable=False,
unique=True,
index=True
)
itpay_payment_qr_url_desktop: Mapped[str | None] = mapped_column(Text, nullable=True)
itpay_payment_qr_url_android: Mapped[str | None] = mapped_column(Text, nullable=True)
itpay_payment_qr_url_ios: Mapped[str | None] = mapped_column(Text, nullable=True)
itpay_payment_qr_image_desktop: Mapped[str | None] = mapped_column(Text, nullable=True)
itpay_payment_qr_image_android: Mapped[str | None] = mapped_column(Text, nullable=True)
itpay_payment_qr_image_ios: Mapped[str | None] = mapped_column(Text, nullable=True)
itpay_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
itpay_qr_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
itpay_amount: Mapped[Decimal] = mapped_column(Numeric(38, 2), nullable=False)
itpay_created_at: Mapped[DateTime] = mapped_column(
DateTime(timezone=True),
nullable=False
)

View File

@@ -5,6 +5,7 @@ from src.infrastructure.database.repositories.legal_entity_repository import Leg
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 from src.infrastructure.database.repositories.wallets_repository import WalletRepository
from src.infrastructure.database.repositories.orders_repository import OrderRepository
__all__ = [ __all__ = [
'AdminUserRepository', 'AdminUserRepository',
@@ -14,4 +15,5 @@ __all__ = [
'OrganizationWalletRepository', 'OrganizationWalletRepository',
'PurchaseRequestRepository', 'PurchaseRequestRepository',
'WalletRepository', 'WalletRepository',
'OrderRepository',
] ]

View File

@@ -0,0 +1,151 @@
from __future__ import annotations
from dataclasses import replace
from datetime import datetime, timezone
from decimal import Decimal
from sqlalchemy import desc, select, or_, extract, func
from sqlalchemy.ext.asyncio import AsyncSession
from src.application.abstractions.repositories.i_order_repository import IOrderRepository
from src.application.contracts import ILogger
from src.application.domain.entities.order import OrderEntity
from src.application.domain.enums import OrderStatus
from src.infrastructure.database.models.order import OrderModel
class OrderRepository(IOrderRepository):
def __init__(self, session: AsyncSession, logger: ILogger):
self._session = session
self._logger = logger
@staticmethod
def _to_entity(model: OrderModel) -> OrderEntity:
return OrderEntity(
id=model.id,
created_at=model.created_at,
updated_at=model.updated_at,
user_id=model.user_id,
usdt_amount=model.usdt_amount,
usdt_exchange_rate=model.usdt_exchange_rate,
gas_fee=model.gas_fee,
total_price=model.total_price,
service_fee=model.service_fee,
status=model.status,
client_payment_id=model.client_payment_id,
itpay_payment_qr_url_desktop=model.itpay_payment_qr_url_desktop,
itpay_payment_qr_url_android=model.itpay_payment_qr_url_android,
itpay_payment_qr_url_ios=model.itpay_payment_qr_url_ios,
itpay_payment_qr_image_desktop=model.itpay_payment_qr_image_desktop,
itpay_payment_qr_image_android=model.itpay_payment_qr_image_android,
itpay_payment_qr_image_ios=model.itpay_payment_qr_image_ios,
itpay_id=model.itpay_id,
itpay_qr_id=model.itpay_qr_id,
itpay_amount=model.itpay_amount,
itpay_created_at=model.itpay_created_at,
)
def _search_filter(self, search: str | None):
if not search or not search.strip():
return None
pattern = f'%{search.strip()}%'
return or_(
OrderModel.id.ilike(pattern),
OrderModel.user_id.ilike(pattern),
OrderModel.client_payment_id.ilike(pattern),
OrderModel.itpay_id.ilike(pattern),
OrderModel.itpay_qr_id.ilike(pattern),
)
async def get_by_id(self,order_id: str) -> OrderEntity | None:
stmt=select(OrderModel).where(OrderModel.id==order_id)
model=await self._session.scalar(stmt)
if model is None:
return None
return self._to_entity(model)
async def list_by_user_id(
self,
*,
user_id: str,
limit: int,
offset: int
) -> list[OrderEntity]:
stmt=(
select(OrderModel)
.where(OrderModel.user_id==user_id)
.order_by(desc(OrderModel.created_at))
.limit(limit)
.offset(offset)
)
result=await self._session.scalars(stmt)
return [self._to_entity(model) for model in result.all()]
async def list_all(
self,
*,
limit: int,
offset: int,
search: str | None = None
) -> list[OrderEntity]:
stmt=(
select(OrderModel)
.order_by(desc(OrderModel.created_at))
.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(model) for model in res.scalars().all()]
async def list_by_date(
self,
*,
year: int,
month: int,
limit: int,
offset: int,
) -> list[OrderEntity]:
stmt=(
select(OrderModel)
.where(
extract("year", OrderModel.created_at) == year,
extract("month", OrderModel.created_at) == month,
)
.order_by(desc(OrderModel.created_at))
.limit(limit)
.offset(offset)
)
res = await self._session.execute(stmt)
return [self._to_entity(model) for model in res.scalars().all()]
async def count_all(self,*,search: str | None = None) -> int:
stmt = select(func.count()).select_from(OrderModel)
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())
async def count_by_date(self,*,year: int,month: int ) -> int:
stmt = select(func.count()).select_from(OrderModel)
stmt = stmt.where(
extract("year", OrderModel.created_at) == year,
extract("month", OrderModel.created_at) == month
)
res = await self._session.execute(stmt)
return int(res.scalar_one())
async def count_by_user_id(self,*,user_id: str) -> int:
stmt = select(func.count()).select_from(OrderModel).where(OrderModel.user_id == user_id)
res = await self._session.execute(stmt)
return int(res.scalar_one())

View File

@@ -9,6 +9,7 @@ from src.application.abstractions.repositories import (
IPurchaseRequestRepository, IPurchaseRequestRepository,
IUserRepository, IUserRepository,
IWalletRepository, IWalletRepository,
IOrderRepository,
) )
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
@@ -20,6 +21,7 @@ from src.infrastructure.database.repositories import (
PurchaseRequestRepository, PurchaseRequestRepository,
UserRepository, UserRepository,
WalletRepository, WalletRepository,
OrderRepository,
) )
@@ -34,6 +36,7 @@ class UnitOfWork(IUnitOfWork):
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._wallet_repository: IWalletRepository | None = None
self._order_repository: IOrderRepository | None = None
self._logger: ILogger = logger self._logger: ILogger = logger
async def __aenter__(self): async def __aenter__(self):
@@ -103,3 +106,11 @@ class UnitOfWork(IUnitOfWork):
session=self._session, logger=self._logger session=self._session, logger=self._logger
) )
return self._purchase_request_repository return self._purchase_request_repository
@property
def order_repository(self) -> IOrderRepository:
if self._order_repository is None:
self._order_repository = OrderRepository(
session=self._session, logger=self._logger
)
return self._order_repository

View File

@@ -32,6 +32,10 @@ from src.application.commands import (
ListUserWalletsCommand, ListUserWalletsCommand,
GetUserMnemonicCommand, GetUserMnemonicCommand,
GetUserSecretKeysCommand, GetUserSecretKeysCommand,
ListOrdersCommand,
ListUserOrdersCommand,
ListOrdersByDateCommand,
GetOrderCommand,
) )
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
@@ -249,4 +253,28 @@ def get_get_user_secret_keys_command(
uow: IUnitOfWork = Depends(get_unit_of_work), uow: IUnitOfWork = Depends(get_unit_of_work),
logger: ILogger = Depends(get_logger), logger: ILogger = Depends(get_logger),
) -> GetUserSecretKeysCommand: ) -> GetUserSecretKeysCommand:
return GetUserSecretKeysCommand(uow, logger) return GetUserSecretKeysCommand(uow, logger)
def get_list_orders_command(
uow: IUnitOfWork = Depends(get_unit_of_work),
logger: ILogger = Depends(get_logger),
) -> ListOrdersCommand:
return ListOrdersCommand(uow, logger)
def get_list_user_orders_command(
uow: IUnitOfWork = Depends(get_unit_of_work),
logger: ILogger = Depends(get_logger),
) -> ListUserOrdersCommand:
return ListUserOrdersCommand(uow, logger)
def get_list_orders_by_date_command(
uow: IUnitOfWork = Depends(get_unit_of_work),
logger: ILogger = Depends(get_logger),
) -> ListOrdersByDateCommand:
return ListOrdersByDateCommand(uow, logger)
def get_get_order_command(
uow: IUnitOfWork = Depends(get_unit_of_work),
logger: ILogger = Depends(get_logger),
) -> GetOrderCommand:
return GetOrderCommand(uow, logger)

View File

@@ -7,6 +7,7 @@ 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.users import users_router from src.presentation.routing.users import users_router
from src.presentation.routing.search import search_router from src.presentation.routing.search import search_router
from src.presentation.routing.orders import orders_router
v1_router = APIRouter(prefix='/v1') v1_router = APIRouter(prefix='/v1')
v1_router.include_router(auth_router) v1_router.include_router(auth_router)
@@ -15,4 +16,5 @@ v1_router.include_router(organizations_router)
v1_router.include_router(documents_router) v1_router.include_router(documents_router)
v1_router.include_router(purchase_requests_router) v1_router.include_router(purchase_requests_router)
v1_router.include_router(users_router) v1_router.include_router(users_router)
v1_router.include_router(search_router) v1_router.include_router(search_router)
v1_router.include_router(orders_router)

View File

@@ -0,0 +1,85 @@
from fastapi import APIRouter, Depends, Request, Query, status
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_list_orders_command,
get_list_orders_by_date_command,
get_get_order_command,
)
from src.presentation.schemas.mappers import order_to_response
from src.presentation.schemas.order import (
OrdersResponse,
OrderResponse,
OrderDetailResponse,
)
from src.application.commands.orders_commands import (
ListOrdersCommand,
ListOrdersByDateCommand,
GetOrderCommand,
)
orders_router = APIRouter(prefix='/orders', tags=['orders'])
@orders_router.get('', response_model=OrdersResponse)
async def list_orders(
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: ListOrdersCommand = Depends(get_list_orders_command),
):
items, total = await command(limit=limit, offset=offset, search=q)
return OrdersResponse(
items=[order_to_response(x) for x in items],
total=total,
)
@orders_router.get('/{order_id}', response_model=OrderDetailResponse)
async def get_order(
order_id: str,
auth: AdminAuthContext = Depends(require_admin_access),
command: GetOrderCommand = Depends(get_get_order_command),
):
order = await command(order_id=order_id)
return OrderDetailResponse(
id=order.id,
created_at=order.created_at.isoformat() if order.created_at is not None else None,
updated_at=order.updated_at.isoformat() if order.updated_at is not None else None,
user_id=order.user_id,
usdt_amount=str(order.usdt_amount) if order.usdt_amount is not None else None,
usdt_exchange_rate=str(order.usdt_exchange_rate) if order.usdt_exchange_rate is not None else None,
gas_fee=str(order.gas_fee) if order.gas_fee is not None else None,
total_price=str(order.total_price) if order.total_price is not None else None,
service_fee=str(order.service_fee) if order.service_fee is not None else None,
status=order.status,
client_payment_id=order.client_payment_id,
itpay_payment_qr_url_desktop=order.itpay_payment_qr_url_desktop,
itpay_payment_qr_url_android=order.itpay_payment_qr_url_android,
itpay_payment_qr_url_ios=order.itpay_payment_qr_url_ios,
itpay_payment_qr_image_desktop=order.itpay_payment_qr_image_desktop,
itpay_payment_qr_image_android=order.itpay_payment_qr_image_android,
itpay_payment_qr_image_ios=order.itpay_payment_qr_image_ios,
itpay_id=order.itpay_id,
itpay_qr_id=order.itpay_qr_id,
itpay_amount=str(order.itpay_amount) if order.itpay_amount is not None else None,
itpay_created_at=order.itpay_created_at.isoformat() if order.itpay_created_at is not None else None,
)
@orders_router.get('/filter/date', response_model=OrdersResponse)
async def list_orders_by_date(
year: int = Query(..., ge=2000, le=2100),
month: int = Query(..., ge=1, le=12),
limit: int = Query(default=50, ge=1, le=200),
offset: int = Query(default=0, ge=0),
auth: AdminAuthContext = Depends(require_admin_access),
command: ListOrdersByDateCommand = Depends(get_list_orders_by_date_command),
):
orders, total = await command(year=year, month=month, limit=limit, offset=offset)
return OrdersResponse(
items=[order_to_response(o) for o in orders],
total=total,
)

View File

@@ -11,6 +11,7 @@ from src.presentation.dependencies.commands import (
get_list_user_wallets_command, get_list_user_wallets_command,
get_get_user_mnemonic_command, get_get_user_mnemonic_command,
get_get_user_secret_keys_command, get_get_user_secret_keys_command,
get_list_user_orders_command,
) )
from src.presentation.schemas.mappers import ( from src.presentation.schemas.mappers import (
mnemonic_to_response, mnemonic_to_response,
@@ -18,6 +19,7 @@ from src.presentation.schemas.mappers import (
party_search_to_response, party_search_to_response,
secret_key_to_response, secret_key_to_response,
wallet_to_response, wallet_to_response,
order_to_response,
) )
from src.presentation.schemas.user import ( from src.presentation.schemas.user import (
UserResponse, UserResponse,
@@ -30,6 +32,9 @@ from src.presentation.schemas.entity import (
SecretKeyResponse, SecretKeyResponse,
WalletResponse, WalletResponse,
) )
from src.presentation.schemas.order import (
OrdersResponse,
)
from src.application.commands.set_password import SetPasswordCommand from src.application.commands.set_password import SetPasswordCommand
from src.application.commands.user_commands import ( from src.application.commands.user_commands import (
ListUsersCommand, ListUsersCommand,
@@ -40,8 +45,12 @@ from src.application.commands.user_wallet_commands import (
GetUserMnemonicCommand, GetUserMnemonicCommand,
GetUserSecretKeysCommand GetUserSecretKeysCommand
) )
from src.application.commands.orders_commands import (
ListUserOrdersCommand,
)
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.get('', response_model=UserListResponse) @users_router.get('', response_model=UserListResponse)
@@ -77,6 +86,21 @@ async def list_user_wallets(
return [wallet_to_response(w) for w in wallets] return [wallet_to_response(w) for w in wallets]
@users_router.get('/{user_id}/orders', response_model=OrdersResponse)
async def list_user_orders(
user_id: str,
limit: int = Query(default=50, ge=1, le=200),
offset: int = Query(default=0, ge=0),
auth: AdminAuthContext = Depends(require_admin_access),
command: ListUserOrdersCommand = Depends(get_list_user_orders_command),
):
orders, total = await command(user_id=user_id, limit=limit, offset=offset)
return OrdersResponse(
items=[order_to_response(o) for o in orders],
total=total,
)
@users_router.get('/{user_id}/wallets/mnemonic', response_model=MnemonicResponse) @users_router.get('/{user_id}/wallets/mnemonic', response_model=MnemonicResponse)
async def get_user_mnemonic( async def get_user_mnemonic(
user_id: str, user_id: str,
@@ -125,4 +149,4 @@ async def set_password(
command: SetPasswordCommand = Depends(get_set_password_command), command: SetPasswordCommand = Depends(get_set_password_command),
): ):
await command(user_id=user_id, 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'})

View File

@@ -1,4 +1,5 @@
from __future__ import annotations from __future__ import annotations
from urllib import response
from src.application.domain.entities.organization import ( from src.application.domain.entities.organization import (
LegalEntityEntity, LegalEntityEntity,
@@ -10,6 +11,9 @@ from src.application.domain.entities.organization import (
from src.application.domain.entities.user import ( from src.application.domain.entities.user import (
UserEntity, UserEntity,
) )
from src.application.domain.entities.order import (
OrderEntity,
)
from src.presentation.schemas.user import ( from src.presentation.schemas.user import (
UserResponse, UserResponse,
) )
@@ -25,6 +29,9 @@ from src.presentation.schemas.entity import (
SecretKeyResponse, SecretKeyResponse,
WalletResponse, WalletResponse,
) )
from src.presentation.schemas.order import (
OrderResponse,
)
def organization_to_response(entity: LegalEntityEntity) -> OrganizationResponse: def organization_to_response(entity: LegalEntityEntity) -> OrganizationResponse:
@@ -156,4 +163,17 @@ def user_to_response(entity: UserEntity) -> UserResponse:
created_at=entity.created_at.isoformat() if entity.created_at else None, created_at=entity.created_at.isoformat() if entity.created_at else None,
updated_at=entity.updated_at.isoformat() if entity.updated_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, kyc_verified_at=entity.kyc_verified_at.isoformat() if entity.kyc_verified_at else None,
) )
def order_to_response(entity: OrderEntity) -> OrderResponse:
return OrderResponse(
order_id=entity.id,
order_status=entity.status,
usdt_amount=str(entity.usdt_amount),
usdt_exchange_rate=str(entity.usdt_exchange_rate) if entity.usdt_exchange_rate is not None else None,
gas_fee=str(entity.gas_fee) if entity.gas_fee is not None else None,
total_price=str(entity.total_price) if entity.total_price is not None else None,
service_fee=str(entity.service_fee) if entity.service_fee is not None else None,
updated_at=entity.updated_at.isoformat() if entity.updated_at else None,
)

View File

@@ -0,0 +1,43 @@
from decimal import Decimal
from pydantic import BaseModel,Field
from src.application.domain.enums import OrderStatus
class OrderDetailResponse(BaseModel):
id: str | None = None
created_at: str | None = None
updated_at: str | None = None
user_id: str | None = None
usdt_amount: str | None = None
usdt_exchange_rate: str | None = None
gas_fee: str | None = None
total_price: str | None = None
service_fee: str | None = None
status: OrderStatus | None = None
client_payment_id: str | None = None
itpay_payment_qr_url_desktop: str | None = None
itpay_payment_qr_url_android: str | None = None
itpay_payment_qr_url_ios: str | None = None
itpay_payment_qr_image_desktop: str | None = None
itpay_payment_qr_image_android: str | None = None
itpay_payment_qr_image_ios: str | None = None
itpay_id: str | None = None
itpay_qr_id: str | None = None
itpay_amount: str | None = None
itpay_created_at: str | None = None
class OrderResponse(BaseModel):
order_id: str
order_status: OrderStatus | None = None
usdt_amount: str | None = None
usdt_exchange_rate: str | None = None
gas_fee: str | None = None
total_price: str | None = None
service_fee: str | None = None
updated_at: str | None = None
class OrdersResponse(BaseModel):
items: list[OrderResponse]
total: int