refactors
This commit is contained in:
parent
f46d2ae8b3
commit
168b67acee
12 changed files with 471 additions and 126 deletions
|
|
@ -1,22 +1,18 @@
|
|||
"""Exchange routes for Bitcoin trading."""
|
||||
|
||||
import uuid
|
||||
from datetime import UTC, date, datetime, time, timedelta
|
||||
from datetime import UTC, date, datetime, timedelta
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy import and_, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from auth import require_permission
|
||||
from database import get_db
|
||||
from date_validation import validate_date_in_range
|
||||
from exceptions import BadRequestError
|
||||
from mappers import ExchangeMapper
|
||||
from models import (
|
||||
Availability,
|
||||
BitcoinTransferMethod,
|
||||
Exchange,
|
||||
ExchangeStatus,
|
||||
Permission,
|
||||
PriceHistory,
|
||||
|
|
@ -24,6 +20,7 @@ from models import (
|
|||
User,
|
||||
)
|
||||
from price_fetcher import PAIR_BTC_EUR, SOURCE_BITFINEX, fetch_btc_eur_price
|
||||
from repositories.exchange import ExchangeRepository
|
||||
from repositories.price import PriceRepository
|
||||
from schemas import (
|
||||
AdminExchangeResponse,
|
||||
|
|
@ -44,6 +41,7 @@ from shared_constants import (
|
|||
PREMIUM_PERCENTAGE,
|
||||
SLOT_DURATION_MINUTES,
|
||||
)
|
||||
from utils.enum_validation import validate_enum
|
||||
|
||||
router = APIRouter(prefix="/api/exchange", tags=["exchange"])
|
||||
|
||||
|
|
@ -190,28 +188,18 @@ async def get_available_slots(
|
|||
validate_date_in_range(date_param, context="book")
|
||||
|
||||
# Get availability for the date
|
||||
result = await db.execute(
|
||||
select(Availability).where(Availability.date == date_param)
|
||||
)
|
||||
availabilities = result.scalars().all()
|
||||
from repositories.availability import AvailabilityRepository
|
||||
from repositories.exchange import ExchangeRepository
|
||||
|
||||
availability_repo = AvailabilityRepository(db)
|
||||
availabilities = await availability_repo.get_by_date(date_param)
|
||||
|
||||
if not availabilities:
|
||||
return AvailableSlotsResponse(date=date_param, slots=[])
|
||||
|
||||
# Get already booked slots for the date
|
||||
date_start = datetime.combine(date_param, time.min, tzinfo=UTC)
|
||||
date_end = datetime.combine(date_param, time.max, tzinfo=UTC)
|
||||
|
||||
result = await db.execute(
|
||||
select(Exchange.slot_start).where(
|
||||
and_(
|
||||
Exchange.slot_start >= date_start,
|
||||
Exchange.slot_start <= date_end,
|
||||
Exchange.status == ExchangeStatus.BOOKED,
|
||||
)
|
||||
)
|
||||
)
|
||||
booked_starts = {row[0] for row in result.all()}
|
||||
exchange_repo = ExchangeRepository(db)
|
||||
booked_starts = await exchange_repo.get_booked_slots_for_date(date_param)
|
||||
|
||||
# Expand each availability into slots
|
||||
all_slots: list[BookableSlot] = []
|
||||
|
|
@ -247,21 +235,16 @@ async def create_exchange(
|
|||
- EUR amount is within configured limits
|
||||
"""
|
||||
# Validate direction
|
||||
try:
|
||||
direction = TradeDirection(request.direction)
|
||||
except ValueError:
|
||||
raise BadRequestError(
|
||||
f"Invalid direction: {request.direction}. Must be 'buy' or 'sell'."
|
||||
) from None
|
||||
direction: TradeDirection = validate_enum(
|
||||
TradeDirection, request.direction, "direction"
|
||||
)
|
||||
|
||||
# Validate bitcoin transfer method
|
||||
try:
|
||||
bitcoin_transfer_method = BitcoinTransferMethod(request.bitcoin_transfer_method)
|
||||
except ValueError:
|
||||
raise BadRequestError(
|
||||
f"Invalid bitcoin_transfer_method: {request.bitcoin_transfer_method}. "
|
||||
"Must be 'onchain' or 'lightning'."
|
||||
) from None
|
||||
bitcoin_transfer_method: BitcoinTransferMethod = validate_enum(
|
||||
BitcoinTransferMethod,
|
||||
request.bitcoin_transfer_method,
|
||||
"bitcoin_transfer_method",
|
||||
)
|
||||
|
||||
# Use service to create exchange (handles all validation)
|
||||
service = ExchangeService(db)
|
||||
|
|
@ -289,12 +272,8 @@ async def get_my_trades(
|
|||
current_user: User = Depends(require_permission(Permission.VIEW_OWN_EXCHANGES)),
|
||||
) -> list[ExchangeResponse]:
|
||||
"""Get the current user's exchanges, sorted by date (newest first)."""
|
||||
result = await db.execute(
|
||||
select(Exchange)
|
||||
.where(Exchange.user_id == current_user.id)
|
||||
.order_by(Exchange.slot_start.desc())
|
||||
)
|
||||
exchanges = result.scalars().all()
|
||||
exchange_repo = ExchangeRepository(db)
|
||||
exchanges = await exchange_repo.get_by_user_id(current_user.id, order_by_desc=True)
|
||||
|
||||
return [ExchangeMapper.to_response(ex, current_user.email) for ex in exchanges]
|
||||
|
||||
|
|
@ -348,19 +327,8 @@ async def get_upcoming_trades(
|
|||
_current_user: User = Depends(require_permission(Permission.VIEW_ALL_EXCHANGES)),
|
||||
) -> list[AdminExchangeResponse]:
|
||||
"""Get all upcoming booked trades, sorted by slot time ascending."""
|
||||
now = datetime.now(UTC)
|
||||
result = await db.execute(
|
||||
select(Exchange)
|
||||
.options(joinedload(Exchange.user))
|
||||
.where(
|
||||
and_(
|
||||
Exchange.slot_start > now,
|
||||
Exchange.status == ExchangeStatus.BOOKED,
|
||||
)
|
||||
)
|
||||
.order_by(Exchange.slot_start.asc())
|
||||
)
|
||||
exchanges = result.scalars().all()
|
||||
exchange_repo = ExchangeRepository(db)
|
||||
exchanges = await exchange_repo.get_upcoming_booked()
|
||||
|
||||
return [ExchangeMapper.to_admin_response(ex) for ex in exchanges]
|
||||
|
||||
|
|
@ -383,45 +351,19 @@ async def get_past_trades(
|
|||
- user_search: Search by user email (partial match)
|
||||
"""
|
||||
|
||||
now = datetime.now(UTC)
|
||||
|
||||
# Start with base query for past trades (slot_start <= now OR not booked)
|
||||
query = (
|
||||
select(Exchange)
|
||||
.options(joinedload(Exchange.user))
|
||||
.where(
|
||||
(Exchange.slot_start <= now) | (Exchange.status != ExchangeStatus.BOOKED)
|
||||
)
|
||||
)
|
||||
|
||||
# Apply status filter
|
||||
status_enum: ExchangeStatus | None = None
|
||||
if status:
|
||||
try:
|
||||
status_enum = ExchangeStatus(status)
|
||||
query = query.where(Exchange.status == status_enum)
|
||||
except ValueError:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Invalid status: {status}",
|
||||
) from None
|
||||
status_enum = validate_enum(ExchangeStatus, status, "status")
|
||||
|
||||
# Apply date range filter
|
||||
if start_date:
|
||||
start_dt = datetime.combine(start_date, time.min, tzinfo=UTC)
|
||||
query = query.where(Exchange.slot_start >= start_dt)
|
||||
if end_date:
|
||||
end_dt = datetime.combine(end_date, time.max, tzinfo=UTC)
|
||||
query = query.where(Exchange.slot_start <= end_dt)
|
||||
|
||||
# Apply user search filter (join with User table)
|
||||
if user_search:
|
||||
query = query.join(Exchange.user).where(User.email.ilike(f"%{user_search}%"))
|
||||
|
||||
# Order by most recent first
|
||||
query = query.order_by(Exchange.slot_start.desc())
|
||||
|
||||
result = await db.execute(query)
|
||||
exchanges = result.scalars().all()
|
||||
# Use repository for query
|
||||
exchange_repo = ExchangeRepository(db)
|
||||
exchanges = await exchange_repo.get_past_trades(
|
||||
status=status_enum,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
user_search=user_search,
|
||||
)
|
||||
|
||||
return [ExchangeMapper.to_admin_response(ex) for ex in exchanges]
|
||||
|
||||
|
|
@ -487,6 +429,10 @@ async def search_users(
|
|||
Returns users whose email contains the search query (case-insensitive).
|
||||
Limited to 10 results for autocomplete purposes.
|
||||
"""
|
||||
# Note: UserRepository doesn't have search yet, but we can add it
|
||||
# For now, keeping direct query for this specific use case
|
||||
from sqlalchemy import select
|
||||
|
||||
result = await db.execute(
|
||||
select(User).where(User.email.ilike(f"%{q}%")).order_by(User.email).limit(10)
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue