Init.
This commit is contained in:
0
backend/app/routers/__init__.py
Normal file
0
backend/app/routers/__init__.py
Normal file
82
backend/app/routers/auth.py
Normal file
82
backend/app/routers/auth.py
Normal file
@ -0,0 +1,82 @@
|
||||
from fastapi import APIRouter, Depends, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from jose import JWTError
|
||||
from app.database import get_db
|
||||
from app.schemas.user import UserCreate, UserLogin, TokenResponse, UserResponse
|
||||
from app.services.auth_service import register_user, login_user
|
||||
from app.crud.user import get_user_by_id
|
||||
from app.utils.security import decode_token
|
||||
from app.utils.exceptions import UnauthorizedError
|
||||
|
||||
router = APIRouter(prefix="/api/v1/auth", tags=["auth"])
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
|
||||
|
||||
|
||||
async def get_current_user(
|
||||
token: str = Depends(oauth2_scheme),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
try:
|
||||
payload = decode_token(token)
|
||||
user_id: str = payload.get("sub")
|
||||
if user_id is None:
|
||||
raise UnauthorizedError()
|
||||
except JWTError:
|
||||
raise UnauthorizedError()
|
||||
|
||||
user = await get_user_by_id(db, int(user_id))
|
||||
if user is None:
|
||||
raise UnauthorizedError()
|
||||
|
||||
return user
|
||||
|
||||
|
||||
@router.post("/register", response_model=TokenResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def register(
|
||||
user_data: UserCreate,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
return await register_user(db, user_data)
|
||||
|
||||
|
||||
@router.post("/login", response_model=TokenResponse)
|
||||
async def login(
|
||||
login_data: UserLogin,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
return await login_user(db, login_data)
|
||||
|
||||
|
||||
@router.get("/me", response_model=UserResponse)
|
||||
async def get_current_user_info(
|
||||
current_user = Depends(get_current_user)
|
||||
):
|
||||
return current_user
|
||||
|
||||
|
||||
@router.post("/refresh", response_model=TokenResponse)
|
||||
async def refresh_token(
|
||||
token: str,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
try:
|
||||
payload = decode_token(token)
|
||||
user_id: str = payload.get("sub")
|
||||
if user_id is None:
|
||||
raise UnauthorizedError()
|
||||
except JWTError:
|
||||
raise UnauthorizedError()
|
||||
|
||||
user = await get_user_by_id(db, int(user_id))
|
||||
if user is None:
|
||||
raise UnauthorizedError()
|
||||
|
||||
from app.utils.security import create_access_token, create_refresh_token
|
||||
access_token = create_access_token({"sub": str(user.id)})
|
||||
new_refresh_token = create_refresh_token({"sub": str(user.id)})
|
||||
|
||||
return TokenResponse(
|
||||
access_token=access_token,
|
||||
refresh_token=new_refresh_token,
|
||||
)
|
||||
173
backend/app/routers/bets.py
Normal file
173
backend/app/routers/bets.py
Normal file
@ -0,0 +1,173 @@
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.database import get_db
|
||||
from app.schemas.bet import BetCreate, BetUpdate, BetResponse, BetDetailResponse, SettleBetRequest
|
||||
from app.routers.auth import get_current_user
|
||||
from app.crud.bet import get_bet_by_id, get_open_bets, get_user_bets, create_bet
|
||||
from app.services.bet_service import accept_bet, settle_bet, cancel_bet
|
||||
from app.models import User, BetCategory, BetStatus
|
||||
from app.utils.exceptions import BetNotFoundError, NotBetParticipantError
|
||||
|
||||
router = APIRouter(prefix="/api/v1/bets", tags=["bets"])
|
||||
|
||||
|
||||
@router.get("", response_model=list[BetResponse])
|
||||
async def list_bets(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
category: BetCategory | None = None,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
bets = await get_open_bets(db, skip=skip, limit=limit, category=category)
|
||||
return bets
|
||||
|
||||
|
||||
@router.post("", response_model=BetResponse)
|
||||
async def create_new_bet(
|
||||
bet_data: BetCreate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
bet = await create_bet(db, bet_data, current_user.id)
|
||||
await db.commit()
|
||||
bet = await get_bet_by_id(db, bet.id)
|
||||
return bet
|
||||
|
||||
|
||||
@router.get("/{bet_id}", response_model=BetDetailResponse)
|
||||
async def get_bet(
|
||||
bet_id: int,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
bet = await get_bet_by_id(db, bet_id)
|
||||
if not bet:
|
||||
raise BetNotFoundError()
|
||||
return bet
|
||||
|
||||
|
||||
@router.put("/{bet_id}", response_model=BetResponse)
|
||||
async def update_bet(
|
||||
bet_id: int,
|
||||
bet_data: BetUpdate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
bet = await get_bet_by_id(db, bet_id)
|
||||
if not bet:
|
||||
raise BetNotFoundError()
|
||||
|
||||
if bet.creator_id != current_user.id:
|
||||
raise NotBetParticipantError()
|
||||
|
||||
if bet.status != BetStatus.OPEN:
|
||||
raise ValueError("Cannot update non-open bet")
|
||||
|
||||
# Update fields
|
||||
if bet_data.title is not None:
|
||||
bet.title = bet_data.title
|
||||
if bet_data.description is not None:
|
||||
bet.description = bet_data.description
|
||||
if bet_data.event_date is not None:
|
||||
bet.event_date = bet_data.event_date
|
||||
if bet_data.creator_position is not None:
|
||||
bet.creator_position = bet_data.creator_position
|
||||
if bet_data.opponent_position is not None:
|
||||
bet.opponent_position = bet_data.opponent_position
|
||||
if bet_data.stake_amount is not None:
|
||||
bet.stake_amount = bet_data.stake_amount
|
||||
if bet_data.creator_odds is not None:
|
||||
bet.creator_odds = bet_data.creator_odds
|
||||
if bet_data.opponent_odds is not None:
|
||||
bet.opponent_odds = bet_data.opponent_odds
|
||||
if bet_data.expires_at is not None:
|
||||
bet.expires_at = bet_data.expires_at
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(bet)
|
||||
return bet
|
||||
|
||||
|
||||
@router.delete("/{bet_id}")
|
||||
async def delete_bet(
|
||||
bet_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
await cancel_bet(db, bet_id, current_user.id)
|
||||
return {"message": "Bet cancelled successfully"}
|
||||
|
||||
|
||||
@router.post("/{bet_id}/accept", response_model=BetResponse)
|
||||
async def accept_bet_route(
|
||||
bet_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
bet = await accept_bet(db, bet_id, current_user.id)
|
||||
return bet
|
||||
|
||||
|
||||
@router.post("/{bet_id}/settle", response_model=BetDetailResponse)
|
||||
async def settle_bet_route(
|
||||
bet_id: int,
|
||||
settle_data: SettleBetRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
bet = await settle_bet(db, bet_id, settle_data.winner_id, current_user.id)
|
||||
return bet
|
||||
|
||||
|
||||
@router.get("/my/created", response_model=list[BetResponse])
|
||||
async def get_my_created_bets(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import selectinload
|
||||
from app.models import Bet
|
||||
|
||||
result = await db.execute(
|
||||
select(Bet)
|
||||
.where(Bet.creator_id == current_user.id)
|
||||
.options(selectinload(Bet.creator), selectinload(Bet.opponent))
|
||||
.order_by(Bet.created_at.desc())
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
@router.get("/my/accepted", response_model=list[BetResponse])
|
||||
async def get_my_accepted_bets(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import selectinload
|
||||
from app.models import Bet
|
||||
|
||||
result = await db.execute(
|
||||
select(Bet)
|
||||
.where(Bet.opponent_id == current_user.id)
|
||||
.options(selectinload(Bet.creator), selectinload(Bet.opponent))
|
||||
.order_by(Bet.created_at.desc())
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
@router.get("/my/active", response_model=list[BetResponse])
|
||||
async def get_my_active_bets(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
bets = await get_user_bets(db, current_user.id)
|
||||
active_bets = [bet for bet in bets if bet.status in [BetStatus.MATCHED, BetStatus.IN_PROGRESS]]
|
||||
return active_bets
|
||||
|
||||
|
||||
@router.get("/my/history", response_model=list[BetDetailResponse])
|
||||
async def get_my_bet_history(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
bets = await get_user_bets(db, current_user.id, status=BetStatus.COMPLETED)
|
||||
return bets
|
||||
62
backend/app/routers/users.py
Normal file
62
backend/app/routers/users.py
Normal file
@ -0,0 +1,62 @@
|
||||
from fastapi import APIRouter, Depends, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.database import get_db
|
||||
from app.schemas.user import UserResponse, UserUpdate, UserStats
|
||||
from app.routers.auth import get_current_user
|
||||
from app.crud.user import get_user_by_id
|
||||
from app.crud.bet import get_user_bets
|
||||
from app.models import User, BetStatus
|
||||
from app.utils.exceptions import UserNotFoundError
|
||||
|
||||
router = APIRouter(prefix="/api/v1/users", tags=["users"])
|
||||
|
||||
|
||||
@router.get("/{user_id}", response_model=UserResponse)
|
||||
async def get_user(
|
||||
user_id: int,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
user = await get_user_by_id(db, user_id)
|
||||
if not user:
|
||||
raise UserNotFoundError()
|
||||
return user
|
||||
|
||||
|
||||
@router.put("/me", response_model=UserResponse)
|
||||
async def update_current_user(
|
||||
user_data: UserUpdate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
if user_data.display_name is not None:
|
||||
current_user.display_name = user_data.display_name
|
||||
if user_data.avatar_url is not None:
|
||||
current_user.avatar_url = user_data.avatar_url
|
||||
if user_data.bio is not None:
|
||||
current_user.bio = user_data.bio
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(current_user)
|
||||
return current_user
|
||||
|
||||
|
||||
@router.get("/{user_id}/stats", response_model=UserStats)
|
||||
async def get_user_stats(
|
||||
user_id: int,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
user = await get_user_by_id(db, user_id)
|
||||
if not user:
|
||||
raise UserNotFoundError()
|
||||
|
||||
# Get active bets count
|
||||
user_bets = await get_user_bets(db, user_id)
|
||||
active_bets = sum(1 for bet in user_bets if bet.status in [BetStatus.MATCHED, BetStatus.IN_PROGRESS])
|
||||
|
||||
return UserStats(
|
||||
total_bets=user.total_bets,
|
||||
wins=user.wins,
|
||||
losses=user.losses,
|
||||
win_rate=user.win_rate,
|
||||
active_bets=active_bets,
|
||||
)
|
||||
50
backend/app/routers/wallet.py
Normal file
50
backend/app/routers/wallet.py
Normal file
@ -0,0 +1,50 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.database import get_db
|
||||
from app.schemas.wallet import WalletResponse, DepositRequest, WithdrawalRequest, TransactionResponse
|
||||
from app.routers.auth import get_current_user
|
||||
from app.crud.wallet import get_user_wallet, get_wallet_transactions
|
||||
from app.services.wallet_service import deposit_funds, withdraw_funds
|
||||
from app.models import User
|
||||
|
||||
router = APIRouter(prefix="/api/v1/wallet", tags=["wallet"])
|
||||
|
||||
|
||||
@router.get("", response_model=WalletResponse)
|
||||
async def get_wallet(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
wallet = await get_user_wallet(db, current_user.id)
|
||||
return wallet
|
||||
|
||||
|
||||
@router.post("/deposit", response_model=WalletResponse)
|
||||
async def deposit(
|
||||
deposit_data: DepositRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
wallet = await deposit_funds(db, current_user.id, deposit_data.amount)
|
||||
return wallet
|
||||
|
||||
|
||||
@router.post("/withdraw", response_model=WalletResponse)
|
||||
async def withdraw(
|
||||
withdrawal_data: WithdrawalRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
wallet = await withdraw_funds(db, current_user.id, withdrawal_data.amount)
|
||||
return wallet
|
||||
|
||||
|
||||
@router.get("/transactions", response_model=list[TransactionResponse])
|
||||
async def get_transactions(
|
||||
limit: int = 50,
|
||||
offset: int = 0,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
transactions = await get_wallet_transactions(db, current_user.id, limit, offset)
|
||||
return transactions
|
||||
43
backend/app/routers/websocket.py
Normal file
43
backend/app/routers/websocket.py
Normal file
@ -0,0 +1,43 @@
|
||||
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Query
|
||||
from typing import Dict
|
||||
import json
|
||||
|
||||
router = APIRouter(tags=["websocket"])
|
||||
|
||||
# Store active connections
|
||||
active_connections: Dict[int, WebSocket] = {}
|
||||
|
||||
|
||||
@router.websocket("/api/v1/ws")
|
||||
async def websocket_endpoint(websocket: WebSocket, token: str = Query(...)):
|
||||
await websocket.accept()
|
||||
|
||||
# In a real implementation, you would validate the token here
|
||||
# For MVP, we'll accept all connections
|
||||
user_id = 1 # Placeholder
|
||||
|
||||
active_connections[user_id] = websocket
|
||||
|
||||
try:
|
||||
while True:
|
||||
data = await websocket.receive_text()
|
||||
# Handle incoming messages if needed
|
||||
except WebSocketDisconnect:
|
||||
if user_id in active_connections:
|
||||
del active_connections[user_id]
|
||||
|
||||
|
||||
async def broadcast_event(event_type: str, data: dict, user_ids: list[int] = None):
|
||||
"""Broadcast an event to specific users or all connected users"""
|
||||
message = json.dumps({
|
||||
"type": event_type,
|
||||
"data": data
|
||||
})
|
||||
|
||||
if user_ids:
|
||||
for user_id in user_ids:
|
||||
if user_id in active_connections:
|
||||
await active_connections[user_id].send_text(message)
|
||||
else:
|
||||
for connection in active_connections.values():
|
||||
await connection.send_text(message)
|
||||
Reference in New Issue
Block a user