114 lines
3.1 KiB
Python
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,
|
|
)
|
|
|