Files
modbison/backend/app/routers/wallet.py
T
2026-09-18 01:04:36 +00:00

114 lines
3.1 KiB
Python

"""Wallet & transactions API endpoints.
docs/planning/economy.md US-1 (task 909e3e9a59af).
"""
from datetime import datetime
from typing import Annotated, Any
from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.dependencies import CurrentUser
from app.models.wallet import TransactionType
from app.services.wallet import wallet_service
router = APIRouter()
DbDep = Annotated[AsyncSession, Depends(get_db)]
class WalletResponse(BaseModel):
id: int
user_id: int
balance: int
created_at: datetime
updated_at: datetime
model_config = {"from_attributes": True}
class TransactionResponse(BaseModel):
id: int
wallet_id: int
type: TransactionType
amount: int
balance_after: int
reference_type: str | None = None
reference_id: str | None = None
idempotency_key: str | None = None
metadata_json: dict[str, Any] | None = None
created_at: datetime
model_config = {"from_attributes": True}
class TransactionListResponse(BaseModel):
transactions: list[TransactionResponse]
total: int
limit: int
offset: int
@router.get("", response_model=WalletResponse)
@router.get("/", response_model=WalletResponse)
async def get_wallet(current_user: CurrentUser, db: DbDep):
"""Retrieve the current user's wallet balance and metadata."""
wallet = await wallet_service.get_or_create_wallet(db, current_user.id)
return wallet
@router.get("/transactions", response_model=TransactionListResponse)
async def get_wallet_transactions(
current_user: CurrentUser,
db: DbDep,
limit: int = Query(50, ge=1, le=100),
offset: int = Query(0, ge=0),
):
"""List the current user's ledger transactions, ordered newest-first."""
wallet = await wallet_service.get_or_create_wallet(db, current_user.id)
txs, total = await wallet_service.get_transactions(db, wallet.id, limit=limit, offset=offset)
return TransactionListResponse(
transactions=txs,
total=total,
limit=limit,
offset=offset,
)
class RewardRequest(BaseModel):
amount: int = Field(..., gt=0, le=10000, description="Amount of Buffalo Chips to reward")
reason: str | None = Field(default="in_game_reward", description="Reward source or reason")
class RewardResponse(BaseModel):
success: bool
amount: int
new_balance: int
@router.post("/reward", response_model=RewardResponse)
async def reward_chips(
payload: RewardRequest,
current_user: CurrentUser,
db: DbDep,
):
"""Credit in-game Buffalo Chips reward directly to the player's wallet."""
wallet, tx = await wallet_service.credit(
db,
user_id=current_user.id,
amount=payload.amount,
tx_type=TransactionType.admin_adjustment,
reference_type="gameplay_reward",
reference_id=payload.reason or "in_game_reward",
metadata={"reason": payload.reason or "in_game_reward"},
)
return RewardResponse(
success=True,
amount=payload.amount,
new_balance=wallet.balance,
)