lots of stuff
This commit is contained in:
parent
f946fbf7b8
commit
4be45f8f7c
9 changed files with 513 additions and 236 deletions
|
|
@ -1,5 +1,6 @@
|
|||
"""Exchange routes for Bitcoin trading."""
|
||||
|
||||
import uuid
|
||||
from datetime import UTC, date, datetime, time, timedelta
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
|
|
@ -167,6 +168,7 @@ def _to_exchange_response(
|
|||
email = user_email if user_email is not None else exchange.user.email
|
||||
return ExchangeResponse(
|
||||
id=exchange.id,
|
||||
public_id=str(exchange.public_id),
|
||||
user_id=exchange.user_id,
|
||||
user_email=email,
|
||||
slot_start=exchange.slot_start,
|
||||
|
|
@ -396,7 +398,8 @@ async def create_exchange(
|
|||
status_code=400,
|
||||
detail=(
|
||||
f"You already have a trade booked on {slot_date.strftime('%Y-%m-%d')}. "
|
||||
f"Only one trade per day is allowed. Trade ID: {existing_trade.id}"
|
||||
f"Only one trade per day is allowed. "
|
||||
f"Trade ID: {existing_trade.public_id}"
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -512,6 +515,26 @@ async def create_exchange(
|
|||
# Calculate sats amount based on agreed price
|
||||
sats_amount = calculate_sats_amount(request.eur_amount, agreed_price)
|
||||
|
||||
# Check if slot is already booked (only consider BOOKED status, not cancelled)
|
||||
slot_booked_query = select(Exchange).where(
|
||||
and_(
|
||||
Exchange.slot_start == request.slot_start,
|
||||
Exchange.status == ExchangeStatus.BOOKED,
|
||||
)
|
||||
)
|
||||
slot_booked_result = await db.execute(slot_booked_query)
|
||||
slot_booked = slot_booked_result.scalar_one_or_none()
|
||||
|
||||
if slot_booked:
|
||||
slot_str = request.slot_start.strftime("%Y-%m-%d %H:%M")
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail=(
|
||||
f"This slot at {slot_str} UTC has already been booked. "
|
||||
"Select another slot."
|
||||
),
|
||||
)
|
||||
|
||||
# Create the exchange
|
||||
exchange = Exchange(
|
||||
user_id=current_user.id,
|
||||
|
|
@ -532,12 +555,14 @@ async def create_exchange(
|
|||
try:
|
||||
await db.commit()
|
||||
await db.refresh(exchange)
|
||||
except IntegrityError:
|
||||
except IntegrityError as e:
|
||||
await db.rollback()
|
||||
# This should rarely happen now since we check explicitly above,
|
||||
# but keep it for other potential integrity violations
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="This slot has already been booked. Select another slot.",
|
||||
) from None
|
||||
detail="Database constraint violation. Please try again.",
|
||||
) from e
|
||||
|
||||
return _to_exchange_response(exchange, current_user.email)
|
||||
|
||||
|
|
@ -565,16 +590,16 @@ async def get_my_trades(
|
|||
return [_to_exchange_response(ex, current_user.email) for ex in exchanges]
|
||||
|
||||
|
||||
@trades_router.get("/{exchange_id}", response_model=ExchangeResponse)
|
||||
@trades_router.get("/{public_id}", response_model=ExchangeResponse)
|
||||
async def get_my_trade(
|
||||
exchange_id: int,
|
||||
public_id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_permission(Permission.VIEW_OWN_EXCHANGES)),
|
||||
) -> ExchangeResponse:
|
||||
"""Get a specific trade by ID. User can only access their own trades."""
|
||||
"""Get a specific trade by public ID. User can only access their own trades."""
|
||||
result = await db.execute(
|
||||
select(Exchange).where(
|
||||
and_(Exchange.id == exchange_id, Exchange.user_id == current_user.id)
|
||||
and_(Exchange.public_id == public_id, Exchange.user_id == current_user.id)
|
||||
)
|
||||
)
|
||||
exchange = result.scalar_one_or_none()
|
||||
|
|
@ -588,9 +613,9 @@ async def get_my_trade(
|
|||
return _to_exchange_response(exchange, current_user.email)
|
||||
|
||||
|
||||
@trades_router.post("/{exchange_id}/cancel", response_model=ExchangeResponse)
|
||||
@trades_router.post("/{public_id}/cancel", response_model=ExchangeResponse)
|
||||
async def cancel_my_trade(
|
||||
exchange_id: int,
|
||||
public_id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_permission(Permission.CANCEL_OWN_EXCHANGE)),
|
||||
) -> ExchangeResponse:
|
||||
|
|
@ -599,14 +624,14 @@ async def cancel_my_trade(
|
|||
result = await db.execute(
|
||||
select(Exchange)
|
||||
.options(joinedload(Exchange.user))
|
||||
.where(Exchange.id == exchange_id)
|
||||
.where(Exchange.public_id == public_id)
|
||||
)
|
||||
exchange = result.scalar_one_or_none()
|
||||
|
||||
if not exchange:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Trade {exchange_id} not found",
|
||||
detail="Trade not found",
|
||||
)
|
||||
|
||||
# Verify ownership
|
||||
|
|
@ -651,6 +676,7 @@ def _to_admin_exchange_response(exchange: Exchange) -> AdminExchangeResponse:
|
|||
user = exchange.user
|
||||
return AdminExchangeResponse(
|
||||
id=exchange.id,
|
||||
public_id=str(exchange.public_id),
|
||||
user_id=exchange.user_id,
|
||||
user_email=user.email,
|
||||
user_contact=ExchangeUserContact(
|
||||
|
|
@ -760,11 +786,9 @@ async def get_past_trades(
|
|||
return [_to_admin_exchange_response(ex) for ex in exchanges]
|
||||
|
||||
|
||||
@admin_trades_router.post(
|
||||
"/{exchange_id}/complete", response_model=AdminExchangeResponse
|
||||
)
|
||||
@admin_trades_router.post("/{public_id}/complete", response_model=AdminExchangeResponse)
|
||||
async def complete_trade(
|
||||
exchange_id: int,
|
||||
public_id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_current_user: User = Depends(require_permission(Permission.COMPLETE_EXCHANGE)),
|
||||
) -> AdminExchangeResponse:
|
||||
|
|
@ -773,14 +797,14 @@ async def complete_trade(
|
|||
result = await db.execute(
|
||||
select(Exchange)
|
||||
.options(joinedload(Exchange.user))
|
||||
.where(Exchange.id == exchange_id)
|
||||
.where(Exchange.public_id == public_id)
|
||||
)
|
||||
exchange = result.scalar_one_or_none()
|
||||
|
||||
if not exchange:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Trade {exchange_id} not found",
|
||||
detail="Trade not found",
|
||||
)
|
||||
|
||||
# Check slot has passed
|
||||
|
|
@ -806,11 +830,9 @@ async def complete_trade(
|
|||
return _to_admin_exchange_response(exchange)
|
||||
|
||||
|
||||
@admin_trades_router.post(
|
||||
"/{exchange_id}/no-show", response_model=AdminExchangeResponse
|
||||
)
|
||||
@admin_trades_router.post("/{public_id}/no-show", response_model=AdminExchangeResponse)
|
||||
async def mark_no_show(
|
||||
exchange_id: int,
|
||||
public_id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_current_user: User = Depends(require_permission(Permission.COMPLETE_EXCHANGE)),
|
||||
) -> AdminExchangeResponse:
|
||||
|
|
@ -819,14 +841,14 @@ async def mark_no_show(
|
|||
result = await db.execute(
|
||||
select(Exchange)
|
||||
.options(joinedload(Exchange.user))
|
||||
.where(Exchange.id == exchange_id)
|
||||
.where(Exchange.public_id == public_id)
|
||||
)
|
||||
exchange = result.scalar_one_or_none()
|
||||
|
||||
if not exchange:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Trade {exchange_id} not found",
|
||||
detail="Trade not found",
|
||||
)
|
||||
|
||||
# Check slot has passed
|
||||
|
|
@ -852,9 +874,9 @@ async def mark_no_show(
|
|||
return _to_admin_exchange_response(exchange)
|
||||
|
||||
|
||||
@admin_trades_router.post("/{exchange_id}/cancel", response_model=AdminExchangeResponse)
|
||||
@admin_trades_router.post("/{public_id}/cancel", response_model=AdminExchangeResponse)
|
||||
async def admin_cancel_trade(
|
||||
exchange_id: int,
|
||||
public_id: uuid.UUID,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_current_user: User = Depends(require_permission(Permission.CANCEL_ANY_EXCHANGE)),
|
||||
) -> AdminExchangeResponse:
|
||||
|
|
@ -863,14 +885,14 @@ async def admin_cancel_trade(
|
|||
result = await db.execute(
|
||||
select(Exchange)
|
||||
.options(joinedload(Exchange.user))
|
||||
.where(Exchange.id == exchange_id)
|
||||
.where(Exchange.public_id == public_id)
|
||||
)
|
||||
exchange = result.scalar_one_or_none()
|
||||
|
||||
if not exchange:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Trade {exchange_id} not found",
|
||||
detail="Trade not found",
|
||||
)
|
||||
|
||||
# Check status is BOOKED
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue