309 lines
9.4 KiB
Python

from typing import Any, List
from datetime import datetime, timedelta
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Form
from sqlalchemy.orm import Session
from app import crud, models, schemas
from app.api import deps
from app.models.transaction import TransactionType
from app.models.wallet import WalletType
from app.services.file_upload import save_bot_image
from app.core.email import send_bot_purchase_confirmation
router = APIRouter()
@router.get("/", response_model=List[schemas.Bot])
def read_bots(
*,
db: Session = Depends(deps.get_db),
current_user: models.User = Depends(deps.get_current_active_verified_user),
skip: int = 0,
limit: int = 100,
) -> Any:
"""
Retrieve available bots.
"""
bots = crud.bot.get_active(db, skip=skip, limit=limit)
return bots
@router.get("/{bot_id}", response_model=schemas.Bot)
def read_bot(
*,
db: Session = Depends(deps.get_db),
current_user: models.User = Depends(deps.get_current_active_verified_user),
bot_id: int,
) -> Any:
"""
Get a specific bot by ID.
"""
bot = crud.bot.get(db, id=bot_id)
if not bot:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Bot not found",
)
if not bot.is_active and current_user.role != "admin":
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Bot not found or not active",
)
return bot
@router.post("/{bot_id}/purchase", response_model=schemas.BotPurchase)
def purchase_bot(
*,
db: Session = Depends(deps.get_db),
current_user: models.User = Depends(deps.get_current_active_verified_user),
bot_id: int,
purchase_data: schemas.BotPurchaseRequest,
) -> Any:
"""
Purchase a bot.
"""
bot = crud.bot.get(db, id=bot_id)
if not bot:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Bot not found",
)
if not bot.is_active:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Bot is not active",
)
# Validate purchase amount
if purchase_data.amount < bot.min_purchase_amount:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Minimum purchase amount is {bot.min_purchase_amount} USDT",
)
if purchase_data.amount > bot.max_purchase_amount:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Maximum purchase amount is {bot.max_purchase_amount} USDT",
)
# Get user's trading wallet
trading_wallet = crud.wallet.get_by_user_and_type(
db, user_id=current_user.id, wallet_type=WalletType.TRADING
)
if not trading_wallet:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Trading wallet not found",
)
# Check if user has enough balance
if trading_wallet.balance < purchase_data.amount:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Insufficient funds in trading wallet",
)
# Calculate expected ROI
expected_roi_amount = purchase_data.amount * (bot.roi_percentage / 100)
# Calculate end time
start_time = datetime.utcnow()
end_time = start_time + timedelta(hours=bot.duration_hours)
# Create bot purchase
bot_purchase_in = schemas.BotPurchaseCreate(
user_id=current_user.id,
bot_id=bot.id,
amount=purchase_data.amount,
expected_roi_amount=expected_roi_amount,
start_time=start_time,
end_time=end_time,
status=schemas.BotPurchaseStatus.RUNNING,
)
bot_purchase = crud.bot_purchase.create(db, obj_in=bot_purchase_in)
# Deduct amount from trading wallet
crud.wallet.update_balance(db, wallet_id=trading_wallet.id, amount=purchase_data.amount, add=False)
# Create transaction record
crud.transaction.create(
db,
obj_in=schemas.TransactionCreate(
user_id=current_user.id,
wallet_id=trading_wallet.id,
amount=-purchase_data.amount,
transaction_type=TransactionType.BOT_PURCHASE,
description=f"Bot purchase - {bot.name}",
bot_purchase_id=bot_purchase.id,
),
)
# Send confirmation email
send_bot_purchase_confirmation(
email_to=current_user.email,
bot_name=bot.name,
amount=purchase_data.amount,
expected_roi=expected_roi_amount,
end_date=end_time.strftime("%Y-%m-%d %H:%M:%S UTC"),
)
return bot_purchase
@router.get("/purchased", response_model=List[schemas.BotPurchase])
def read_purchased_bots(
*,
db: Session = Depends(deps.get_db),
current_user: models.User = Depends(deps.get_current_active_verified_user),
skip: int = 0,
limit: int = 100,
) -> Any:
"""
Retrieve user's purchased bots.
"""
bot_purchases = crud.bot_purchase.get_by_user(db, user_id=current_user.id, skip=skip, limit=limit)
return bot_purchases
# Admin endpoints
@router.post("/admin", response_model=schemas.Bot)
async def create_bot(
*,
db: Session = Depends(deps.get_db),
current_user: models.User = Depends(deps.get_current_admin),
name: str = Form(...),
description: str = Form(None),
roi_percentage: float = Form(...),
duration_hours: int = Form(...),
min_purchase_amount: float = Form(...),
max_purchase_amount: float = Form(...),
is_active: bool = Form(True),
image: UploadFile = File(None),
) -> Any:
"""
Create a new bot (admin only).
"""
# Check if bot with same name already exists
existing_bot = crud.bot.get_by_name(db, name=name)
if existing_bot:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Bot with this name already exists",
)
# Create bot
bot_in = schemas.BotCreate(
name=name,
description=description,
roi_percentage=roi_percentage,
duration_hours=duration_hours,
min_purchase_amount=min_purchase_amount,
max_purchase_amount=max_purchase_amount,
is_active=is_active,
)
bot = crud.bot.create(db, obj_in=bot_in)
# Save image if provided
if image:
image_path = save_bot_image(bot.id, image)
crud.bot.update(db, db_obj=bot, obj_in={"image_path": image_path})
bot.image_path = image_path
return bot
@router.put("/admin/{bot_id}", response_model=schemas.Bot)
async def update_bot(
*,
db: Session = Depends(deps.get_db),
current_user: models.User = Depends(deps.get_current_admin),
bot_id: int,
name: str = Form(None),
description: str = Form(None),
roi_percentage: float = Form(None),
duration_hours: int = Form(None),
min_purchase_amount: float = Form(None),
max_purchase_amount: float = Form(None),
is_active: bool = Form(None),
image: UploadFile = File(None),
) -> Any:
"""
Update a bot (admin only).
"""
bot = crud.bot.get(db, id=bot_id)
if not bot:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Bot not found",
)
# Check if name is being changed and if it conflicts with existing bot
if name and name != bot.name:
existing_bot = crud.bot.get_by_name(db, name=name)
if existing_bot and existing_bot.id != bot_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Bot with this name already exists",
)
# Update bot data
update_data = {}
if name is not None:
update_data["name"] = name
if description is not None:
update_data["description"] = description
if roi_percentage is not None:
update_data["roi_percentage"] = roi_percentage
if duration_hours is not None:
update_data["duration_hours"] = duration_hours
if min_purchase_amount is not None:
update_data["min_purchase_amount"] = min_purchase_amount
if max_purchase_amount is not None:
update_data["max_purchase_amount"] = max_purchase_amount
if is_active is not None:
update_data["is_active"] = is_active
# Save image if provided
if image:
image_path = save_bot_image(bot.id, image)
update_data["image_path"] = image_path
bot = crud.bot.update(db, db_obj=bot, obj_in=update_data)
return bot
@router.delete("/admin/{bot_id}", response_model=schemas.Bot)
def delete_bot(
*,
db: Session = Depends(deps.get_db),
current_user: models.User = Depends(deps.get_current_admin),
bot_id: int,
) -> Any:
"""
Delete a bot (admin only).
"""
bot = crud.bot.get(db, id=bot_id)
if not bot:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Bot not found",
)
# Check if bot has any active purchases
active_purchases = crud.bot_purchase.get_by_bot(db, bot_id=bot_id)
active_purchases = [p for p in active_purchases if p.status == "running"]
if active_purchases:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Cannot delete bot with active purchases",
)
bot = crud.bot.remove(db, id=bot_id)
return bot