from fastapi import APIRouter, Depends, HTTPException, Response, Query from typing import Optional, List import logging import pydantic from pandas import DataFrame from ..db_engine import db, Card, model_to_dict, Team, Player, Pack, Paperdex, CARDSETS, DoesNotExist from ..dependencies import oauth2_scheme, valid_token from ..services.evolution_init import _determine_card_type, initialize_card_evolution router = APIRouter(prefix="/api/v2/cards", tags=["cards"]) class CardPydantic(pydantic.BaseModel): player_id: int team_id: int pack_id: int value: Optional[int] = 0 variant: Optional[int] = 0 class CardModel(pydantic.BaseModel): cards: List[CardPydantic] @router.get("") async def get_cards( player_id: Optional[int] = None, team_id: Optional[int] = None, pack_id: Optional[int] = None, value: Optional[int] = None, min_value: Optional[int] = None, max_value: Optional[int] = None, variant: Optional[int] = None, order_by: Optional[str] = None, limit: Optional[int] = None, dupes: Optional[bool] = None, csv: Optional[bool] = None, ): all_cards = Card.select() # if all_cards.count() == 0: # db.close() # raise HTTPException(status_code=404, detail=f'There are no cards to filter') if team_id is not None: try: this_team = Team.get_by_id(team_id) except DoesNotExist: raise HTTPException(status_code=404, detail=f'No team found with id {team_id}') all_cards = all_cards.where(Card.team == this_team) if player_id is not None: try: this_player = Player.get_by_id(player_id) except DoesNotExist: raise HTTPException(status_code=404, detail=f'No player found with id {player_id}') all_cards = all_cards.where(Card.player == this_player) if pack_id is not None: try: this_pack = Pack.get_by_id(pack_id) except DoesNotExist: raise HTTPException(status_code=404, detail=f'No pack found with id {pack_id}') all_cards = all_cards.where(Card.pack == this_pack) if value is not None: all_cards = all_cards.where(Card.value == value) # if variant is not None: # all_cards = all_cards.where(Card.variant == variant) if min_value is not None: all_cards = all_cards.where(Card.value >= min_value) if max_value is not None: all_cards = all_cards.where(Card.value <= max_value) if order_by is not None: if order_by.lower() == "new": all_cards = all_cards.order_by(-Card.id) else: all_cards = all_cards.order_by(Card.id) if limit is not None: all_cards = all_cards.limit(limit) if dupes: if team_id is None: raise HTTPException( status_code=400, detail="Dupe checking must include a team_id" ) logging.debug("dupe check") p_query = Card.select(Card.player).where(Card.team_id == team_id) seen = set() dupes = [] for x in p_query: if x.player.player_id in seen: dupes.append(x.player_id) else: seen.add(x.player_id) all_cards = all_cards.where(Card.player_id << dupes) # if all_cards.count() == 0: # db.close() # raise HTTPException(status_code=404, detail=f'No cards found') if csv: data_list = [ ["id", "player", "cardset", "rarity", "team", "pack", "value"] ] # , 'variant']] for line in all_cards: data_list.append( [ line.id, line.player.p_name, line.player.cardset, line.player.rarity, line.team.abbrev, line.pack, line.value, # line.variant ] ) return_val = DataFrame(data_list).to_csv(header=False, index=False) return Response(content=return_val, media_type="text/csv") else: card_list = list(all_cards) player_ids = [c.player_id for c in card_list if c.player_id is not None] dex_by_player = {} if player_ids: for row in Paperdex.select().where(Paperdex.player_id << player_ids): dex_by_player.setdefault(row.player_id, []).append(row) return_val = {"count": len(card_list), "cards": []} for x in card_list: this_record = model_to_dict(x) logging.debug(f"this_record: {this_record}") entries = dex_by_player.get(x.player_id, []) this_record["player"]["paperdex"] = { "count": len(entries), "paperdex": [model_to_dict(y, recurse=False) for y in entries], } return_val["cards"].append(this_record) # return_val['cards'].append(model_to_dict(x)) return return_val @router.get("/{card_id}") async def v1_cards_get_one(card_id, csv: Optional[bool] = False): try: this_card = Card.get_by_id(card_id) except DoesNotExist: raise HTTPException(status_code=404, detail=f'No card found with id {card_id}') if csv: data_list = [ ["id", "player", "team", "pack", "value"], [ this_card.id, this_card.player, this_card.team.abbrev, this_card.pack, this_card.value, ], ] return_val = DataFrame(data_list).to_csv(header=False, index=False) return Response(content=return_val, media_type="text/csv") else: return_val = model_to_dict(this_card) return return_val @router.post("") async def v1_cards_post(cards: CardModel, token: str = Depends(oauth2_scheme)): if not valid_token(token): logging.warning("Bad Token: [REDACTED]") raise HTTPException( status_code=401, detail="You are not authorized to post cards. This event has been logged.", ) new_cards = [] player_ids = [] inc_dex = True this_team = Team.get_by_id(cards.cards[0].team_id) if this_team.is_ai or "Gauntlet" in this_team.abbrev: inc_dex = False # new_dex = [] # now = int(datetime.timestamp(datetime.now()) * 1000) for x in cards.cards: this_card = Card( player_id=x.player_id, team_id=x.team_id, pack_id=x.pack_id, value=x.value, # variant=x.variant ) if inc_dex: Paperdex.get_or_create(team_id=x.team_id, player_id=x.player_id) player_ids.append(x.player_id) new_cards.append(this_card) with db.atomic(): Card.bulk_create(new_cards, batch_size=15) cost_query = Player.update(cost=Player.cost + 1).where( Player.player_id << player_ids ) cost_query.execute() # sheets.post_new_cards(SHEETS_AUTH, lc_id) # WP-10: initialize evolution state for each new card (fire-and-forget) for x in cards.cards: try: this_player = Player.get_by_id(x.player_id) card_type = _determine_card_type(this_player) initialize_card_evolution(x.player_id, x.team_id, card_type) except Exception: logging.exception( "evolution hook: unexpected error for player_id=%s team_id=%s", x.player_id, x.team_id, ) raise HTTPException( status_code=200, detail=f"{len(new_cards)} cards have been added" ) # @router.post('/ai-update') # async def v1_cards_ai_update(token: str = Depends(oauth2_scheme)): # if not valid_token(token): # logging.warning('Bad Token: [REDACTED]') # db.close() # raise HTTPException( # status_code=401, # detail='You are not authorized to update AI cards. This event has been logged.' # ) # # sheets.send_ai_cards(SHEETS_AUTH) # raise HTTPException(status_code=200, detail=f'Just sent AI cards to sheets') @router.post("/legal-check/{rarity_name}") async def v1_cards_legal_check( rarity_name: str, card_id: list = Query(default=None), token: str = Depends(oauth2_scheme), ): if not valid_token(token): logging.warning("Bad Token: [REDACTED]") raise HTTPException(status_code=401, detail="Unauthorized") if rarity_name not in CARDSETS.keys(): return f"Rarity name {rarity_name} not a valid check" # Handle case where card_id is passed as a stringified list if ( card_id and len(card_id) == 1 and isinstance(card_id[0], str) and card_id[0].startswith("[") ): import ast try: card_id = [int(x) for x in ast.literal_eval(card_id[0])] except (ValueError, SyntaxError): pass bad_cards = [] all_cards = Card.select().where(Card.id << card_id) for x in all_cards: if x.player.cardset_id not in CARDSETS[rarity_name]["human"]: if x.player.p_name in x.player.description: bad_cards.append(x.player.description) else: bad_cards.append(f"{x.player.description} {x.player.p_name}") return {"count": len(bad_cards), "bad_cards": bad_cards} @router.post("/post-update/{starting_id}") async def v1_cards_post_update(starting_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): logging.warning("Bad Token: [REDACTED]") raise HTTPException( status_code=401, detail="You are not authorized to update card lists. This event has been logged.", ) # sheets.post_new_cards(SHEETS_AUTH, starting_id) raise HTTPException( status_code=200, detail=f"Just sent cards to sheets starting at ID {starting_id}", ) @router.post("/post-delete") async def v1_cards_post_delete(del_ids: str, token: str = Depends(oauth2_scheme)): if not valid_token(token): logging.warning("Bad Token: [REDACTED]") raise HTTPException( status_code=401, detail="You are not authorized to delete card lists. This event has been logged.", ) logging.info(f"del_ids: {del_ids} / type: {type(del_ids)}") # sheets.post_deletion(SHEETS_AUTH, del_ids.split(',')) @router.post("/wipe-team/{team_id}") async def v1_cards_wipe_team(team_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): logging.warning("Bad Token: [REDACTED]") raise HTTPException( status_code=401, detail="You are not authorized to wipe teams. This event has been logged.", ) try: this_team = Team.get_by_id(team_id) except DoesNotExist: logging.error(f'/cards/wipe-team/{team_id} - could not find team') raise HTTPException(status_code=404, detail=f'Team {team_id} not found') t_query = Card.update(team=None).where(Card.team == this_team).execute() return f"Wiped {t_query} cards" @router.patch("/{card_id}") async def v1_cards_patch( card_id, player_id: Optional[int] = None, team_id: Optional[int] = None, pack_id: Optional[int] = None, value: Optional[int] = None, variant: Optional[int] = None, roster1_id: Optional[int] = None, roster2_id: Optional[int] = None, roster3_id: Optional[int] = None, token: str = Depends(oauth2_scheme), ): if not valid_token(token): logging.warning("Bad Token: [REDACTED]") raise HTTPException( status_code=401, detail="You are not authorized to patch cards. This event has been logged.", ) try: this_card = Card.get_by_id(card_id) except DoesNotExist: raise HTTPException(status_code=404, detail=f'No card found with id {card_id}') if player_id is not None: this_card.player_id = player_id if team_id is not None: if team_id == 0: this_card.team_id = None else: this_card.team_id = team_id if pack_id is not None: this_card.pack_id = pack_id if value is not None: this_card.value = value # if variant is not None: # this_card.variant = variant if roster1_id is not None: this_card.roster1_id = roster1_id if roster2_id is not None: this_card.roster2_id = roster2_id if roster3_id is not None: this_card.roster3_id = roster3_id if this_card.save() == 1: return_val = model_to_dict(this_card) return return_val else: raise HTTPException( status_code=418, detail="Well slap my ass and call me a teapot; I could not save that rarity", ) @router.delete("/{card_id}") async def v1_cards_delete(card_id, token: str = Depends(oauth2_scheme)): if not valid_token(token): logging.warning("Bad Token: [REDACTED]") raise HTTPException( status_code=401, detail="You are not authorized to delete packs. This event has been logged.", ) try: this_card = Card.get_by_id(card_id) except DoesNotExist: raise HTTPException(status_code=404, detail=f'No cards found with id {card_id}') count = this_card.delete_instance() if count == 1: raise HTTPException(status_code=200, detail=f"Card {card_id} has been deleted") else: raise HTTPException(status_code=500, detail=f"Card {card_id} was not deleted")