diff --git a/app/routers_v2/awards.py b/app/routers_v2/awards.py index 89ed4bc..774dab3 100644 --- a/app/routers_v2/awards.py +++ b/app/routers_v2/awards.py @@ -55,6 +55,7 @@ async def get_awards( all_awards = all_awards.where(Award.image == image) limit = max(0, min(limit, 500)) + total_count = all_awards.count() if not csv else 0 all_awards = all_awards.limit(limit) if csv: @@ -76,7 +77,7 @@ async def get_awards( return Response(content=return_val, media_type="text/csv") else: - return_val = {"count": all_awards.count(), "awards": []} + return_val = {"count": total_count, "awards": []} for x in all_awards: return_val["awards"].append(model_to_dict(x)) diff --git a/app/routers_v2/batstats.py b/app/routers_v2/batstats.py index c60949b..1180f32 100644 --- a/app/routers_v2/batstats.py +++ b/app/routers_v2/batstats.py @@ -6,14 +6,20 @@ import logging import pydantic from pandas import DataFrame -from ..db_engine import db, BattingStat, model_to_dict, fn, Card, Player, Current, DoesNotExist +from ..db_engine import ( + db, + BattingStat, + model_to_dict, + fn, + Card, + Player, + Current, + DoesNotExist, +) from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA -router = APIRouter( - prefix='/api/v2/batstats', - tags=['Pre-Season 7 Batting Stats'] -) +router = APIRouter(prefix="/api/v2/batstats", tags=["Pre-Season 7 Batting Stats"]) class BatStat(pydantic.BaseModel): @@ -50,7 +56,7 @@ class BatStat(pydantic.BaseModel): csc: Optional[int] = 0 week: int season: int - created: Optional[int] = int(datetime.timestamp(datetime.now())*1000) + created: Optional[int] = int(datetime.timestamp(datetime.now()) * 1000) game_id: int @@ -63,11 +69,20 @@ class BatStatReturnList(pydantic.BaseModel): stats: list[BatStat] -@router.get('', response_model=BatStatReturnList) +@router.get("", response_model=BatStatReturnList) async def get_batstats( - card_id: int = None, player_id: int = None, team_id: int = None, vs_team_id: int = None, week: int = None, - season: int = None, week_start: int = None, week_end: int = None, created: int = None, csv: bool = None, - limit: Optional[int] = 100): + card_id: int = None, + player_id: int = None, + team_id: int = None, + vs_team_id: int = None, + week: int = None, + season: int = None, + week_start: int = None, + week_end: int = None, + created: int = None, + csv: bool = None, + limit: Optional[int] = 100, +): all_stats = BattingStat.select().join(Card).join(Player).order_by(BattingStat.id) if season is not None: @@ -100,43 +115,122 @@ async def get_batstats( # raise HTTPException(status_code=404, detail=f'No batting stats found') limit = max(0, min(limit, 500)) + total_count = all_stats.count() if not csv else 0 all_stats = all_stats.limit(limit) if csv: - data_list = [['id', 'card_id', 'player_id', 'cardset', 'team', 'vs_team', 'pos', 'pa', 'ab', 'run', 'hit', 'rbi', 'double', - 'triple', 'hr', 'bb', 'so', 'hbp', 'sac', 'ibb', 'gidp', 'sb', 'cs', 'bphr', 'bpfo', 'bp1b', - 'bplo', 'xch', 'xhit', 'error', 'pb', 'sbc', 'csc', 'week', 'season', 'created', 'game_id', 'roster_num']] + data_list = [ + [ + "id", + "card_id", + "player_id", + "cardset", + "team", + "vs_team", + "pos", + "pa", + "ab", + "run", + "hit", + "rbi", + "double", + "triple", + "hr", + "bb", + "so", + "hbp", + "sac", + "ibb", + "gidp", + "sb", + "cs", + "bphr", + "bpfo", + "bp1b", + "bplo", + "xch", + "xhit", + "error", + "pb", + "sbc", + "csc", + "week", + "season", + "created", + "game_id", + "roster_num", + ] + ] for line in all_stats: data_list.append( [ - line.id, line.card.id, line.card.player.player_id, line.card.player.cardset.name, line.team.abbrev, line.vs_team.abbrev, - line.pos, line.pa, line.ab, line.run, line.hit, line.rbi, line.double, line.triple, line.hr, - line.bb, line.so, line.hbp, line.sac, line.ibb, line.gidp, line.sb, line.cs, line.bphr, line.bpfo, - line.bp1b, line.bplo, line.xch, line.xhit, line.error, line.pb, line.sbc, line.csc, line.week, - line.season, line.created, line.game_id, line.roster_num + line.id, + line.card.id, + line.card.player.player_id, + line.card.player.cardset.name, + line.team.abbrev, + line.vs_team.abbrev, + line.pos, + line.pa, + line.ab, + line.run, + line.hit, + line.rbi, + line.double, + line.triple, + line.hr, + line.bb, + line.so, + line.hbp, + line.sac, + line.ibb, + line.gidp, + line.sb, + line.cs, + line.bphr, + line.bpfo, + line.bp1b, + line.bplo, + line.xch, + line.xhit, + line.error, + line.pb, + line.sbc, + line.csc, + line.week, + line.season, + line.created, + line.game_id, + line.roster_num, ] ) return_val = DataFrame(data_list).to_csv(header=False, index=False) - return Response(content=return_val, media_type='text/csv') + return Response(content=return_val, media_type="text/csv") else: - return_val = {'count': all_stats.count(), 'stats': []} + return_val = {"count": total_count, "stats": []} for x in all_stats: - return_val['stats'].append(model_to_dict(x, recurse=False)) + return_val["stats"].append(model_to_dict(x, recurse=False)) return return_val -@router.get('/player/{player_id}', response_model=BatStat) +@router.get("/player/{player_id}", response_model=BatStat) async def get_player_stats( - player_id: int, team_id: int = None, vs_team_id: int = None, week_start: int = None, week_end: int = None, - csv: bool = None): - all_stats = (BattingStat - .select(fn.COUNT(BattingStat.created).alias('game_count')) - .join(Card) - .group_by(BattingStat.card) - .where(BattingStat.card.player == player_id)).scalar() + player_id: int, + team_id: int = None, + vs_team_id: int = None, + week_start: int = None, + week_end: int = None, + csv: bool = None, +): + all_stats = ( + BattingStat.select(fn.COUNT(BattingStat.created).alias("game_count")) + .join(Card) + .group_by(BattingStat.card) + .where(BattingStat.card.player == player_id) + ).scalar() if team_id is not None: all_stats = all_stats.where(BattingStat.team_id == team_id) @@ -150,37 +244,82 @@ async def get_player_stats( if csv: data_list = [ [ - 'pa', 'ab', 'run', 'hit', 'rbi', 'double', 'triple', 'hr', 'bb', 'so', 'hbp', 'sac', 'ibb', 'gidp', - 'sb', 'cs', 'bphr', 'bpfo', 'bp1b', 'bplo', 'xch', 'xhit', 'error', 'pb', 'sbc', 'csc', - ],[ - all_stats.pa_sum, all_stats.ab_sum, all_stats.run, all_stats.hit_sum, all_stats.rbi_sum, - all_stats.double_sum, all_stats.triple_sum, all_stats.hr_sum, all_stats.bb_sum, all_stats.so_sum, - all_stats.hbp_sum, all_stats.sac, all_stats.ibb_sum, all_stats.gidp_sum, all_stats.sb_sum, - all_stats.cs_sum, all_stats.bphr_sum, all_stats.bpfo_sum, all_stats.bp1b_sum, all_stats.bplo_sum, - all_stats.xch, all_stats.xhit_sum, all_stats.error_sum, all_stats.pb_sum, all_stats.sbc_sum, - all_stats.csc_sum - ] + "pa", + "ab", + "run", + "hit", + "rbi", + "double", + "triple", + "hr", + "bb", + "so", + "hbp", + "sac", + "ibb", + "gidp", + "sb", + "cs", + "bphr", + "bpfo", + "bp1b", + "bplo", + "xch", + "xhit", + "error", + "pb", + "sbc", + "csc", + ], + [ + all_stats.pa_sum, + all_stats.ab_sum, + all_stats.run, + all_stats.hit_sum, + all_stats.rbi_sum, + all_stats.double_sum, + all_stats.triple_sum, + all_stats.hr_sum, + all_stats.bb_sum, + all_stats.so_sum, + all_stats.hbp_sum, + all_stats.sac, + all_stats.ibb_sum, + all_stats.gidp_sum, + all_stats.sb_sum, + all_stats.cs_sum, + all_stats.bphr_sum, + all_stats.bpfo_sum, + all_stats.bp1b_sum, + all_stats.bplo_sum, + all_stats.xch, + all_stats.xhit_sum, + all_stats.error_sum, + all_stats.pb_sum, + all_stats.sbc_sum, + all_stats.csc_sum, + ], ] return_val = DataFrame(data_list).to_csv(header=False, index=False) - return Response(content=return_val, media_type='text/csv') + return Response(content=return_val, media_type="text/csv") else: - logging.debug(f'stat pull query: {all_stats}\n') + logging.debug(f"stat pull query: {all_stats}\n") # logging.debug(f'result 0: {all_stats[0]}\n') for x in all_stats: - logging.debug(f'this_line: {model_to_dict(x)}') + logging.debug(f"this_line: {model_to_dict(x)}") return_val = model_to_dict(all_stats[0]) return return_val -@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("", include_in_schema=PRIVATE_IN_SCHEMA) async def post_batstats(stats: BattingStatModel, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logging.warning('Bad Token: [REDACTED]') + logging.warning("Bad Token: [REDACTED]") raise HTTPException( status_code=401, - detail='You are not authorized to post stats. This event has been logged.' + detail="You are not authorized to post stats. This event has been logged.", ) new_stats = [] @@ -219,36 +358,40 @@ async def post_batstats(stats: BattingStatModel, token: str = Depends(oauth2_sch csc=x.csc, week=x.week, season=x.season, - created=datetime.fromtimestamp(x.created / 1000) if x.created else datetime.now(), - game_id=x.game_id + created=datetime.fromtimestamp(x.created / 1000) + if x.created + else datetime.now(), + game_id=x.game_id, ) new_stats.append(this_stat) with db.atomic(): BattingStat.bulk_create(new_stats, batch_size=15) - raise HTTPException(status_code=200, detail=f'{len(new_stats)} batting lines have been added') + raise HTTPException( + status_code=200, detail=f"{len(new_stats)} batting lines have been added" + ) -@router.delete('/{stat_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.delete("/{stat_id}", include_in_schema=PRIVATE_IN_SCHEMA) async def delete_batstat(stat_id, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logging.warning('Bad Token: [REDACTED]') + logging.warning("Bad Token: [REDACTED]") raise HTTPException( status_code=401, - detail='You are not authorized to delete stats. This event has been logged.' + detail="You are not authorized to delete stats. This event has been logged.", ) try: this_stat = BattingStat.get_by_id(stat_id) except DoesNotExist: - raise HTTPException(status_code=404, detail=f'No stat found with id {stat_id}') + raise HTTPException(status_code=404, detail=f"No stat found with id {stat_id}") count = this_stat.delete_instance() if count == 1: - raise HTTPException(status_code=200, detail=f'Stat {stat_id} has been deleted') + raise HTTPException(status_code=200, detail=f"Stat {stat_id} has been deleted") else: - raise HTTPException(status_code=500, detail=f'Stat {stat_id} was not deleted') + raise HTTPException(status_code=500, detail=f"Stat {stat_id} was not deleted") # @app.get('/api/v1/plays/batting') @@ -453,4 +596,3 @@ async def delete_batstat(stat_id, token: str = Depends(oauth2_scheme)): # } # db.close() # return return_stats - diff --git a/app/routers_v2/battingcardratings.py b/app/routers_v2/battingcardratings.py index 4196dc8..d881d04 100644 --- a/app/routers_v2/battingcardratings.py +++ b/app/routers_v2/battingcardratings.py @@ -179,6 +179,7 @@ async def get_card_ratings( ) all_ratings = all_ratings.where(BattingCardRatings.battingcard << set_cards) + total_count = all_ratings.count() if not csv else 0 all_ratings = all_ratings.limit(max(0, min(limit, 500))) if csv: @@ -195,7 +196,7 @@ async def get_card_ratings( else: return_val = { - "count": all_ratings.count(), + "count": total_count, "ratings": [ model_to_dict(x, recurse=not short_output) for x in all_ratings ], diff --git a/app/routers_v2/events.py b/app/routers_v2/events.py index 68058f8..a5f3b1f 100644 --- a/app/routers_v2/events.py +++ b/app/routers_v2/events.py @@ -8,10 +8,7 @@ from ..db_engine import Event, model_to_dict, fn, DoesNotExist from ..dependencies import oauth2_scheme, valid_token -router = APIRouter( - prefix='/api/v2/events', - tags=['events'] -) +router = APIRouter(prefix="/api/v2/events", tags=["events"]) class EventModel(pydantic.BaseModel): @@ -23,78 +20,102 @@ class EventModel(pydantic.BaseModel): active: Optional[bool] = False -@router.get('') +@router.get("") async def v1_events_get( - name: Optional[str] = None, in_desc: Optional[str] = None, active: Optional[bool] = None, - csv: Optional[bool] = None, limit: Optional[int] = 100): + name: Optional[str] = None, + in_desc: Optional[str] = None, + active: Optional[bool] = None, + csv: Optional[bool] = None, + limit: Optional[int] = 100, +): all_events = Event.select().order_by(Event.id) if name is not None: all_events = all_events.where(fn.Lower(Event.name) == name.lower()) if in_desc is not None: all_events = all_events.where( - (fn.Lower(Event.short_desc).contains(in_desc.lower())) | - (fn.Lower(Event.long_desc).contains(in_desc.lower())) + (fn.Lower(Event.short_desc).contains(in_desc.lower())) + | (fn.Lower(Event.long_desc).contains(in_desc.lower())) ) if active is not None: all_events = all_events.where(Event.active == active) + total_count = all_events.count() if not csv else 0 all_events = all_events.limit(max(0, min(limit, 500))) if csv: - data_list = [['id', 'name', 'short_desc', 'long_desc', 'url', 'thumbnail', 'active']] + data_list = [ + ["id", "name", "short_desc", "long_desc", "url", "thumbnail", "active"] + ] for line in all_events: data_list.append( [ - line.id, line.name, line.short_desc, line.long_desc, line.url, line.thumbnail, line.active + line.id, + line.name, + line.short_desc, + line.long_desc, + line.url, + line.thumbnail, + line.active, ] ) return_val = DataFrame(data_list).to_csv(header=False, index=False) - return Response(content=return_val, media_type='text/csv') + return Response(content=return_val, media_type="text/csv") else: - return_val = {'count': all_events.count(), 'events': []} + return_val = {"count": total_count, "events": []} for x in all_events: - return_val['events'].append(model_to_dict(x)) + return_val["events"].append(model_to_dict(x)) return return_val -@router.get('/{event_id}') +@router.get("/{event_id}") async def v1_events_get_one(event_id, csv: Optional[bool] = False): try: this_event = Event.get_by_id(event_id) except DoesNotExist: - raise HTTPException(status_code=404, detail=f'No event found with id {event_id}') + raise HTTPException( + status_code=404, detail=f"No event found with id {event_id}" + ) if csv: data_list = [ - ['id', 'name', 'short_desc', 'long_desc', 'url', 'thumbnail', 'active'], - [this_event.id, this_event.name, this_event.short_desc, this_event.long_desc, this_event.url, - this_event.thumbnail, this_event.active] + ["id", "name", "short_desc", "long_desc", "url", "thumbnail", "active"], + [ + this_event.id, + this_event.name, + this_event.short_desc, + this_event.long_desc, + this_event.url, + this_event.thumbnail, + this_event.active, + ], ] return_val = DataFrame(data_list).to_csv(header=False, index=False) - return Response(content=return_val, media_type='text/csv') + return Response(content=return_val, media_type="text/csv") else: return_val = model_to_dict(this_event) return return_val -@router.post('') +@router.post("") async def v1_events_post(event: EventModel, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logging.warning('Bad Token: [REDACTED]') + logging.warning("Bad Token: [REDACTED]") raise HTTPException( status_code=401, - detail='You are not authorized to post events. This event has been logged.' + detail="You are not authorized to post events. This event has been logged.", ) dupe_event = Event.get_or_none(Event.name == event.name) if dupe_event: - raise HTTPException(status_code=400, detail=f'There is already an event using {event.name}') + raise HTTPException( + status_code=400, detail=f"There is already an event using {event.name}" + ) this_event = Event( name=event.name, @@ -102,7 +123,7 @@ async def v1_events_post(event: EventModel, token: str = Depends(oauth2_scheme)) long_desc=event.long_desc, url=event.url, thumbnail=event.thumbnail, - active=event.active + active=event.active, ) saved = this_event.save() @@ -112,25 +133,33 @@ async def v1_events_post(event: EventModel, token: str = Depends(oauth2_scheme)) else: raise HTTPException( status_code=418, - detail='Well slap my ass and call me a teapot; I could not save that cardset' + detail="Well slap my ass and call me a teapot; I could not save that cardset", ) -@router.patch('/{event_id}') +@router.patch("/{event_id}") async def v1_events_patch( - event_id, name: Optional[str] = None, short_desc: Optional[str] = None, long_desc: Optional[str] = None, - url: Optional[str] = None, thumbnail: Optional[str] = None, active: Optional[bool] = None, - token: str = Depends(oauth2_scheme)): + event_id, + name: Optional[str] = None, + short_desc: Optional[str] = None, + long_desc: Optional[str] = None, + url: Optional[str] = None, + thumbnail: Optional[str] = None, + active: Optional[bool] = None, + token: str = Depends(oauth2_scheme), +): if not valid_token(token): - logging.warning('Bad Token: [REDACTED]') + logging.warning("Bad Token: [REDACTED]") raise HTTPException( status_code=401, - detail='You are not authorized to patch events. This event has been logged.' + detail="You are not authorized to patch events. This event has been logged.", ) try: this_event = Event.get_by_id(event_id) except DoesNotExist: - raise HTTPException(status_code=404, detail=f'No event found with id {event_id}') + raise HTTPException( + status_code=404, detail=f"No event found with id {event_id}" + ) if name is not None: this_event.name = name @@ -151,26 +180,30 @@ async def v1_events_patch( else: raise HTTPException( status_code=418, - detail='Well slap my ass and call me a teapot; I could not save that event' + detail="Well slap my ass and call me a teapot; I could not save that event", ) -@router.delete('/{event_id}') +@router.delete("/{event_id}") async def v1_events_delete(event_id, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logging.warning('Bad Token: [REDACTED]') + logging.warning("Bad Token: [REDACTED]") raise HTTPException( status_code=401, - detail='You are not authorized to delete events. This event has been logged.' + detail="You are not authorized to delete events. This event has been logged.", ) try: this_event = Event.get_by_id(event_id) except DoesNotExist: - raise HTTPException(status_code=404, detail=f'No event found with id {event_id}') + raise HTTPException( + status_code=404, detail=f"No event found with id {event_id}" + ) count = this_event.delete_instance() if count == 1: - raise HTTPException(status_code=200, detail=f'Event {event_id} has been deleted') + raise HTTPException( + status_code=200, detail=f"Event {event_id} has been deleted" + ) else: - raise HTTPException(status_code=500, detail=f'Event {event_id} was not deleted') + raise HTTPException(status_code=500, detail=f"Event {event_id} was not deleted") diff --git a/app/routers_v2/gamerewards.py b/app/routers_v2/gamerewards.py index 0afb6ec..86448c6 100644 --- a/app/routers_v2/gamerewards.py +++ b/app/routers_v2/gamerewards.py @@ -43,6 +43,7 @@ async def v1_gamerewards_get( all_rewards = all_rewards.where(GameRewards.money == money) limit = max(0, min(limit, 500)) + total_count = all_rewards.count() if not csv else 0 all_rewards = all_rewards.limit(limit) if csv: @@ -61,7 +62,7 @@ async def v1_gamerewards_get( return Response(content=return_val, media_type="text/csv") else: - return_val = {"count": all_rewards.count(), "gamerewards": []} + return_val = {"count": total_count, "gamerewards": []} for x in all_rewards: return_val["gamerewards"].append(model_to_dict(x)) diff --git a/app/routers_v2/gauntletrewards.py b/app/routers_v2/gauntletrewards.py index 607a125..85ecb6a 100644 --- a/app/routers_v2/gauntletrewards.py +++ b/app/routers_v2/gauntletrewards.py @@ -48,9 +48,10 @@ async def v1_gauntletreward_get( all_rewards = all_rewards.order_by(-GauntletReward.loss_max, GauntletReward.win_num) limit = max(0, min(limit, 500)) + total_count = all_rewards.count() all_rewards = all_rewards.limit(limit) - return_val = {"count": all_rewards.count(), "rewards": []} + return_val = {"count": total_count, "rewards": []} for x in all_rewards: return_val["rewards"].append(model_to_dict(x)) diff --git a/app/routers_v2/mlbplayers.py b/app/routers_v2/mlbplayers.py index f2a0e67..37a38fc 100644 --- a/app/routers_v2/mlbplayers.py +++ b/app/routers_v2/mlbplayers.py @@ -102,6 +102,7 @@ async def get_players( if offense_col is not None: all_players = all_players.where(MlbPlayer.offense_col << offense_col) + total_count = all_players.count() if not csv else 0 all_players = all_players.limit(max(0, min(limit, 500))) if csv: @@ -109,7 +110,7 @@ async def get_players( return Response(content=return_val, media_type="text/csv") return_val = { - "count": all_players.count(), + "count": total_count, "players": [model_to_dict(x) for x in all_players], } return return_val diff --git a/app/routers_v2/pitchingcardratings.py b/app/routers_v2/pitchingcardratings.py index 294ef14..78153b4 100644 --- a/app/routers_v2/pitchingcardratings.py +++ b/app/routers_v2/pitchingcardratings.py @@ -169,6 +169,7 @@ async def get_card_ratings( ) all_ratings = all_ratings.where(PitchingCardRatings.pitchingcard << set_cards) + total_count = all_ratings.count() if not csv else 0 all_ratings = all_ratings.limit(max(0, min(limit, 500))) if csv: @@ -177,7 +178,7 @@ async def get_card_ratings( else: return_val = { - "count": all_ratings.count(), + "count": total_count, "ratings": [ model_to_dict(x, recurse=not short_output) for x in all_ratings ], diff --git a/app/routers_v2/pitstats.py b/app/routers_v2/pitstats.py index e540d8b..c03fc45 100644 --- a/app/routers_v2/pitstats.py +++ b/app/routers_v2/pitstats.py @@ -98,6 +98,7 @@ async def get_pit_stats( if gs is not None: all_stats = all_stats.where(PitchingStat.gs == 1 if gs else 0) + total_count = all_stats.count() if not csv else 0 all_stats = all_stats.limit(max(0, min(limit, 500))) # if all_stats.count() == 0: @@ -177,7 +178,7 @@ async def get_pit_stats( return Response(content=return_val, media_type="text/csv") else: - return_val = {"count": all_stats.count(), "stats": []} + return_val = {"count": total_count, "stats": []} for x in all_stats: return_val["stats"].append(model_to_dict(x, recurse=False)) diff --git a/app/routers_v2/results.py b/app/routers_v2/results.py index 4e41f7b..2ba50e9 100644 --- a/app/routers_v2/results.py +++ b/app/routers_v2/results.py @@ -142,6 +142,7 @@ async def get_results( all_results = all_results.order_by(Result.id) limit = max(0, min(limit, 500)) + total_count = all_results.count() if not csv else 0 all_results = all_results.limit(limit) # Not functional # if vs_ai is not None: @@ -201,7 +202,7 @@ async def get_results( return Response(content=return_val, media_type="text/csv") else: - return_val = {"count": all_results.count(), "results": []} + return_val = {"count": total_count, "results": []} for x in all_results: return_val["results"].append(model_to_dict(x)) diff --git a/app/routers_v2/rewards.py b/app/routers_v2/rewards.py index 48a50cc..ec63d83 100644 --- a/app/routers_v2/rewards.py +++ b/app/routers_v2/rewards.py @@ -34,9 +34,6 @@ async def get_rewards( ): all_rewards = Reward.select().order_by(Reward.id) - if all_rewards.count() == 0: - raise HTTPException(status_code=404, detail="There are no rewards to filter") - if name is not None: all_rewards = all_rewards.where(fn.Lower(Reward.name) == name.lower()) if team_id is not None: @@ -52,7 +49,8 @@ async def get_rewards( if week is not None: all_rewards = all_rewards.where(Reward.week == week) - if all_rewards.count() == 0: + total_count = all_rewards.count() + if total_count == 0: raise HTTPException(status_code=404, detail="No rewards found") limit = max(0, min(limit, 500)) @@ -69,7 +67,7 @@ async def get_rewards( return Response(content=return_val, media_type="text/csv") else: - return_val = {"count": all_rewards.count(), "rewards": []} + return_val = {"count": total_count, "rewards": []} for x in all_rewards: return_val["rewards"].append(model_to_dict(x, recurse=not flat)) diff --git a/app/routers_v2/scout_claims.py b/app/routers_v2/scout_claims.py index 2ae3d85..0751682 100644 --- a/app/routers_v2/scout_claims.py +++ b/app/routers_v2/scout_claims.py @@ -30,12 +30,14 @@ async def get_scout_claims( if claimed_by_team_id is not None: query = query.where(ScoutClaim.claimed_by_team_id == claimed_by_team_id) + total_count = query.count() + if limit is not None: limit = max(0, min(limit, 500)) query = query.limit(limit) results = [model_to_dict(x, recurse=False) for x in query] - return {"count": len(results), "results": results} + return {"count": total_count, "results": results} @router.get("/{claim_id}") diff --git a/app/routers_v2/scout_opportunities.py b/app/routers_v2/scout_opportunities.py index 0be0e63..474eff1 100644 --- a/app/routers_v2/scout_opportunities.py +++ b/app/routers_v2/scout_opportunities.py @@ -1 +1,127 @@ -aW1wb3J0IGpzb24KZnJvbSBkYXRldGltZSBpbXBvcnQgZGF0ZXRpbWUKZnJvbSBmYXN0YXBpIGltcG9ydCBBUElSb3V0ZXIsIERlcGVuZHMsIEhUVFBFeGNlcHRpb24KZnJvbSB0eXBpbmcgaW1wb3J0IE9wdGlvbmFsLCBMaXN0CmltcG9ydCBsb2dnaW5nCmltcG9ydCBweWRhbnRpYwoKZnJvbSAuLmRiX2VuZ2luZSBpbXBvcnQgU2NvdXRPcHBvcnR1bml0eSwgU2NvdXRDbGFpbSwgbW9kZWxfdG9fZGljdApmcm9tIC4uZGVwZW5kZW5jaWVzIGltcG9ydCBvYXV0aDJfc2NoZW1lLCB2YWxpZF90b2tlbgoKcm91dGVyID0gQVBJUm91dGVyKHByZWZpeD0iL2FwaS92Mi9zY291dF9vcHBvcnR1bml0aWVzIiwgdGFncz1bInNjb3V0X29wcG9ydHVuaXRpZXMiXSkKCgpjbGFzcyBTY291dE9wcG9ydHVuaXR5TW9kZWwocHlkYW50aWMuQmFzZU1vZGVsKToKICAgIHBhY2tfaWQ6IE9wdGlvbmFsW2ludF0gPSBOb25lCiAgICBvcGVuZXJfdGVhbV9pZDogaW50CiAgICBjYXJkX2lkczogTGlzdFtpbnRdCiAgICBleHBpcmVzX2F0OiBpbnQKICAgIGNyZWF0ZWQ6IE9wdGlvbmFsW2ludF0gPSBOb25lCgoKZGVmIG9wcG9ydHVuaXR5X3RvX2RpY3Qob3BwLCByZWN1cnNlPVRydWUpOgogICAgIiIiQ29udmVydCBhIFNjb3V0T3Bwb3J0dW5pdHkgdG8gZGljdCB3aXRoIGNhcmRfaWRzIGRlc2VyaWFsaXplZC4iIiIKICAgIHJlc3VsdCA9IG1vZGVsX3RvX2RpY3Qob3BwLCByZWN1cnNlPXJlY3Vyc2UpCiAgICBpZiBpc2luc3RhbmNlKHJlc3VsdC5nZXQoImNhcmRfaWRzIiksIHN0cik6CiAgICAgICAgcmVzdWx0WyJjYXJkX2lkcyJdID0ganNvbi5sb2FkcyhyZXN1bHRbImNhcmRfaWRzIl0pCiAgICByZXR1cm4gcmVzdWx0CgoKQHJvdXRlci5nZXQoIiIpCmFzeW5jIGRlZiBnZXRfc2NvdXRfb3Bwb3J0dW5pdGllcygKICAgIGNsYWltZWQ6IE9wdGlvbmFsW2Jvb2xdID0gTm9uZSwKICAgIGV4cGlyZWRfYmVmb3JlOiBPcHRpb25hbFtpbnRdID0gTm9uZSwKICAgIG9wZW5lcl90ZWFtX2lkOiBPcHRpb25hbFtpbnRdID0gTm9uZSwKICAgIGxpbWl0OiBPcHRpb25hbFtpbnRdID0gMTAwLAopOgoKICAgIGxpbWl0ID0gbWF4KDEsIG1pbihsaW1pdCwgNTAwKSkKICAgIHF1ZXJ5ID0gU2NvdXRPcHBvcnR1bml0eS5zZWxlY3QoKS5vcmRlcl9ieShTY291dE9wcG9ydHVuaXR5LmlkKQoKICAgIGlmIG9wZW5lcl90ZWFtX2lkIGlzIG5vdCBOb25lOgogICAgICAgIHF1ZXJ5ID0gcXVlcnkud2hlcmUoU2NvdXRPcHBvcnR1bml0eS5vcGVuZXJfdGVhbV9pZCA9PSBvcGVuZXJfdGVhbV9pZCkKCiAgICBpZiBleHBpcmVkX2JlZm9yZSBpcyBub3QgTm9uZToKICAgICAgICBxdWVyeSA9IHF1ZXJ5LndoZXJlKFNjb3V0T3Bwb3J0dW5pdHkuZXhwaXJlc19hdCA8IGV4cGlyZWRfYmVmb3JlKQoKICAgIGlmIGNsYWltZWQgaXMgbm90IE5vbmU6CiAgICAgICAgIyBDaGVjayB3aGV0aGVyIGFueSBzY291dF9jbGFpbXMgZXhpc3QgZm9yIGVhY2ggb3Bwb3J0dW5pdHkKICAgICAgICBjbGFpbV9zdWJxdWVyeSA9IFNjb3V0Q2xhaW0uc2VsZWN0KFNjb3V0Q2xhaW0uc2NvdXRfb3Bwb3J0dW5pdHkpCiAgICAgICAgaWYgY2xhaW1lZDoKICAgICAgICAgICAgcXVlcnkgPSBxdWVyeS53aGVyZShTY291dE9wcG9ydHVuaXR5LmlkLmluXyhjbGFpbV9zdWJxdWVyeSkpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcXVlcnkgPSBxdWVyeS53aGVyZShTY291dE9wcG9ydHVuaXR5LmlkLm5vdF9pbihjbGFpbV9zdWJxdWVyeSkpCgogICAgcXVlcnkgPSBxdWVyeS5saW1pdChsaW1pdCkKICAgIHJlc3VsdHMgPSBbb3Bwb3J0dW5pdHlfdG9fZGljdCh4LCByZWN1cnNlPUZhbHNlKSBmb3IgeCBpbiBxdWVyeV0KICAgIHJldHVybiB7ImNvdW50IjogbGVuKHJlc3VsdHMpLCAicmVzdWx0cyI6IHJlc3VsdHN9CgoKQHJvdXRlci5nZXQoIi97b3Bwb3J0dW5pdHlfaWR9IikKYXN5bmMgZGVmIGdldF9vbmVfc2NvdXRfb3Bwb3J0dW5pdHkob3Bwb3J0dW5pdHlfaWQ6IGludCk6CiAgICB0cnk6CiAgICAgICAgb3BwID0gU2NvdXRPcHBvcnR1bml0eS5nZXRfYnlfaWQob3Bwb3J0dW5pdHlfaWQpCiAgICBleGNlcHQgRXhjZXB0aW9uOgogICAgICAgIHJhaXNlIEhUVFBFeGNlcHRpb24oCiAgICAgICAgICAgIHN0YXR1c19jb2RlPTQwNCwKICAgICAgICAgICAgZGV0YWlsPWYiTm8gc2NvdXQgb3Bwb3J0dW5pdHkgZm91bmQgd2l0aCBpZCB7b3Bwb3J0dW5pdHlfaWR9IiwKICAgICAgICApCgogICAgcmV0dXJuIG9wcG9ydHVuaXR5X3RvX2RpY3Qob3BwKQoKCkByb3V0ZXIucG9zdCgiIikKYXN5bmMgZGVmIHBvc3Rfc2NvdXRfb3Bwb3J0dW5pdHkoCiAgICBvcHBvcnR1bml0eTogU2NvdXRPcHBvcnR1bml0eU1vZGVsLCB0b2tlbjogc3RyID0gRGVwZW5kcyhvYXV0aDJfc2NoZW1lKQopOgogICAgaWYgbm90IHZhbGlkX3Rva2VuKHRva2VuKToKICAgICAgICBsb2dnaW5nLndhcm5pbmcoZiJCYWQgVG9rZW46IHt0b2tlbn0iKQogICAgICAgIHJhaXNlIEhUVFBFeGNlcHRpb24oCiAgICAgICAgICAgIHN0YXR1c19jb2RlPTQwMSwKICAgICAgICAgICAgZGV0YWlsPSJZb3UgYXJlIG5vdCBhdXRob3JpemVkIHRvIHBvc3Qgc2NvdXQgb3Bwb3J0dW5pdGllcy4gVGhpcyBldmVudCBoYXMgYmVlbiBsb2dnZWQuIiwKICAgICAgICApCgogICAgb3BwX2RhdGEgPSBvcHBvcnR1bml0eS5kaWN0KCkKICAgIG9wcF9kYXRhWyJjYXJkX2lkcyJdID0ganNvbi5kdW1wcyhvcHBfZGF0YVsiY2FyZF9pZHMiXSkKICAgIGlmIG9wcF9kYXRhWyJjcmVhdGVkIl0gaXMgTm9uZToKICAgICAgICBvcHBfZGF0YVsiY3JlYXRlZCJdID0gaW50KGRhdGV0aW1lLnRpbWVzdGFtcChkYXRldGltZS5ub3coKSkgKiAxMDAwKQoKICAgIHRoaXNfb3BwID0gU2NvdXRPcHBvcnR1bml0eSgqKm9wcF9kYXRhKQogICAgc2F2ZWQgPSB0aGlzX29wcC5zYXZlKCkKCiAgICBpZiBzYXZlZCA9PSAxOgogICAgICAgIHJldHVybiBvcHBvcnR1bml0eV90b19kaWN0KHRoaXNfb3BwKQogICAgZWxzZToKICAgICAgICByYWlzZSBIVFRQRXhjZXB0aW9uKHN0YXR1c19jb2RlPTQxOCwgZGV0YWlsPSJDb3VsZCBub3Qgc2F2ZSBzY291dCBvcHBvcnR1bml0eSIpCgoKQHJvdXRlci5kZWxldGUoIi97b3Bwb3J0dW5pdHlfaWR9IikKYXN5bmMgZGVmIGRlbGV0ZV9zY291dF9vcHBvcnR1bml0eSgKICAgIG9wcG9ydHVuaXR5X2lkOiBpbnQsIHRva2VuOiBzdHIgPSBEZXBlbmRzKG9hdXRoMl9zY2hlbWUpCik6CiAgICBpZiBub3QgdmFsaWRfdG9rZW4odG9rZW4pOgogICAgICAgIGxvZ2dpbmcud2FybmluZyhmIkJhZCBUb2tlbjoge3Rva2VufSIpCiAgICAgICAgcmFpc2UgSFRUUEV4Y2VwdGlvbigKICAgICAgICAgICAgc3RhdHVzX2NvZGU9NDAxLAogICAgICAgICAgICBkZXRhaWw9IllvdSBhcmUgbm90IGF1dGhvcml6ZWQgdG8gZGVsZXRlIHNjb3V0IG9wcG9ydHVuaXRpZXMuIFRoaXMgZXZlbnQgaGFzIGJlZW4gbG9nZ2VkLiIsCiAgICAgICAgKQogICAgdHJ5OgogICAgICAgIG9wcCA9IFNjb3V0T3Bwb3J0dW5pdHkuZ2V0X2J5X2lkKG9wcG9ydHVuaXR5X2lkKQogICAgZXhjZXB0IEV4Y2VwdGlvbjoKICAgICAgICByYWlzZSBIVFRQRXhjZXB0aW9uKAogICAgICAgICAgICBzdGF0dXNfY29kZT00MDQsCiAgICAgICAgICAgIGRldGFpbD1mIk5vIHNjb3V0IG9wcG9ydHVuaXR5IGZvdW5kIHdpdGggaWQge29wcG9ydHVuaXR5X2lkfSIsCiAgICAgICAgKQoKICAgIGNvdW50ID0gb3BwLmRlbGV0ZV9pbnN0YW5jZSgpCiAgICBpZiBjb3VudCA9PSAxOgogICAgICAgIHJhaXNlIEhUVFBFeGNlcHRpb24oCiAgICAgICAgICAgIHN0YXR1c19jb2RlPTIwMCwKICAgICAgICAgICAgZGV0YWlsPWYiU2NvdXQgb3Bwb3J0dW5pdHkge29wcG9ydHVuaXR5X2lkfSBoYXMgYmVlbiBkZWxldGVkIiwKICAgICAgICApCiAgICBlbHNlOgogICAgICAgIHJhaXNlIEhUVFBFeGNlcHRpb24oCiAgICAgICAgICAgIHN0YXR1c19jb2RlPTUwMCwKICAgICAgICAgICAgZGV0YWlsPWYiU2NvdXQgb3Bwb3J0dW5pdHkge29wcG9ydHVuaXR5X2lkfSB3YXMgbm90IGRlbGV0ZWQiLAogICAgICAgICkK \ No newline at end of file +import json +from datetime import datetime +from fastapi import APIRouter, Depends, HTTPException +from typing import Optional, List +import logging +import pydantic + +from ..db_engine import ScoutOpportunity, ScoutClaim, model_to_dict +from ..dependencies import oauth2_scheme, valid_token + +router = APIRouter(prefix="/api/v2/scout_opportunities", tags=["scout_opportunities"]) + + +class ScoutOpportunityModel(pydantic.BaseModel): + pack_id: Optional[int] = None + opener_team_id: int + card_ids: List[int] + expires_at: int + created: Optional[int] = None + + +def opportunity_to_dict(opp, recurse=True): + """Convert a ScoutOpportunity to dict with card_ids deserialized.""" + result = model_to_dict(opp, recurse=recurse) + if isinstance(result.get("card_ids"), str): + result["card_ids"] = json.loads(result["card_ids"]) + return result + + +@router.get("") +async def get_scout_opportunities( + claimed: Optional[bool] = None, + expired_before: Optional[int] = None, + opener_team_id: Optional[int] = None, + limit: Optional[int] = 100, +): + + limit = max(0, min(limit, 500)) + query = ScoutOpportunity.select().order_by(ScoutOpportunity.id) + + if opener_team_id is not None: + query = query.where(ScoutOpportunity.opener_team_id == opener_team_id) + + if expired_before is not None: + query = query.where(ScoutOpportunity.expires_at < expired_before) + + if claimed is not None: + # Check whether any scout_claims exist for each opportunity + claim_subquery = ScoutClaim.select(ScoutClaim.scout_opportunity) + if claimed: + query = query.where(ScoutOpportunity.id.in_(claim_subquery)) + else: + query = query.where(ScoutOpportunity.id.not_in(claim_subquery)) + + total_count = query.count() + query = query.limit(limit) + results = [opportunity_to_dict(x, recurse=False) for x in query] + return {"count": total_count, "results": results} + + +@router.get("/{opportunity_id}") +async def get_one_scout_opportunity(opportunity_id: int): + try: + opp = ScoutOpportunity.get_by_id(opportunity_id) + except Exception: + raise HTTPException( + status_code=404, + detail=f"No scout opportunity found with id {opportunity_id}", + ) + + return opportunity_to_dict(opp) + + +@router.post("") +async def post_scout_opportunity( + opportunity: ScoutOpportunityModel, token: str = Depends(oauth2_scheme) +): + if not valid_token(token): + logging.warning(f"Bad Token: {token}") + raise HTTPException( + status_code=401, + detail="You are not authorized to post scout opportunities. This event has been logged.", + ) + + opp_data = opportunity.dict() + opp_data["card_ids"] = json.dumps(opp_data["card_ids"]) + if opp_data["created"] is None: + opp_data["created"] = int(datetime.timestamp(datetime.now()) * 1000) + + this_opp = ScoutOpportunity(**opp_data) + saved = this_opp.save() + + if saved == 1: + return opportunity_to_dict(this_opp) + else: + raise HTTPException(status_code=418, detail="Could not save scout opportunity") + + +@router.delete("/{opportunity_id}") +async def delete_scout_opportunity( + opportunity_id: int, token: str = Depends(oauth2_scheme) +): + if not valid_token(token): + logging.warning(f"Bad Token: {token}") + raise HTTPException( + status_code=401, + detail="You are not authorized to delete scout opportunities. This event has been logged.", + ) + try: + opp = ScoutOpportunity.get_by_id(opportunity_id) + except Exception: + raise HTTPException( + status_code=404, + detail=f"No scout opportunity found with id {opportunity_id}", + ) + + count = opp.delete_instance() + if count == 1: + raise HTTPException( + status_code=200, + detail=f"Scout opportunity {opportunity_id} has been deleted", + ) + else: + raise HTTPException( + status_code=500, + detail=f"Scout opportunity {opportunity_id} was not deleted", + ) diff --git a/app/routers_v2/stratgame.py b/app/routers_v2/stratgame.py index 73f44cf..e6492c3 100644 --- a/app/routers_v2/stratgame.py +++ b/app/routers_v2/stratgame.py @@ -78,6 +78,7 @@ async def get_games( StratGame.game_type.contains(f"gauntlet-{gauntlet_id}") ) + total_count = all_games.count() if not csv else 0 all_games = all_games.limit(max(0, min(limit, 500))) if csv: @@ -107,7 +108,7 @@ async def get_games( return Response(content=output.to_csv(index=False), media_type="text/csv") return_val = { - "count": all_games.count(), + "count": total_count, "games": [model_to_dict(x, recurse=not short_output) for x in all_games], } return return_val