This commit is contained in:
Redsandy
2026-08-12 11:26:05 +03:00
commit c9f66f330e
17 changed files with 1569 additions and 0 deletions

189
app/api/routes.py Normal file
View File

@@ -0,0 +1,189 @@
from datetime import datetime, timezone
from decimal import Decimal
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.core.security import create_access_token, decode_access_token
from app.models import Deposit, User
from app.schemas import (
DepositRequest,
DepositResponse,
DevLoginRequest,
GameStub,
TelegramAuthRequest,
TokenResponse,
UserResponse,
WalletResponse,
)
router = APIRouter()
security = HTTPBearer()
ALLOWED_DEPOSIT_AMOUNTS = {Decimal("100"), Decimal("500"), Decimal("1000")}
GAMES: list[GameStub] = [
GameStub(
slug="raven-reels",
title="Вороньи Барабаны",
description="Слоты у врат Вырия. Скоро откроются.",
),
GameStub(
slug="golden-gate",
title="Золотые Врата",
description="Портал удачи. Пока запечатан.",
),
GameStub(
slug="forest-fortune",
title="Лесная Удача",
description="Древние символы ещё не проснулись.",
),
]
def user_to_response(user: User) -> UserResponse:
return UserResponse(
id=user.id,
telegram_id=user.telegram_id,
username=user.username,
first_name=user.first_name,
balance=user.balance,
agreement_accepted=user.agreement_accepted_at is not None,
agreement_accepted_at=user.agreement_accepted_at,
created_at=user.created_at,
)
def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(get_db),
) -> User:
user_id = decode_access_token(credentials.credentials)
if user_id is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
user = db.execute(select(User).where(User.id == int(user_id))).scalar_one_or_none()
if user is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found")
return user
@router.post("/auth/dev-login", response_model=TokenResponse)
def dev_login(payload: DevLoginRequest, db: Session = Depends(get_db)) -> TokenResponse:
user = db.execute(select(User).where(User.telegram_id == payload.telegram_id)).scalar_one_or_none()
if user is None:
user = User(
telegram_id=payload.telegram_id,
username=payload.username,
first_name=payload.first_name,
balance=Decimal("0.00"),
)
db.add(user)
db.commit()
db.refresh(user)
else:
user.username = payload.username
user.first_name = payload.first_name
db.commit()
db.refresh(user)
token = create_access_token(str(user.id))
return TokenResponse(access_token=token)
@router.post("/auth/telegram", response_model=TokenResponse)
def telegram_auth(_payload: TelegramAuthRequest) -> TokenResponse:
# BOT_TOKEN not configured yet — wire real initData validation later.
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED,
detail="Telegram auth requires BOT_TOKEN. Use /auth/dev-login for local development.",
)
@router.get("/me", response_model=UserResponse)
def get_me(user: User = Depends(get_current_user)) -> UserResponse:
return user_to_response(user)
@router.post("/me/agreement", response_model=UserResponse)
def accept_agreement(
user: User = Depends(get_current_user),
db: Session = Depends(get_db),
) -> UserResponse:
if user.agreement_accepted_at is None:
user.agreement_accepted_at = datetime.now(timezone.utc)
db.commit()
db.refresh(user)
return user_to_response(user)
@router.get("/wallet", response_model=WalletResponse)
def get_wallet(
user: User = Depends(get_current_user),
db: Session = Depends(get_db),
) -> WalletResponse:
deposits = (
db.execute(
select(Deposit).where(Deposit.user_id == user.id).order_by(Deposit.created_at.desc()).limit(20)
)
.scalars()
.all()
)
return WalletResponse(
balance=user.balance,
deposits=[DepositResponse.model_validate(d) for d in deposits],
)
@router.post("/wallet/deposit", response_model=WalletResponse)
def deposit_stub(
payload: DepositRequest,
user: User = Depends(get_current_user),
db: Session = Depends(get_db),
) -> WalletResponse:
if user.agreement_accepted_at is None:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Accept user agreement first",
)
amount = payload.amount.quantize(Decimal("0.01"))
if amount not in ALLOWED_DEPOSIT_AMOUNTS:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Amount must be one of: {sorted(ALLOWED_DEPOSIT_AMOUNTS)}",
)
deposit = Deposit(user_id=user.id, amount=amount)
user.balance = (user.balance or Decimal("0.00")) + amount
db.add(deposit)
db.commit()
db.refresh(user)
deposits = (
db.execute(
select(Deposit).where(Deposit.user_id == user.id).order_by(Deposit.created_at.desc()).limit(20)
)
.scalars()
.all()
)
return WalletResponse(
balance=user.balance,
deposits=[DepositResponse.model_validate(d) for d in deposits],
)
@router.get("/games", response_model=list[GameStub])
def list_games() -> list[GameStub]:
return GAMES
@router.get("/games/{slug}", response_model=GameStub)
def get_game(slug: str) -> GameStub:
for game in GAMES:
if game.slug == slug:
return game
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Game not found")