🐐
This commit is contained in:
229
backend/routers/cards.py
Normal file
229
backend/routers/cards.py
Normal file
@@ -0,0 +1,229 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
||||
from sqlalchemy import asc, case, desc, func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from game.card import _get_specific_card_async
|
||||
from core.database import get_db
|
||||
from services.database_functions import check_boosters, fill_card_pool, BOOSTER_MAX
|
||||
from core.dependencies import get_current_user, limiter
|
||||
from core.models import Card as CardModel
|
||||
from core.models import Deck as DeckModel
|
||||
from core.models import DeckCard as DeckCardModel
|
||||
from core.models import User as UserModel
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/boosters")
|
||||
def get_boosters(user: UserModel = Depends(get_current_user), db: Session = Depends(get_db)):
|
||||
count, countdown = check_boosters(user, db)
|
||||
return {"count": count, "countdown": countdown, "email_verified": user.email_verified}
|
||||
|
||||
|
||||
@router.get("/cards")
|
||||
def get_cards(
|
||||
skip: int = 0,
|
||||
limit: int = 40,
|
||||
search: str = "",
|
||||
rarities: list[str] = Query(default=[]),
|
||||
types: list[str] = Query(default=[]),
|
||||
cost_min: int = 1,
|
||||
cost_max: int = 10,
|
||||
favorites_only: bool = False,
|
||||
wtt_only: bool = False,
|
||||
sort_by: str = "name",
|
||||
sort_dir: str = "asc",
|
||||
user: UserModel = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
q = db.query(CardModel).filter(CardModel.user_id == user.id)
|
||||
|
||||
if search:
|
||||
q = q.filter(CardModel.name.ilike(f"%{search}%"))
|
||||
if rarities:
|
||||
q = q.filter(CardModel.card_rarity.in_(rarities))
|
||||
if types:
|
||||
q = q.filter(CardModel.card_type.in_(types))
|
||||
q = q.filter(CardModel.cost >= cost_min, CardModel.cost <= cost_max)
|
||||
if favorites_only:
|
||||
q = q.filter(CardModel.is_favorite == True)
|
||||
if wtt_only:
|
||||
q = q.filter(CardModel.willing_to_trade == True)
|
||||
|
||||
total = q.count()
|
||||
|
||||
# case() for rarity ordering matches frontend RARITY_ORDER constant
|
||||
rarity_order_expr = case(
|
||||
(CardModel.card_rarity == 'common', 0),
|
||||
(CardModel.card_rarity == 'uncommon', 1),
|
||||
(CardModel.card_rarity == 'rare', 2),
|
||||
(CardModel.card_rarity == 'super_rare', 3),
|
||||
(CardModel.card_rarity == 'epic', 4),
|
||||
(CardModel.card_rarity == 'legendary', 5),
|
||||
else_=0
|
||||
)
|
||||
# coalesce mirrors frontend: received_at ?? generated_at
|
||||
date_received_expr = func.coalesce(CardModel.received_at, CardModel.generated_at)
|
||||
|
||||
sort_map = {
|
||||
"name": CardModel.name,
|
||||
"cost": CardModel.cost,
|
||||
"attack": CardModel.attack,
|
||||
"defense": CardModel.defense,
|
||||
"rarity": rarity_order_expr,
|
||||
"date_generated": CardModel.generated_at,
|
||||
"date_received": date_received_expr,
|
||||
}
|
||||
sort_col = sort_map.get(sort_by, CardModel.name)
|
||||
order_fn = desc if sort_dir == "desc" else asc
|
||||
# Secondary sort by name keeps pages stable when primary values are tied
|
||||
q = q.order_by(order_fn(sort_col), asc(CardModel.name))
|
||||
|
||||
cards = q.offset(skip).limit(limit).all()
|
||||
return {
|
||||
"cards": [
|
||||
{c.name: getattr(card, c.name) for c in card.__table__.columns}
|
||||
for card in cards
|
||||
],
|
||||
"total": total,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/cards/in-decks")
|
||||
def get_cards_in_decks(user: UserModel = Depends(get_current_user), db: Session = Depends(get_db)):
|
||||
deck_ids = [d.id for d in db.query(DeckModel).filter(DeckModel.user_id == user.id, DeckModel.deleted == False).all()]
|
||||
if not deck_ids:
|
||||
return []
|
||||
card_ids = db.query(DeckCardModel.card_id).filter(DeckCardModel.deck_id.in_(deck_ids)).distinct().all()
|
||||
return [str(row.card_id) for row in card_ids]
|
||||
|
||||
|
||||
@router.post("/open_pack")
|
||||
@limiter.limit("10/minute")
|
||||
async def open_pack(request: Request, user: UserModel = Depends(get_current_user), db: Session = Depends(get_db)):
|
||||
if not user.email_verified:
|
||||
raise HTTPException(status_code=403, detail="You must verify your email before opening packs")
|
||||
|
||||
check_boosters(user, db)
|
||||
|
||||
if user.boosters == 0:
|
||||
raise HTTPException(status_code=400, detail="No booster packs available")
|
||||
|
||||
cards = (
|
||||
db.query(CardModel)
|
||||
.filter(CardModel.user_id == None, CardModel.ai_used == False)
|
||||
.limit(5)
|
||||
.all()
|
||||
)
|
||||
|
||||
if len(cards) < 5:
|
||||
asyncio.create_task(fill_card_pool())
|
||||
raise HTTPException(status_code=503, detail="Card pool is low, please try again shortly")
|
||||
|
||||
now = datetime.now()
|
||||
for card in cards:
|
||||
card.user_id = user.id
|
||||
card.received_at = now
|
||||
|
||||
was_full = user.boosters == BOOSTER_MAX
|
||||
user.boosters -= 1
|
||||
if was_full:
|
||||
user.boosters_countdown = datetime.now()
|
||||
|
||||
db.commit()
|
||||
|
||||
asyncio.create_task(fill_card_pool())
|
||||
|
||||
return [
|
||||
{**{c.name: getattr(card, c.name) for c in card.__table__.columns},
|
||||
"card_rarity": card.card_rarity,
|
||||
"card_type": card.card_type}
|
||||
for card in cards
|
||||
]
|
||||
|
||||
|
||||
@router.post("/cards/{card_id}/report")
|
||||
def report_card(card_id: str, user: UserModel = Depends(get_current_user), db: Session = Depends(get_db)):
|
||||
card = db.query(CardModel).filter(
|
||||
CardModel.id == uuid.UUID(card_id),
|
||||
CardModel.user_id == user.id
|
||||
).first()
|
||||
if not card:
|
||||
raise HTTPException(status_code=404, detail="Card not found")
|
||||
card.reported = True
|
||||
db.commit()
|
||||
return {"message": "Card reported"}
|
||||
|
||||
|
||||
@router.post("/cards/{card_id}/refresh")
|
||||
@limiter.limit("5/hour")
|
||||
async def refresh_card(request: Request, card_id: str, user: UserModel = Depends(get_current_user), db: Session = Depends(get_db)):
|
||||
card = db.query(CardModel).filter(
|
||||
CardModel.id == uuid.UUID(card_id),
|
||||
CardModel.user_id == user.id
|
||||
).first()
|
||||
if not card:
|
||||
raise HTTPException(status_code=404, detail="Card not found")
|
||||
|
||||
if user.last_refresh_at and datetime.now() - user.last_refresh_at < timedelta(minutes=10):
|
||||
remaining = (user.last_refresh_at + timedelta(minutes=10)) - datetime.now()
|
||||
minutes = int(remaining.total_seconds() // 60)
|
||||
seconds = int(remaining.total_seconds() % 60)
|
||||
raise HTTPException(
|
||||
status_code=429,
|
||||
detail=f"You can refresh again in {minutes}m {seconds}s"
|
||||
)
|
||||
|
||||
new_card = await _get_specific_card_async(card.name)
|
||||
if not new_card:
|
||||
raise HTTPException(status_code=502, detail="Failed to regenerate card from Wikipedia")
|
||||
|
||||
card.image_link = new_card.image_link
|
||||
card.card_rarity = new_card.card_rarity.name
|
||||
card.card_type = new_card.card_type.name
|
||||
card.text = new_card.text
|
||||
card.attack = new_card.attack
|
||||
card.defense = new_card.defense
|
||||
card.cost = new_card.cost
|
||||
card.reported = False
|
||||
card.generated_at = datetime.now()
|
||||
card.received_at = datetime.now()
|
||||
|
||||
user.last_refresh_at = datetime.now()
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
**{c.name: getattr(card, c.name) for c in card.__table__.columns},
|
||||
"card_rarity": card.card_rarity,
|
||||
"card_type": card.card_type,
|
||||
}
|
||||
|
||||
|
||||
@router.post("/cards/{card_id}/favorite")
|
||||
def toggle_favorite(card_id: str, user: UserModel = Depends(get_current_user), db: Session = Depends(get_db)):
|
||||
card = db.query(CardModel).filter(
|
||||
CardModel.id == uuid.UUID(card_id),
|
||||
CardModel.user_id == user.id
|
||||
).first()
|
||||
if not card:
|
||||
raise HTTPException(status_code=404, detail="Card not found")
|
||||
card.is_favorite = not card.is_favorite
|
||||
db.commit()
|
||||
return {"is_favorite": card.is_favorite}
|
||||
|
||||
|
||||
@router.post("/cards/{card_id}/willing-to-trade")
|
||||
def toggle_willing_to_trade(card_id: str, user: UserModel = Depends(get_current_user), db: Session = Depends(get_db)):
|
||||
card = db.query(CardModel).filter(
|
||||
CardModel.id == uuid.UUID(card_id),
|
||||
CardModel.user_id == user.id
|
||||
).first()
|
||||
if not card:
|
||||
raise HTTPException(status_code=404, detail="Card not found")
|
||||
card.willing_to_trade = not card.willing_to_trade
|
||||
db.commit()
|
||||
return {"willing_to_trade": card.willing_to_trade}
|
||||
Reference in New Issue
Block a user