- Add SQL migration script to update all franchise values - Change AI roster queries from Team.lname to Team.sname - Add FRANCHISE_NORMALIZE helper for bulk imports - Update St Louis Cardinals hardcoded fix Enables cross-era player matching for AI rosters (fixes Oakland Athletics issue) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
902 lines
37 KiB
Python
902 lines
37 KiB
Python
import datetime
|
|
import os.path
|
|
import base64
|
|
|
|
import pandas as pd
|
|
from fastapi import APIRouter, Depends, HTTPException, Request, Response, Query
|
|
from fastapi.responses import FileResponse
|
|
from fastapi.templating import Jinja2Templates
|
|
from html2image import Html2Image
|
|
from typing import Optional, List, Literal
|
|
import logging
|
|
import pydantic
|
|
from pandas import DataFrame
|
|
from playwright.async_api import async_playwright
|
|
|
|
from ..card_creation import get_batter_card_data, get_pitcher_card_data
|
|
from ..db_engine import db, Player, model_to_dict, fn, chunked, Paperdex, Cardset, Rarity, BattingCard, \
|
|
BattingCardRatings, PitchingCard, PitchingCardRatings, CardPosition, MlbPlayer
|
|
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
|
|
|
|
# Franchise normalization: Convert city+team names to city-agnostic team names
|
|
# This enables cross-era player matching (e.g., 'Oakland Athletics' -> 'Athletics')
|
|
FRANCHISE_NORMALIZE = {
|
|
'Arizona Diamondbacks': 'Diamondbacks',
|
|
'Atlanta Braves': 'Braves',
|
|
'Baltimore Orioles': 'Orioles',
|
|
'Boston Red Sox': 'Red Sox',
|
|
'Chicago Cubs': 'Cubs',
|
|
'Chicago White Sox': 'White Sox',
|
|
'Cincinnati Reds': 'Reds',
|
|
'Cleveland Guardians': 'Guardians',
|
|
'Colorado Rockies': 'Rockies',
|
|
'Detroit Tigers': 'Tigers',
|
|
'Houston Astros': 'Astros',
|
|
'Kansas City Royals': 'Royals',
|
|
'Los Angeles Angels': 'Angels',
|
|
'Los Angeles Dodgers': 'Dodgers',
|
|
'Miami Marlins': 'Marlins',
|
|
'Milwaukee Brewers': 'Brewers',
|
|
'Minnesota Twins': 'Twins',
|
|
'New York Mets': 'Mets',
|
|
'New York Yankees': 'Yankees',
|
|
'Oakland Athletics': 'Athletics',
|
|
'Philadelphia Phillies': 'Phillies',
|
|
'Pittsburgh Pirates': 'Pirates',
|
|
'San Diego Padres': 'Padres',
|
|
'San Francisco Giants': 'Giants',
|
|
'Seattle Mariners': 'Mariners',
|
|
'St Louis Cardinals': 'Cardinals',
|
|
'St. Louis Cardinals': 'Cardinals',
|
|
'Tampa Bay Rays': 'Rays',
|
|
'Texas Rangers': 'Rangers',
|
|
'Toronto Blue Jays': 'Blue Jays',
|
|
'Washington Nationals': 'Nationals',
|
|
}
|
|
|
|
|
|
def normalize_franchise(franchise: str) -> str:
|
|
"""Convert city+team name to team-only (e.g., 'Oakland Athletics' -> 'Athletics')"""
|
|
titled = franchise.title()
|
|
return FRANCHISE_NORMALIZE.get(titled, titled)
|
|
|
|
|
|
logging.basicConfig(
|
|
filename=LOG_DATA['filename'],
|
|
format=LOG_DATA['format'],
|
|
level=LOG_DATA['log_level']
|
|
)
|
|
|
|
router = APIRouter(
|
|
prefix='/api/v2/players',
|
|
tags=['players']
|
|
)
|
|
|
|
|
|
templates = Jinja2Templates(directory="storage/templates")
|
|
|
|
|
|
class PlayerPydantic(pydantic.BaseModel):
|
|
player_id: int = None
|
|
p_name: str
|
|
cost: int
|
|
image: str
|
|
image2: Optional[str] = None
|
|
mlbclub: str
|
|
franchise: str
|
|
cardset_id: int
|
|
set_num: int
|
|
rarity_id: int
|
|
pos_1: str
|
|
pos_2: Optional[str] = None
|
|
pos_3: Optional[str] = None
|
|
pos_4: Optional[str] = None
|
|
pos_5: Optional[str] = None
|
|
pos_6: Optional[str] = None
|
|
pos_7: Optional[str] = None
|
|
pos_8: Optional[str] = None
|
|
headshot: Optional[str] = None
|
|
vanity_card: Optional[str] = None
|
|
strat_code: Optional[str] = None
|
|
bbref_id: Optional[str] = None
|
|
fangr_id: Optional[str] = None
|
|
description: str
|
|
quantity: Optional[int] = 999
|
|
mlbplayer_id: Optional[int] = None
|
|
|
|
|
|
class PlayerModel(pydantic.BaseModel):
|
|
players: List[PlayerPydantic]
|
|
|
|
|
|
@router.get('')
|
|
async def get_players(
|
|
name: Optional[str] = None, value: Optional[int] = None, min_cost: Optional[int] = None,
|
|
max_cost: Optional[int] = None, has_image2: Optional[bool] = None, mlbclub: Optional[str] = None,
|
|
franchise: Optional[str] = None, cardset_id: list = Query(default=None), rarity_id: list = Query(default=None),
|
|
pos_include: list = Query(default=None), pos_exclude: list = Query(default=None), has_headshot: Optional[bool] = None,
|
|
has_vanity_card: Optional[bool] = None, strat_code: Optional[str] = None, bbref_id: Optional[str] = None,
|
|
fangr_id: Optional[str] = None, inc_dex: Optional[bool] = True, in_desc: Optional[str] = None,
|
|
flat: Optional[bool] = False, sort_by: Optional[str] = False, cardset_id_exclude: list = Query(default=None),
|
|
limit: Optional[int] = None, csv: Optional[bool] = None, short_output: Optional[bool] = False, mlbplayer_id: Optional[int] = None,
|
|
inc_keys: Optional[bool] = False):
|
|
all_players = Player.select()
|
|
if all_players.count() == 0:
|
|
db.close()
|
|
raise HTTPException(status_code=404, detail=f'There are no players to filter')
|
|
|
|
if name is not None:
|
|
all_players = all_players.where(fn.Lower(Player.p_name) == name.lower())
|
|
if value is not None:
|
|
all_players = all_players.where(Player.cost == value)
|
|
if min_cost is not None:
|
|
all_players = all_players.where(Player.cost >= min_cost)
|
|
if max_cost is not None:
|
|
all_players = all_players.where(Player.cost <= max_cost)
|
|
if has_image2 is not None:
|
|
all_players = all_players.where(Player.image2.is_null(not has_image2))
|
|
if mlbclub is not None:
|
|
all_players = all_players.where(fn.Lower(Player.mlbclub) == mlbclub.lower())
|
|
if franchise is not None:
|
|
all_players = all_players.where(fn.Lower(Player.franchise) == franchise.lower())
|
|
if cardset_id is not None:
|
|
all_players = all_players.where(Player.cardset_id << cardset_id)
|
|
if cardset_id_exclude is not None:
|
|
all_players = all_players.where(Player.cardset_id.not_in(cardset_id_exclude))
|
|
if rarity_id is not None:
|
|
all_players = all_players.where(Player.rarity_id << rarity_id)
|
|
if pos_include is not None:
|
|
p_list = [x.upper() for x in pos_include]
|
|
all_players = all_players.where(
|
|
(Player.pos_1 << p_list) | (Player.pos_2 << p_list) | (Player.pos_3 << p_list) | (Player.pos_4 << p_list) |
|
|
(Player.pos_5 << p_list) | (Player.pos_6 << p_list) | (Player.pos_7 << p_list) | (Player.pos_8 << p_list)
|
|
)
|
|
if has_headshot is not None:
|
|
all_players = all_players.where(Player.headshot.is_null(not has_headshot))
|
|
if has_vanity_card is not None:
|
|
all_players = all_players.where(Player.vanity_card.is_null(not has_vanity_card))
|
|
if strat_code is not None:
|
|
all_players = all_players.where(Player.strat_code == strat_code)
|
|
if bbref_id is not None:
|
|
all_players = all_players.where(Player.bbref_id == bbref_id)
|
|
if fangr_id is not None:
|
|
all_players = all_players.where(Player.fangr_id == fangr_id)
|
|
if mlbplayer_id is not None:
|
|
all_players = all_players.where(Player.mlbplayer_id == mlbplayer_id)
|
|
if in_desc is not None:
|
|
all_players = all_players.where(fn.Lower(Player.description).contains(in_desc.lower()))
|
|
|
|
if sort_by is not None:
|
|
if sort_by == 'cost-desc':
|
|
all_players = all_players.order_by(-Player.cost)
|
|
elif sort_by == 'cost-asc':
|
|
all_players = all_players.order_by(Player.cost)
|
|
elif sort_by == 'name-asc':
|
|
all_players = all_players.order_by(Player.p_name)
|
|
elif sort_by == 'name-desc':
|
|
all_players = all_players.order_by(-Player.p_name)
|
|
elif sort_by == 'rarity-desc':
|
|
all_players = all_players.order_by(Player.rarity)
|
|
elif sort_by == 'rarity-asc':
|
|
all_players = all_players.order_by(-Player.rarity)
|
|
|
|
final_players = []
|
|
# logging.info(f'pos_exclude: {type(pos_exclude)} - {pos_exclude} - is None: {pos_exclude is None}')
|
|
for x in all_players:
|
|
if pos_exclude is not None and set([x.upper() for x in pos_exclude]).intersection(x.get_all_pos()):
|
|
pass
|
|
else:
|
|
final_players.append(x)
|
|
|
|
if limit is not None and len(final_players) >= limit:
|
|
break
|
|
|
|
# if len(final_players) == 0:
|
|
# db.close()
|
|
# raise HTTPException(status_code=404, detail=f'No players found')
|
|
|
|
if csv:
|
|
card_vals = [model_to_dict(x) for x in all_players]
|
|
db.close()
|
|
|
|
for x in card_vals:
|
|
x['player_name'] = x['p_name']
|
|
x['cardset_name'] = x['cardset']['name']
|
|
x['rarity'] = x['rarity']['name']
|
|
x['for_purchase'] = x['cardset']['for_purchase']
|
|
x['ranked_legal'] = x['cardset']['ranked_legal']
|
|
if x['player_name'] not in x['description']:
|
|
x['description'] = f'{x["description"]} {x["player_name"]}'
|
|
|
|
card_df = pd.DataFrame(card_vals)
|
|
output = card_df[[
|
|
'player_id', 'player_name', 'cost', 'image', 'image2', 'mlbclub', 'franchise', 'cardset_name', 'rarity',
|
|
'pos_1', 'pos_2', 'pos_3', 'pos_4', 'pos_5', 'pos_6', 'pos_7', 'pos_8', 'headshot', 'vanity_card',
|
|
'fangr_id', 'bbref_id', 'description', 'for_purchase', 'ranked_legal'
|
|
]]
|
|
return Response(content=pd.DataFrame(output).to_csv(index=False), media_type='text/csv')
|
|
|
|
# all_players.order_by(-Player.rarity.value, Player.p_name)
|
|
# data_list = [['id', 'name', 'value', 'image', 'image2', 'mlbclub', 'franchise', 'cardset', 'rarity', 'pos_1',
|
|
# 'pos_2', 'pos_3', 'pos_4', 'pos_5', 'pos_6', 'pos_7', 'pos_8', 'headshot', 'vanity_card',
|
|
# 'strat_code', 'bbref_id', 'description', 'for_purchase', 'ranked_legal']]
|
|
# for line in final_players:
|
|
# data_list.append(
|
|
# [
|
|
# line.player_id, line.p_name, line.cost, line.image, line.image2, line.mlbclub, line.franchise,
|
|
# line.cardset, line.rarity, line.pos_1, line.pos_2, line.pos_3, line.pos_4, line.pos_5, line.pos_6,
|
|
# line.pos_7, line.pos_8, line.headshot, line.vanity_card, line.strat_code, line.bbref_id,
|
|
# line.description, line.cardset.for_purchase, line.cardset.ranked_legal
|
|
# # line.description, line.cardset.in_packs, line.quantity
|
|
# ]
|
|
# )
|
|
# return_val = DataFrame(data_list).to_csv(header=False, index=False)
|
|
#
|
|
# db.close()
|
|
# return Response(content=return_val, media_type='text/csv')
|
|
|
|
else:
|
|
return_val = {'count': len(final_players), 'players': []}
|
|
for x in final_players:
|
|
|
|
this_record = model_to_dict(x, recurse=not (flat or short_output))
|
|
|
|
if inc_dex:
|
|
this_dex = Paperdex.select().where(Paperdex.player == x)
|
|
this_record['paperdex'] = {'count': this_dex.count(), 'paperdex': []}
|
|
for y in this_dex:
|
|
this_record['paperdex']['paperdex'].append(model_to_dict(y, recurse=False))
|
|
|
|
if inc_keys and (flat or short_output):
|
|
if this_record['mlbplayer'] is not None:
|
|
this_mlb = MlbPlayer.get_by_id(this_record['mlbplayer'])
|
|
this_record['key_mlbam'] = this_mlb.key_mlbam
|
|
this_record['key_fangraphs'] = this_mlb.key_fangraphs
|
|
this_record['key_bbref'] = this_mlb.key_bbref
|
|
this_record['key_retro'] = this_mlb.key_retro
|
|
this_record['offense_col'] = this_mlb.offense_col
|
|
|
|
return_val['players'].append(this_record)
|
|
|
|
# return_val['players'].append(model_to_dict(x, recurse=not flat))
|
|
|
|
db.close()
|
|
return return_val
|
|
|
|
|
|
@router.get('/random')
|
|
async def get_random_player(
|
|
min_cost: Optional[int] = None, max_cost: Optional[int] = None, in_packs: Optional[bool] = None,
|
|
min_rarity: Optional[int] = None, max_rarity: Optional[int] = None, limit: Optional[int] = None,
|
|
pos_include: Optional[str] = None, pos_exclude: Optional[str] = None, franchise: Optional[str] = None,
|
|
mlbclub: Optional[str] = None, cardset_id: list = Query(default=None), pos_inc: list = Query(default=None),
|
|
pos_exc: list = Query(default=None), csv: Optional[bool] = None):
|
|
all_players = (Player
|
|
.select()
|
|
.join(Cardset)
|
|
.switch(Player)
|
|
.join(Rarity)
|
|
.order_by(fn.Random()))
|
|
|
|
if min_cost is not None:
|
|
all_players = all_players.where(Player.cost >= min_cost)
|
|
if max_cost is not None:
|
|
all_players = all_players.where(Player.cost <= max_cost)
|
|
if in_packs is not None:
|
|
if in_packs:
|
|
all_players = all_players.where(Player.cardset.in_packs)
|
|
if min_rarity is not None:
|
|
all_players = all_players.where(Player.rarity.value >= min_rarity)
|
|
if max_rarity is not None:
|
|
all_players = all_players.where(Player.rarity.value <= max_rarity)
|
|
if pos_include is not None:
|
|
all_players = all_players.where(
|
|
(fn.lower(Player.pos_1) == pos_include.lower()) | (fn.lower(Player.pos_2) == pos_include.lower()) |
|
|
(fn.lower(Player.pos_3) == pos_include.lower()) | (fn.lower(Player.pos_4) == pos_include.lower()) |
|
|
(fn.lower(Player.pos_5) == pos_include.lower()) | (fn.lower(Player.pos_6) == pos_include.lower()) |
|
|
(fn.lower(Player.pos_7) == pos_include.lower()) | (fn.lower(Player.pos_8) == pos_include.lower())
|
|
)
|
|
if franchise is not None:
|
|
all_players = all_players.where(fn.Lower(Player.franchise) == franchise.lower())
|
|
if mlbclub is not None:
|
|
all_players = all_players.where(fn.Lower(Player.mlbclub) == mlbclub.lower())
|
|
if cardset_id is not None:
|
|
all_players = all_players.where(Player.cardset_id << cardset_id)
|
|
if pos_inc is not None:
|
|
p_list = [x.upper() for x in pos_inc]
|
|
all_players = all_players.where(
|
|
(Player.pos_1 << p_list) | (Player.pos_2 << p_list) | (Player.pos_3 << p_list) | (Player.pos_4 << p_list) |
|
|
(Player.pos_5 << p_list) | (Player.pos_6 << p_list) | (Player.pos_7 << p_list) | (Player.pos_8 << p_list)
|
|
)
|
|
# if pos_exc is not None:
|
|
# p_list = [x.upper() for x in pos_exc]
|
|
# logging.info(f'starting query: {all_players}\n\np_list: {p_list}\n\n')
|
|
# all_players = all_players.where(
|
|
# Player.pos_1.not_in(p_list) & Player.pos_2.not_in(p_list) & Player.pos_3.not_in(p_list) &
|
|
# Player.pos_4.not_in(p_list) & Player.pos_5.not_in(p_list) & Player.pos_6.not_in(p_list) &
|
|
# Player.pos_7.not_in(p_list) & Player.pos_8.not_in(p_list)
|
|
# )
|
|
# logging.info(f'post pos query: {all_players}')
|
|
|
|
if pos_exclude is not None and pos_exc is None:
|
|
final_players = [x for x in all_players if pos_exclude not in x.get_all_pos()]
|
|
elif pos_exc is not None and pos_exclude is None:
|
|
final_players = []
|
|
p_list = [x.upper() for x in pos_exc]
|
|
for x in all_players:
|
|
if limit is not None and len(final_players) >= limit:
|
|
break
|
|
if not set(p_list).intersection(x.get_all_pos()):
|
|
final_players.append(x)
|
|
else:
|
|
final_players = all_players
|
|
|
|
if limit is not None:
|
|
final_players = final_players[:limit]
|
|
|
|
# if len(final_players) == 0:
|
|
# db.close()
|
|
# raise HTTPException(status_code=404, detail=f'No players found')
|
|
|
|
if csv:
|
|
data_list = [['id', 'name', 'cost', 'image', 'image2', 'mlbclub', 'franchise', 'cardset', 'rarity', 'pos_1',
|
|
'pos_2', 'pos_3', 'pos_4', 'pos_5', 'pos_6', 'pos_7', 'pos_8', 'headshot', 'vanity_card',
|
|
'strat_code', 'bbref_id', 'description']]
|
|
for line in final_players:
|
|
data_list.append(
|
|
[
|
|
line.id, line.p_name, line.cost, line.image, line.image2,
|
|
line.mlbclub, line.franchise, line.cardset.name, line.rarity.name,
|
|
line.pos_1, line.pos_2, line.pos_3, line.pos_4, line.pos_5,
|
|
line.pos_6, line.pos_7, line.pos_8, line.headshot, line.vanity_card,
|
|
line.strat_code, line.bbref_id, line.description
|
|
]
|
|
)
|
|
return_val = DataFrame(data_list).to_csv(header=False, index=False)
|
|
|
|
db.close()
|
|
return Response(content=return_val, media_type='text/csv')
|
|
|
|
else:
|
|
return_val = {'count': len(final_players), 'players': []}
|
|
for x in final_players:
|
|
this_record = model_to_dict(x)
|
|
|
|
this_dex = Paperdex.select().where(Paperdex.player == x)
|
|
this_record['paperdex'] = {'count': this_dex.count(), 'paperdex': []}
|
|
for y in this_dex:
|
|
this_record['paperdex']['paperdex'].append(model_to_dict(y, recurse=False))
|
|
|
|
return_val['players'].append(this_record)
|
|
# return_val['players'].append(model_to_dict(x))
|
|
|
|
db.close()
|
|
return return_val
|
|
|
|
|
|
@router.get('/search')
|
|
async def search_players(
|
|
q: str = Query(..., description="Search query for player name"),
|
|
cardset_id: list = Query(default=None),
|
|
rarity_id: list = Query(default=None),
|
|
limit: int = Query(default=25, ge=1, le=100, description="Maximum number of results to return"),
|
|
unique_names: bool = Query(default=False, description="Return only unique player names (highest player_id)"),
|
|
short_output: bool = False):
|
|
"""
|
|
Real-time fuzzy search for players by name.
|
|
|
|
Returns players matching the query with exact matches prioritized over partial matches.
|
|
When unique_names=True, only returns one player per unique name (the one with highest player_id).
|
|
"""
|
|
# Start with all players
|
|
all_players = Player.select()
|
|
|
|
# Apply name filter (partial match)
|
|
all_players = all_players.where(fn.Lower(Player.p_name).contains(q.lower()))
|
|
|
|
# Apply optional filters
|
|
if cardset_id is not None:
|
|
all_players = all_players.where(Player.cardset_id << cardset_id)
|
|
|
|
if rarity_id is not None:
|
|
all_players = all_players.where(Player.rarity_id << rarity_id)
|
|
|
|
# Convert to list for sorting
|
|
players_list = list(all_players)
|
|
|
|
# Sort by relevance (exact matches first, then name starts, then partial)
|
|
query_lower = q.lower()
|
|
exact_matches = []
|
|
name_start_matches = []
|
|
partial_matches = []
|
|
|
|
for player in players_list:
|
|
name_lower = player.p_name.lower()
|
|
if name_lower == query_lower:
|
|
exact_matches.append(player)
|
|
else:
|
|
# Check if query matches the start of first or last name
|
|
name_parts = name_lower.split()
|
|
starts_with_match = any(part.startswith(query_lower) for part in name_parts)
|
|
|
|
if starts_with_match:
|
|
name_start_matches.append(player)
|
|
elif query_lower in name_lower:
|
|
partial_matches.append(player)
|
|
|
|
# Combine results (exact, then name starts, then partial)
|
|
results = exact_matches + name_start_matches + partial_matches
|
|
|
|
# Deduplicate by name if requested (keeping highest player_id)
|
|
if unique_names:
|
|
seen_names = {}
|
|
for player in results:
|
|
name_lower = player.p_name.lower()
|
|
if name_lower not in seen_names or player.player_id > seen_names[name_lower].player_id:
|
|
seen_names[name_lower] = player
|
|
results = list(seen_names.values())
|
|
|
|
total_matches = len(results)
|
|
limited_results = results[:limit]
|
|
|
|
# Build response
|
|
return_val = {
|
|
'count': len(limited_results),
|
|
'total_matches': total_matches,
|
|
'players': []
|
|
}
|
|
|
|
for x in limited_results:
|
|
this_record = model_to_dict(x, recurse=not short_output)
|
|
|
|
# this_dex = Paperdex.select().where(Paperdex.player == x)
|
|
# this_record['paperdex'] = {'count': this_dex.count(), 'paperdex': []}
|
|
# for y in this_dex:
|
|
# this_record['paperdex']['paperdex'].append(model_to_dict(y, recurse=False))
|
|
|
|
return_val['players'].append(this_record)
|
|
|
|
db.close()
|
|
return return_val
|
|
|
|
|
|
@router.get('/{player_id}')
|
|
async def get_one_player(player_id, csv: Optional[bool] = False):
|
|
try:
|
|
this_player = Player.get_by_id(player_id)
|
|
except Exception:
|
|
db.close()
|
|
raise HTTPException(status_code=404, detail=f'No player found with id {player_id}')
|
|
|
|
if csv:
|
|
data_list = [['id', 'name', 'cost', 'image', 'image2', 'mlbclub', 'franchise', 'cardset', 'rarity', 'pos_1',
|
|
'pos_2', 'pos_3', 'pos_4', 'pos_5', 'pos_6', 'pos_7', 'pos_8', 'headshot', 'vanity_card',
|
|
'strat_code', 'bbref_id', 'description']]
|
|
return_val = DataFrame(data_list).to_csv(header=False, index=False)
|
|
data_list.append(
|
|
[
|
|
this_player.id, this_player.p_name, this_player.cost, this_player.image, this_player.image2,
|
|
this_player.mlbclub, this_player.franchise, this_player.cardset.name, this_player.rarity.name,
|
|
this_player.pos_1, this_player.pos_2, this_player.pos_3, this_player.pos_4, this_player.pos_5,
|
|
this_player.pos_6, this_player.pos_7, this_player.pos_8, this_player.headshot, this_player.vanity_card,
|
|
this_player.strat_code, this_player.bbref_id, this_player.description
|
|
]
|
|
)
|
|
|
|
db.close()
|
|
return Response(content=return_val, media_type='text/csv')
|
|
else:
|
|
return_val = model_to_dict(this_player)
|
|
this_dex = Paperdex.select().where(Paperdex.player == this_player)
|
|
return_val['paperdex'] = {'count': this_dex.count(), 'paperdex': []}
|
|
for x in this_dex:
|
|
return_val['paperdex']['paperdex'].append(model_to_dict(x, recurse=False))
|
|
db.close()
|
|
return return_val
|
|
|
|
|
|
@router.get('/{player_id}/{card_type}card')
|
|
@router.get('/{player_id}/{card_type}card/{d}')
|
|
@router.get('/{player_id}/{card_type}card/{d}/{variant}')
|
|
async def get_batter_card(
|
|
request: Request, player_id: int, card_type: Literal['batting', 'pitching'], variant: int = 0, d: str = None,
|
|
html: Optional[bool] = False):
|
|
try:
|
|
this_player = Player.get_by_id(player_id)
|
|
except Exception:
|
|
db.close()
|
|
raise HTTPException(status_code=404, detail=f'No player found with id {player_id}')
|
|
|
|
headers = {'Cache-Control': 'public, max-age=86400'}
|
|
filename = f'{this_player.description} {this_player.p_name} {card_type} {d}-v{variant}'
|
|
if os.path.isfile(f'storage/cards/cardset-{this_player.cardset.id}/{card_type}/{player_id}-{d}-v{variant}.png') and html is False:
|
|
db.close()
|
|
return FileResponse(
|
|
path=f'storage/cards/cardset-{this_player.cardset.id}/{card_type}/{player_id}-{d}-v{variant}.png',
|
|
media_type='image/png',
|
|
headers=headers
|
|
)
|
|
|
|
all_pos = CardPosition.select().where(CardPosition.player == this_player).order_by(CardPosition.innings.desc())
|
|
|
|
if card_type == 'batting':
|
|
this_bc = BattingCard.get_or_none(BattingCard.player == this_player, BattingCard.variant == variant)
|
|
if this_bc is None:
|
|
raise HTTPException(status_code=404, detail=f'Batting card not found for id {player_id}, variant {variant}')
|
|
|
|
rating_vl = BattingCardRatings.get_or_none(
|
|
BattingCardRatings.battingcard == this_bc, BattingCardRatings.vs_hand == 'L')
|
|
rating_vr = BattingCardRatings.get_or_none(
|
|
BattingCardRatings.battingcard == this_bc, BattingCardRatings.vs_hand == 'R')
|
|
if None in [rating_vr, rating_vl]:
|
|
raise HTTPException(status_code=404, detail=f'Ratings not found for batting card {this_bc.id}')
|
|
|
|
card_data = get_batter_card_data(this_player, this_bc, rating_vl, rating_vr, all_pos)
|
|
# Include Pokemon cardsets here to remove "Pokemon" from cardset name on card
|
|
if this_player.description in this_player.cardset.name and this_player.cardset.id not in [23]:
|
|
card_data['cardset_name'] = this_player.cardset.name
|
|
else:
|
|
card_data['cardset_name'] = this_player.description
|
|
card_data['request'] = request
|
|
html_response = templates.TemplateResponse("player_card.html", card_data)
|
|
|
|
else:
|
|
this_pc = PitchingCard.get_or_none(PitchingCard.player == this_player, PitchingCard.variant == variant)
|
|
if this_pc is None:
|
|
raise HTTPException(
|
|
status_code=404, detail=f'Pitching card not found for id {player_id}, variant {variant}')
|
|
|
|
rating_vl = PitchingCardRatings.get_or_none(
|
|
PitchingCardRatings.pitchingcard == this_pc, PitchingCardRatings.vs_hand == 'L')
|
|
rating_vr = PitchingCardRatings.get_or_none(
|
|
PitchingCardRatings.pitchingcard == this_pc, PitchingCardRatings.vs_hand == 'R')
|
|
if None in [rating_vr, rating_vl]:
|
|
raise HTTPException(status_code=404, detail=f'Ratings not found for pitching card {this_pc.id}')
|
|
|
|
card_data = get_pitcher_card_data(this_player, this_pc, rating_vl, rating_vr, all_pos)
|
|
if this_player.description in this_player.cardset.name and this_player.cardset.id not in [23]:
|
|
card_data['cardset_name'] = this_player.cardset.name
|
|
else:
|
|
card_data['cardset_name'] = this_player.description
|
|
card_data['request'] = request
|
|
html_response = templates.TemplateResponse("player_card.html", card_data)
|
|
|
|
if html:
|
|
db.close()
|
|
return html_response
|
|
|
|
updates = 0
|
|
if card_type == 'batting':
|
|
updates += BattingCardRatings.update(card_data['new_ratings_vl'].dict()).where(
|
|
(BattingCardRatings.id == rating_vl.id)
|
|
).execute()
|
|
updates += BattingCardRatings.update(card_data['new_ratings_vr'].dict()).where(
|
|
(BattingCardRatings.id == rating_vr.id)
|
|
).execute()
|
|
else:
|
|
updates += PitchingCardRatings.update(card_data['new_ratings_vl'].dict()).where(
|
|
(PitchingCardRatings.id == rating_vl.id)
|
|
).execute()
|
|
updates += PitchingCardRatings.update(card_data['new_ratings_vr'].dict()).where(
|
|
(PitchingCardRatings.id == rating_vr.id)
|
|
).execute()
|
|
|
|
logging.debug(f'Rating updates: {updates}')
|
|
logging.debug(f'body:\n{html_response.body.decode("UTF-8")}')
|
|
|
|
file_path = f'storage/cards/cardset-{this_player.cardset.id}/{card_type}/{player_id}-{d}-v{variant}.png'
|
|
async with async_playwright() as p:
|
|
browser = await p.chromium.launch()
|
|
page = await browser.new_page()
|
|
await page.set_content(html_response.body.decode("UTF-8"))
|
|
await page.screenshot(path=file_path, type='png', clip={'x': 0.0, 'y': 0, 'width': 1200, 'height': 600})
|
|
await browser.close()
|
|
|
|
# hti = Html2Image(
|
|
# browser='chrome',
|
|
# size=(1200, 600),
|
|
# output_path=f'storage/cards/cardset-{this_player.cardset.id}/{card_type}/',
|
|
# custom_flags=['--no-sandbox', '--disable-remote-debugging', '--headless', '--disable-gpu',
|
|
# '--disable-software-rasterizer', '--disable-dev-shm-usage']
|
|
# )
|
|
|
|
# x = hti.screenshot(
|
|
# html_str=str(html_response.body.decode("UTF-8")),
|
|
# save_as=f'{player_id}-{d}-v{variant}.png'
|
|
# )
|
|
|
|
db.close()
|
|
return FileResponse(path=file_path, media_type='image/png', headers=headers)
|
|
|
|
|
|
# @router.get('/{player_id}/pitchingcard')
|
|
# async def get_pitcher_card(
|
|
# request: Request, player_id: int, variant: int = 0, d: str = None, html: Optional[bool] = False)
|
|
|
|
|
|
@router.patch('/{player_id}')
|
|
async def v1_players_patch(
|
|
player_id, name: Optional[str] = None, image: Optional[str] = None, image2: Optional[str] = None,
|
|
mlbclub: Optional[str] = None, franchise: Optional[str] = None, cardset_id: Optional[int] = None,
|
|
rarity_id: Optional[int] = None, pos_1: Optional[str] = None, pos_2: Optional[str] = None,
|
|
pos_3: Optional[str] = None, pos_4: Optional[str] = None, pos_5: Optional[str] = None, mlbplayer_id: Optional[int] = None,
|
|
pos_6: Optional[str] = None, pos_7: Optional[str] = None, pos_8: Optional[str] = None,
|
|
headshot: Optional[str] = None, vanity_card: Optional[str] = None, strat_code: Optional[str] = None,
|
|
bbref_id: Optional[str] = None, description: Optional[str] = None, cost: Optional[int] = None,
|
|
fangr_id: Optional[str] = None, token: str = Depends(oauth2_scheme)):
|
|
if not valid_token(token):
|
|
logging.warning(f'Bad Token: {token}')
|
|
db.close()
|
|
raise HTTPException(
|
|
status_code=401,
|
|
detail='You are not authorized to patch players. This event has been logged.'
|
|
)
|
|
|
|
try:
|
|
this_player = Player.get_by_id(player_id)
|
|
except Exception:
|
|
db.close()
|
|
raise HTTPException(status_code=404, detail=f'No player found with id {player_id}')
|
|
|
|
if cost is not None:
|
|
this_player.cost = cost
|
|
if name is not None:
|
|
this_player.p_name = name
|
|
if image is not None:
|
|
this_player.image = image
|
|
if image2 is not None:
|
|
if image2.lower() == 'false':
|
|
this_player.image2 = None
|
|
else:
|
|
this_player.image2 = image2
|
|
if mlbclub is not None:
|
|
this_player.mlbclub = mlbclub
|
|
if franchise is not None:
|
|
this_player.franchise = franchise
|
|
if cardset_id is not None:
|
|
try:
|
|
this_cardset = Cardset.get_by_id(cardset_id)
|
|
except Exception:
|
|
db.close()
|
|
raise HTTPException(status_code=404, detail=f'No cardset found with id {cardset_id}')
|
|
this_player.cardset = this_cardset
|
|
if rarity_id is not None:
|
|
try:
|
|
this_rarity = Rarity.get_by_id(rarity_id)
|
|
except Exception:
|
|
db.close()
|
|
raise HTTPException(status_code=404, detail=f'No rarity found with id {rarity_id}')
|
|
this_player.rarity = this_rarity
|
|
if pos_1 is not None:
|
|
if pos_1 in ['None', 'False', '']:
|
|
this_player.pos_1 = None
|
|
else:
|
|
this_player.pos_1 = pos_1
|
|
if pos_2 is not None:
|
|
if pos_2 in ['None', 'False', '']:
|
|
this_player.pos_2 = None
|
|
else:
|
|
this_player.pos_2 = pos_2
|
|
if pos_3 is not None:
|
|
if pos_3 in ['None', 'False', '']:
|
|
this_player.pos_3 = None
|
|
else:
|
|
this_player.pos_3 = pos_3
|
|
if pos_4 is not None:
|
|
if pos_4 in ['None', 'False', '']:
|
|
this_player.pos_4 = None
|
|
else:
|
|
this_player.pos_4 = pos_4
|
|
if pos_5 is not None:
|
|
if pos_5 in ['None', 'False', '']:
|
|
this_player.pos_5 = None
|
|
else:
|
|
this_player.pos_5 = pos_5
|
|
if pos_6 is not None:
|
|
if pos_6 in ['None', 'False', '']:
|
|
this_player.pos_6 = None
|
|
else:
|
|
this_player.pos_6 = pos_6
|
|
if pos_7 is not None:
|
|
if pos_7 in ['None', 'False', '']:
|
|
this_player.pos_7 = None
|
|
else:
|
|
this_player.pos_7 = pos_7
|
|
if pos_8 is not None:
|
|
if pos_8 in ['None', 'False', '']:
|
|
this_player.pos_8 = None
|
|
else:
|
|
this_player.pos_8 = pos_8
|
|
if headshot is not None:
|
|
this_player.headshot = headshot
|
|
if vanity_card is not None:
|
|
this_player.vanity_card = vanity_card
|
|
if strat_code is not None:
|
|
this_player.strat_code = strat_code
|
|
if bbref_id is not None:
|
|
this_player.bbref_id = bbref_id
|
|
if fangr_id is not None:
|
|
this_player.fangr_id = fangr_id
|
|
if description is not None:
|
|
this_player.description = description
|
|
if mlbplayer_id is not None:
|
|
this_player.mlbplayer_id = mlbplayer_id
|
|
|
|
if this_player.save() == 1:
|
|
return_val = model_to_dict(this_player)
|
|
db.close()
|
|
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.put('')
|
|
async def put_players(players: PlayerModel, token: str = Depends(oauth2_scheme)):
|
|
if not valid_token(token):
|
|
logging.warning(f'Bad Token: {token}')
|
|
db.close()
|
|
raise HTTPException(
|
|
status_code=401,
|
|
detail='You are not authorized to post players. This event has been logged.'
|
|
)
|
|
|
|
new_players = []
|
|
for x in players.players:
|
|
# this_player = Player(
|
|
# player_id=x.player_id,
|
|
# p_name=x.p_name,
|
|
# cost=x.cost,
|
|
# image=x.image,
|
|
# image2=x.image2,
|
|
# mlbclub=x.mlbclub,
|
|
# franchise=x.franchise,
|
|
# cardset_id=x.cardset_id,
|
|
# rarity_id=x.rarity_id,
|
|
# set_num=x.set_num,
|
|
# pos_1=x.pos_1,
|
|
# pos_2=x.pos_2,
|
|
# pos_3=x.pos_3,
|
|
# pos_4=x.pos_4,
|
|
# pos_5=x.pos_5,
|
|
# pos_6=x.pos_6,
|
|
# pos_7=x.pos_7,
|
|
# pos_8=x.pos_8,
|
|
# headshot=x.headshot,
|
|
# vanity_card=x.vanity_card,
|
|
# strat_code=x.strat_code,
|
|
# fangr_id=x.fangr_id,
|
|
# bbref_id=x.bbref_id,
|
|
# description=x.description
|
|
# )
|
|
# new_players.append(this_player)
|
|
new_players.append({
|
|
'player_id': x.player_id,
|
|
'p_name': x.p_name,
|
|
'cost': x.cost,
|
|
'image': x.image,
|
|
'image2': x.image2,
|
|
'mlbclub': x.mlbclub.title(),
|
|
'franchise': normalize_franchise(x.franchise),
|
|
'cardset_id': x.cardset_id,
|
|
'rarity_id': x.rarity_id,
|
|
'set_num': x.set_num,
|
|
'pos_1': x.pos_1,
|
|
'pos_2': x.pos_2,
|
|
'pos_3': x.pos_3,
|
|
'pos_4': x.pos_4,
|
|
'pos_5': x.pos_5,
|
|
'pos_6': x.pos_6,
|
|
'pos_7': x.pos_7,
|
|
'pos_8': x.pos_8,
|
|
'headshot': x.headshot,
|
|
'vanity_card': x.vanity_card,
|
|
'strat_code': x.strat_code,
|
|
'fangr_id': x.fangr_id,
|
|
'bbref_id': x.bbref_id,
|
|
'description': x.description
|
|
})
|
|
|
|
logging.debug(f'new_players: {new_players}')
|
|
|
|
with db.atomic():
|
|
# Player.bulk_create(new_players, batch_size=15)
|
|
for batch in chunked(new_players, 15):
|
|
logging.debug(f'batch: {batch}')
|
|
Player.insert_many(batch).on_conflict_replace().execute()
|
|
db.close()
|
|
|
|
# sheets.update_all_players(SHEETS_AUTH)
|
|
raise HTTPException(status_code=200, detail=f'{len(new_players)} players have been added')
|
|
|
|
|
|
@router.post('')
|
|
async def post_players(new_player: PlayerPydantic, token: str = Depends(oauth2_scheme)):
|
|
if not valid_token(token):
|
|
logging.warning(f'Bad Token: {token}')
|
|
db.close()
|
|
raise HTTPException(
|
|
status_code=401,
|
|
detail='You are not authorized to post players. This event has been logged.'
|
|
)
|
|
|
|
dupe_query = Player.select().where(
|
|
(Player.bbref_id == new_player.bbref_id) & (Player.cardset_id == new_player.cardset_id)
|
|
)
|
|
if dupe_query.count() != 0:
|
|
db.close()
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f'This appears to be a duplicate with player {dupe_query[0].player_id}'
|
|
)
|
|
|
|
p_query = Player.select(Player.player_id).order_by(-Player.player_id).limit(1)
|
|
new_id = p_query[0].player_id + 1
|
|
|
|
new_player.player_id = new_id
|
|
p_id = Player.insert(new_player.dict()).execute()
|
|
|
|
return_val = model_to_dict(Player.get_by_id(p_id))
|
|
db.close()
|
|
return return_val
|
|
|
|
|
|
@router.post('/{player_id}/image-reset')
|
|
async def post_image_reset(player_id: int, dev: bool = False, token: str = Depends(oauth2_scheme)):
|
|
if not valid_token(token):
|
|
logging.warning(f'Bad Token: {token}')
|
|
db.close()
|
|
raise HTTPException(
|
|
status_code=401,
|
|
detail='You are not authorized to modify players. This event has been logged.'
|
|
)
|
|
|
|
this_player = Player.get_or_none(Player.player_id == player_id)
|
|
if this_player is None:
|
|
db.close()
|
|
raise HTTPException(status_code=404, detail=f'Player ID {player_id} not found')
|
|
|
|
now = datetime.datetime.now()
|
|
today_url = f'https://pd{"dev" if dev else ""}.manticorum.com/api/v2/players/{player_id}/' \
|
|
f'{"pitch" if "pitch" in this_player.image else "batt"}ingcard?d={now.year}-{now.month}-{now.day}'
|
|
logging.debug(f'image1 url: {today_url}')
|
|
this_player.image = today_url
|
|
|
|
if this_player.image2 is not None:
|
|
today_url = f'https://pd{"dev" if dev else ""}.manticorum.com/api/v2/players/{player_id}/' \
|
|
f'{"pitch" if "pitch" in this_player.image2 else "batt"}ingcard?d={now.year}-{now.month}-{now.day}'
|
|
logging.debug(f'image2 url: {today_url}')
|
|
this_player.image2 = today_url
|
|
|
|
this_player.save()
|
|
r_player = model_to_dict(this_player)
|
|
db.close()
|
|
return r_player
|
|
|
|
|
|
@router.delete('/{player_id}')
|
|
async def delete_player(player_id, token: str = Depends(oauth2_scheme)):
|
|
if not valid_token(token):
|
|
logging.warning(f'Bad Token: {token}')
|
|
db.close()
|
|
raise HTTPException(
|
|
status_code=401,
|
|
detail='You are not authorized to delete players. This event has been logged.'
|
|
)
|
|
|
|
try:
|
|
this_player = Player.get_by_id(player_id)
|
|
except Exception:
|
|
db.close()
|
|
raise HTTPException(status_code=404, detail=f'No player found with id {player_id}')
|
|
|
|
count = this_player.delete_instance()
|
|
db.close()
|
|
|
|
if count == 1:
|
|
raise HTTPException(status_code=200, detail=f'Player {player_id} has been deleted')
|
|
else:
|
|
raise HTTPException(status_code=500, detail=f'Player {player_id} was not deleted')
|