Files
midas/backend/app/routers/bets.py

114 lines
4.4 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, desc
from pydantic import BaseModel
from decimal import Decimal
from typing import Optional
from datetime import datetime
from app.database import get_db
from app.models.user import User
from app.models.bet import Bet
from app.models.bankroll import Bankroll
from app.utils.auth import get_current_user
from app.services.gamification import check_achievements
router = APIRouter(prefix="/api/bets", tags=["bets"])
class BetCreate(BaseModel):
sport: str
event_name: str
platform: str = "Bet365"
amount: float
odds: float
bet_type: str = ""
emotion: str = ""
is_impulsive: bool = False
class BetResult(BaseModel):
result: str # win, loss, void
@router.post("")
async def create_bet(req: BetCreate, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
bet = Bet(
user_id=user.id, sport=req.sport, event_name=req.event_name,
platform=req.platform, amount=req.amount, odds=req.odds,
bet_type=req.bet_type, emotion=req.emotion, is_impulsive=req.is_impulsive,
result="pending"
)
db.add(bet)
# Update bankroll spending
br = (await db.execute(select(Bankroll).where(Bankroll.user_id == user.id))).scalar_one_or_none()
if br:
br.month_spent = float(br.month_spent or 0) + req.amount
br.week_spent = float(br.week_spent or 0) + req.amount
br.day_spent = float(br.day_spent or 0) + req.amount
await db.commit()
await db.refresh(bet)
await check_achievements(user.id, db)
return {"id": str(bet.id), "status": "created"}
@router.get("")
async def list_bets(
sport: Optional[str] = None,
result: Optional[str] = None,
limit: int = Query(50, le=200),
offset: int = 0,
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
q = select(Bet).where(Bet.user_id == user.id)
if sport:
q = q.where(Bet.sport == sport)
if result:
q = q.where(Bet.result == result)
q = q.order_by(desc(Bet.created_at)).offset(offset).limit(limit)
rows = (await db.execute(q)).scalars().all()
return [
{"id": str(b.id), "sport": b.sport, "event_name": b.event_name, "platform": b.platform,
"amount": float(b.amount), "odds": float(b.odds) if b.odds else None, "result": b.result,
"profit": float(b.profit) if b.profit else 0, "emotion": b.emotion,
"is_impulsive": b.is_impulsive, "created_at": b.created_at.isoformat()}
for b in rows
]
@router.patch("/{bet_id}/result")
async def update_result(bet_id: str, req: BetResult, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
bet = (await db.execute(select(Bet).where(Bet.id == bet_id, Bet.user_id == user.id))).scalar_one_or_none()
if not bet:
raise HTTPException(404, "Bet not found")
bet.result = req.result
if req.result == "win":
bet.profit = float(bet.amount) * (float(bet.odds) - 1)
elif req.result == "loss":
bet.profit = -float(bet.amount)
else:
bet.profit = 0
await db.commit()
return {"id": str(bet.id), "result": bet.result, "profit": float(bet.profit)}
@router.get("/stats")
async def bet_stats(user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
bets = (await db.execute(select(Bet).where(Bet.user_id == user.id))).scalars().all()
total = len(bets)
wins = sum(1 for b in bets if b.result == "win")
losses = sum(1 for b in bets if b.result == "loss")
pending = sum(1 for b in bets if b.result == "pending")
total_profit = sum(float(b.profit or 0) for b in bets)
total_staked = sum(float(b.amount) for b in bets)
win_rate = (wins / (wins + losses) * 100) if (wins + losses) > 0 else 0
roi = (total_profit / total_staked * 100) if total_staked > 0 else 0
by_sport = {}
for b in bets:
s = b.sport or "other"
if s not in by_sport:
by_sport[s] = {"count": 0, "profit": 0}
by_sport[s]["count"] += 1
by_sport[s]["profit"] += float(b.profit or 0)
return {
"total": total, "wins": wins, "losses": losses, "pending": pending,
"total_profit": round(total_profit, 2), "total_staked": round(total_staked, 2),
"win_rate": round(win_rate, 1), "roi": round(roi, 1), "by_sport": by_sport
}