fix: standardize all collection POST routes to use trailing slash
All checks were successful
Build Docker Image / build (pull_request) Successful in 3m38s

aiohttp follows 307 redirects but converts POST to GET, silently
dropping the request body. Standardize all @router.post('') to
@router.post('/') so the canonical URL always has a trailing slash,
preventing 307 redirects when clients POST with trailing slashes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2026-03-09 19:34:28 -05:00
parent bc36970aeb
commit 9ec69f9f2c
20 changed files with 1717 additions and 1212 deletions

View File

@ -4,15 +4,17 @@ import logging
import pydantic import pydantic
from ..db_engine import db, Award, Team, Player, Manager, model_to_dict, chunked, fn from ..db_engine import db, Award, Team, Player, Manager, model_to_dict, chunked, fn
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors from ..dependencies import (
oauth2_scheme,
logger = logging.getLogger('discord_app') valid_token,
PRIVATE_IN_SCHEMA,
router = APIRouter( handle_db_errors,
prefix='/api/v3/awards',
tags=['awards']
) )
logger = logging.getLogger("discord_app")
router = APIRouter(prefix="/api/v3/awards", tags=["awards"])
class AwardModel(pydantic.BaseModel): class AwardModel(pydantic.BaseModel):
name: str name: str
@ -30,13 +32,18 @@ class AwardList(pydantic.BaseModel):
awards: List[AwardModel] awards: List[AwardModel]
@router.get('') @router.get("")
@handle_db_errors @handle_db_errors
async def get_awards( async def get_awards(
name: list = Query(default=None), season: Optional[int] = None, timing: Optional[str] = None, name: list = Query(default=None),
manager_id: list = Query(default=None), player_id: list = Query(default=None), season: Optional[int] = None,
team_id: list = Query(default=None), short_output: Optional[bool] = False, timing: Optional[str] = None,
player_name: list = Query(default=None)): manager_id: list = Query(default=None),
player_id: list = Query(default=None),
team_id: list = Query(default=None),
short_output: Optional[bool] = False,
player_name: list = Query(default=None),
):
all_awards = Award.select() all_awards = Award.select()
if name is not None: if name is not None:
@ -61,39 +68,47 @@ async def get_awards(
all_awards = all_awards.where(Award.player << all_players) all_awards = all_awards.where(Award.player << all_players)
return_awards = { return_awards = {
'count': all_awards.count(), "count": all_awards.count(),
'awards': [model_to_dict(x, recurse=not short_output) for x in all_awards] "awards": [model_to_dict(x, recurse=not short_output) for x in all_awards],
} }
db.close() db.close()
return return_awards return return_awards
@router.get('/{award_id}') @router.get("/{award_id}")
@handle_db_errors @handle_db_errors
async def get_one_award(award_id: int, short_output: Optional[bool] = False): async def get_one_award(award_id: int, short_output: Optional[bool] = False):
this_award = Award.get_or_none(Award.id == award_id) this_award = Award.get_or_none(Award.id == award_id)
if this_award is None: if this_award is None:
db.close() db.close()
raise HTTPException(status_code=404, detail=f'Award ID {award_id} not found') raise HTTPException(status_code=404, detail=f"Award ID {award_id} not found")
db.close() db.close()
return model_to_dict(this_award, recurse=not short_output) return model_to_dict(this_award, recurse=not short_output)
@router.patch('/{award_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/{award_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def patch_award( async def patch_award(
award_id: int, name: Optional[str] = None, season: Optional[int] = None, timing: Optional[str] = None, award_id: int,
image: Optional[str] = None, manager1_id: Optional[int] = None, manager2_id: Optional[int] = None, name: Optional[str] = None,
player_id: Optional[int] = None, team_id: Optional[int] = None, token: str = Depends(oauth2_scheme)): season: Optional[int] = None,
timing: Optional[str] = None,
image: Optional[str] = None,
manager1_id: Optional[int] = None,
manager2_id: Optional[int] = None,
player_id: Optional[int] = None,
team_id: Optional[int] = None,
token: str = Depends(oauth2_scheme),
):
if not valid_token(token): if not valid_token(token):
logger.warning(f'patch_player - Bad Token: {token}') logger.warning(f"patch_player - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_award = Award.get_or_none(Award.id == award_id) this_award = Award.get_or_none(Award.id == award_id)
if this_award is None: if this_award is None:
db.close() db.close()
raise HTTPException(status_code=404, detail=f'Award ID {award_id} not found') raise HTTPException(status_code=404, detail=f"Award ID {award_id} not found")
if name is not None: if name is not None:
this_award.name = name this_award.name = name
@ -118,26 +133,43 @@ async def patch_award(
return r_award return r_award
else: else:
db.close() db.close()
raise HTTPException(status_code=500, detail=f'Unable to patch award {award_id}') raise HTTPException(status_code=500, detail=f"Unable to patch award {award_id}")
@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) @router.post("/", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def post_award(award_list: AwardList, token: str = Depends(oauth2_scheme)): async def post_award(award_list: AwardList, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'patch_player - Bad Token: {token}') logger.warning(f"patch_player - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
new_awards = [] new_awards = []
for x in award_list.awards: for x in award_list.awards:
if x.manager1_id is not None and Manager.get_or_none(Manager.id == x.manager1_id) is None: if (
raise HTTPException(status_code=404, detail=f'Manager ID {x.manager1_id} not found') x.manager1_id is not None
if x.manager2_id is not None and Manager.get_or_none(Manager.id == x.manager2_id) is None: and Manager.get_or_none(Manager.id == x.manager1_id) is None
raise HTTPException(status_code=404, detail=f'Manager ID {x.manager2_id} not found') ):
if x.player_id is not None and Player.get_or_none(Player.id == x.player_id) is None: raise HTTPException(
raise HTTPException(status_code=404, detail=f'Player ID {x.player_id} not found') status_code=404, detail=f"Manager ID {x.manager1_id} not found"
)
if (
x.manager2_id is not None
and Manager.get_or_none(Manager.id == x.manager2_id) is None
):
raise HTTPException(
status_code=404, detail=f"Manager ID {x.manager2_id} not found"
)
if (
x.player_id is not None
and Player.get_or_none(Player.id == x.player_id) is None
):
raise HTTPException(
status_code=404, detail=f"Player ID {x.player_id} not found"
)
if x.team_id is not None and Team.get_or_none(Team.id == x.team_id) is None: if x.team_id is not None and Team.get_or_none(Team.id == x.team_id) is None:
raise HTTPException(status_code=404, detail=f'Team ID {x.team_id} not found') raise HTTPException(
status_code=404, detail=f"Team ID {x.team_id} not found"
)
new_awards.append(x.dict()) new_awards.append(x.dict())
@ -146,29 +178,27 @@ async def post_award(award_list: AwardList, token: str = Depends(oauth2_scheme))
Award.insert_many(batch).on_conflict_ignore().execute() Award.insert_many(batch).on_conflict_ignore().execute()
db.close() db.close()
return f'Inserted {len(new_awards)} awards' return f"Inserted {len(new_awards)} awards"
@router.delete('/{award_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.delete("/{award_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def delete_award(award_id: int, token: str = Depends(oauth2_scheme)): async def delete_award(award_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'patch_player - Bad Token: {token}') logger.warning(f"patch_player - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_award = Award.get_or_none(Award.id == award_id) this_award = Award.get_or_none(Award.id == award_id)
if this_award is None: if this_award is None:
db.close() db.close()
raise HTTPException(status_code=404, detail=f'Award ID {award_id} not found') raise HTTPException(status_code=404, detail=f"Award ID {award_id} not found")
count = this_award.delete_instance() count = this_award.delete_instance()
db.close() db.close()
if count == 1: if count == 1:
return f'Award {award_id} has been deleted' return f"Award {award_id} has been deleted"
else: else:
raise HTTPException(status_code=500, detail=f'Award {award_id} could not be deleted') raise HTTPException(
status_code=500, detail=f"Award {award_id} could not be deleted"
)

View File

@ -3,15 +3,27 @@ from typing import List, Optional, Literal
import logging import logging
import pydantic import pydantic
from ..db_engine import db, BattingStat, Team, Player, Current, model_to_dict, chunked, fn, per_season_weeks from ..db_engine import (
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors db,
BattingStat,
logger = logging.getLogger('discord_app') Team,
Player,
router = APIRouter( Current,
prefix='/api/v3/battingstats', model_to_dict,
tags=['battingstats'] chunked,
fn,
per_season_weeks,
) )
from ..dependencies import (
oauth2_scheme,
valid_token,
PRIVATE_IN_SCHEMA,
handle_db_errors,
)
logger = logging.getLogger("discord_app")
router = APIRouter(prefix="/api/v3/battingstats", tags=["battingstats"])
class BatStatModel(pydantic.BaseModel): class BatStatModel(pydantic.BaseModel):
@ -60,29 +72,37 @@ class BatStatList(pydantic.BaseModel):
stats: List[BatStatModel] stats: List[BatStatModel]
@router.get('') @router.get("")
@handle_db_errors @handle_db_errors
async def get_batstats( async def get_batstats(
season: int, s_type: Optional[str] = 'regular', team_abbrev: list = Query(default=None), season: int,
player_name: list = Query(default=None), player_id: list = Query(default=None), s_type: Optional[str] = "regular",
week_start: Optional[int] = None, week_end: Optional[int] = None, game_num: list = Query(default=None), team_abbrev: list = Query(default=None),
position: list = Query(default=None), limit: Optional[int] = None, sort: Optional[str] = None, player_name: list = Query(default=None),
short_output: Optional[bool] = True): player_id: list = Query(default=None),
if 'post' in s_type.lower(): week_start: Optional[int] = None,
week_end: Optional[int] = None,
game_num: list = Query(default=None),
position: list = Query(default=None),
limit: Optional[int] = None,
sort: Optional[str] = None,
short_output: Optional[bool] = True,
):
if "post" in s_type.lower():
all_stats = BattingStat.post_season(season) all_stats = BattingStat.post_season(season)
if all_stats.count() == 0: if all_stats.count() == 0:
db.close() db.close()
return {'count': 0, 'stats': []} return {"count": 0, "stats": []}
elif s_type.lower() in ['combined', 'total', 'all']: elif s_type.lower() in ["combined", "total", "all"]:
all_stats = BattingStat.combined_season(season) all_stats = BattingStat.combined_season(season)
if all_stats.count() == 0: if all_stats.count() == 0:
db.close() db.close()
return {'count': 0, 'stats': []} return {"count": 0, "stats": []}
else: else:
all_stats = BattingStat.regular_season(season) all_stats = BattingStat.regular_season(season)
if all_stats.count() == 0: if all_stats.count() == 0:
db.close() db.close()
return {'count': 0, 'stats': []} return {"count": 0, "stats": []}
if position is not None: if position is not None:
all_stats = all_stats.where(BattingStat.pos << [x.upper() for x in position]) all_stats = all_stats.where(BattingStat.pos << [x.upper() for x in position])
@ -93,7 +113,9 @@ async def get_batstats(
if player_id: if player_id:
all_stats = all_stats.where(BattingStat.player_id << player_id) all_stats = all_stats.where(BattingStat.player_id << player_id)
else: else:
p_query = Player.select_season(season).where(fn.Lower(Player.name) << [x.lower() for x in player_name]) p_query = Player.select_season(season).where(
fn.Lower(Player.name) << [x.lower() for x in player_name]
)
all_stats = all_stats.where(BattingStat.player << p_query) all_stats = all_stats.where(BattingStat.player << p_query)
if game_num: if game_num:
all_stats = all_stats.where(BattingStat.game == game_num) all_stats = all_stats.where(BattingStat.game == game_num)
@ -108,21 +130,19 @@ async def get_batstats(
db.close() db.close()
raise HTTPException( raise HTTPException(
status_code=404, status_code=404,
detail=f'Start week {start} is after end week {end} - cannot pull stats' detail=f"Start week {start} is after end week {end} - cannot pull stats",
) )
all_stats = all_stats.where( all_stats = all_stats.where((BattingStat.week >= start) & (BattingStat.week <= end))
(BattingStat.week >= start) & (BattingStat.week <= end)
)
if limit: if limit:
all_stats = all_stats.limit(limit) all_stats = all_stats.limit(limit)
if sort: if sort:
if sort == 'newest': if sort == "newest":
all_stats = all_stats.order_by(-BattingStat.week, -BattingStat.game) all_stats = all_stats.order_by(-BattingStat.week, -BattingStat.game)
return_stats = { return_stats = {
'count': all_stats.count(), "count": all_stats.count(),
'stats': [model_to_dict(x, recurse=not short_output) for x in all_stats], "stats": [model_to_dict(x, recurse=not short_output) for x in all_stats],
# 'stats': [{'id': x.id} for x in all_stats] # 'stats': [{'id': x.id} for x in all_stats]
} }
@ -130,52 +150,82 @@ async def get_batstats(
return return_stats return return_stats
@router.get('/totals') @router.get("/totals")
@handle_db_errors @handle_db_errors
async def get_totalstats( async def get_totalstats(
season: int, s_type: Literal['regular', 'post', 'total', None] = None, team_abbrev: list = Query(default=None), season: int,
team_id: list = Query(default=None), player_name: list = Query(default=None), s_type: Literal["regular", "post", "total", None] = None,
week_start: Optional[int] = None, week_end: Optional[int] = None, game_num: list = Query(default=None), team_abbrev: list = Query(default=None),
position: list = Query(default=None), sort: Optional[str] = None, player_id: list = Query(default=None), team_id: list = Query(default=None),
group_by: Literal['team', 'player', 'playerteam'] = 'player', short_output: Optional[bool] = False, player_name: list = Query(default=None),
min_pa: Optional[int] = 1, week: list = Query(default=None)): week_start: Optional[int] = None,
week_end: Optional[int] = None,
game_num: list = Query(default=None),
position: list = Query(default=None),
sort: Optional[str] = None,
player_id: list = Query(default=None),
group_by: Literal["team", "player", "playerteam"] = "player",
short_output: Optional[bool] = False,
min_pa: Optional[int] = 1,
week: list = Query(default=None),
):
if sum(1 for x in [s_type, (week_start or week_end), week] if x is not None) > 1: if sum(1 for x in [s_type, (week_start or week_end), week] if x is not None) > 1:
raise HTTPException(status_code=400, detail=f'Only one of s_type, week_start/week_end, or week may be used.') raise HTTPException(
status_code=400,
detail=f"Only one of s_type, week_start/week_end, or week may be used.",
)
# Build SELECT fields conditionally based on group_by to match GROUP BY exactly # Build SELECT fields conditionally based on group_by to match GROUP BY exactly
select_fields = [] select_fields = []
if group_by == 'player': if group_by == "player":
select_fields = [BattingStat.player] select_fields = [BattingStat.player]
elif group_by == 'team': elif group_by == "team":
select_fields = [BattingStat.team] select_fields = [BattingStat.team]
elif group_by == 'playerteam': elif group_by == "playerteam":
select_fields = [BattingStat.player, BattingStat.team] select_fields = [BattingStat.player, BattingStat.team]
else: else:
# Default case # Default case
select_fields = [BattingStat.player] select_fields = [BattingStat.player]
all_stats = ( all_stats = (
BattingStat BattingStat.select(
.select(*select_fields, *select_fields,
fn.SUM(BattingStat.pa).alias('sum_pa'), fn.SUM(BattingStat.ab).alias('sum_ab'), fn.SUM(BattingStat.pa).alias("sum_pa"),
fn.SUM(BattingStat.run).alias('sum_run'), fn.SUM(BattingStat.hit).alias('sum_hit'), fn.SUM(BattingStat.ab).alias("sum_ab"),
fn.SUM(BattingStat.rbi).alias('sum_rbi'), fn.SUM(BattingStat.double).alias('sum_double'), fn.SUM(BattingStat.run).alias("sum_run"),
fn.SUM(BattingStat.triple).alias('sum_triple'), fn.SUM(BattingStat.hr).alias('sum_hr'), fn.SUM(BattingStat.hit).alias("sum_hit"),
fn.SUM(BattingStat.bb).alias('sum_bb'), fn.SUM(BattingStat.so).alias('sum_so'), fn.SUM(BattingStat.rbi).alias("sum_rbi"),
fn.SUM(BattingStat.hbp).alias('sum_hbp'), fn.SUM(BattingStat.sac).alias('sum_sac'), fn.SUM(BattingStat.double).alias("sum_double"),
fn.SUM(BattingStat.ibb).alias('sum_ibb'), fn.SUM(BattingStat.gidp).alias('sum_gidp'), fn.SUM(BattingStat.triple).alias("sum_triple"),
fn.SUM(BattingStat.sb).alias('sum_sb'), fn.SUM(BattingStat.cs).alias('sum_cs'), fn.SUM(BattingStat.hr).alias("sum_hr"),
fn.SUM(BattingStat.bphr).alias('sum_bphr'), fn.SUM(BattingStat.bpfo).alias('sum_bpfo'), fn.SUM(BattingStat.bb).alias("sum_bb"),
fn.SUM(BattingStat.bp1b).alias('sum_bp1b'), fn.SUM(BattingStat.bplo).alias('sum_bplo'), fn.SUM(BattingStat.so).alias("sum_so"),
fn.SUM(BattingStat.xba).alias('sum_xba'), fn.SUM(BattingStat.xbt).alias('sum_xbt'), fn.SUM(BattingStat.hbp).alias("sum_hbp"),
fn.SUM(BattingStat.xch).alias('sum_xch'), fn.SUM(BattingStat.xhit).alias('sum_xhit'), fn.SUM(BattingStat.sac).alias("sum_sac"),
fn.SUM(BattingStat.error).alias('sum_error'), fn.SUM(BattingStat.pb).alias('sum_pb'), fn.SUM(BattingStat.ibb).alias("sum_ibb"),
fn.SUM(BattingStat.sbc).alias('sum_sbc'), fn.SUM(BattingStat.csc).alias('sum_csc'), fn.SUM(BattingStat.gidp).alias("sum_gidp"),
fn.SUM(BattingStat.roba).alias('sum_roba'), fn.SUM(BattingStat.robs).alias('sum_robs'), fn.SUM(BattingStat.sb).alias("sum_sb"),
fn.SUM(BattingStat.raa).alias('sum_raa'), fn.SUM(BattingStat.rto).alias('sum_rto')) fn.SUM(BattingStat.cs).alias("sum_cs"),
.where(BattingStat.season == season) fn.SUM(BattingStat.bphr).alias("sum_bphr"),
.having(fn.SUM(BattingStat.pa) >= min_pa) fn.SUM(BattingStat.bpfo).alias("sum_bpfo"),
fn.SUM(BattingStat.bp1b).alias("sum_bp1b"),
fn.SUM(BattingStat.bplo).alias("sum_bplo"),
fn.SUM(BattingStat.xba).alias("sum_xba"),
fn.SUM(BattingStat.xbt).alias("sum_xbt"),
fn.SUM(BattingStat.xch).alias("sum_xch"),
fn.SUM(BattingStat.xhit).alias("sum_xhit"),
fn.SUM(BattingStat.error).alias("sum_error"),
fn.SUM(BattingStat.pb).alias("sum_pb"),
fn.SUM(BattingStat.sbc).alias("sum_sbc"),
fn.SUM(BattingStat.csc).alias("sum_csc"),
fn.SUM(BattingStat.roba).alias("sum_roba"),
fn.SUM(BattingStat.robs).alias("sum_robs"),
fn.SUM(BattingStat.raa).alias("sum_raa"),
fn.SUM(BattingStat.rto).alias("sum_rto"),
)
.where(BattingStat.season == season)
.having(fn.SUM(BattingStat.pa) >= min_pa)
) )
if True in [s_type is not None, week_start is not None, week_end is not None]: if True in [s_type is not None, week_start is not None, week_end is not None]:
@ -185,16 +235,20 @@ async def get_totalstats(
elif week_start is not None or week_end is not None: elif week_start is not None or week_end is not None:
if week_start is None or week_end is None: if week_start is None or week_end is None:
raise HTTPException( raise HTTPException(
status_code=400, detail='Both week_start and week_end must be included if either is used.' status_code=400,
detail="Both week_start and week_end must be included if either is used.",
)
weeks["start"] = week_start
if week_end < weeks["start"]:
raise HTTPException(
status_code=400,
detail="week_end must be greater than or equal to week_start",
) )
weeks['start'] = week_start
if week_end < weeks['start']:
raise HTTPException(status_code=400, detail='week_end must be greater than or equal to week_start')
else: else:
weeks['end'] = week_end weeks["end"] = week_end
all_stats = all_stats.where( all_stats = all_stats.where(
(BattingStat.week >= weeks['start']) & (BattingStat.week <= weeks['end']) (BattingStat.week >= weeks["start"]) & (BattingStat.week <= weeks["end"])
) )
elif week is not None: elif week is not None:
all_stats = all_stats.where(BattingStat.week << week) all_stats = all_stats.where(BattingStat.week << week)
@ -204,14 +258,20 @@ async def get_totalstats(
if position is not None: if position is not None:
p_list = [x.upper() for x in position] p_list = [x.upper() for x in position]
all_players = Player.select().where( all_players = Player.select().where(
(Player.pos_1 << p_list) | (Player.pos_2 << p_list) | (Player.pos_3 << p_list) | ( Player.pos_4 << p_list) | (Player.pos_1 << p_list)
(Player.pos_5 << p_list) | (Player.pos_6 << p_list) | (Player.pos_7 << p_list) | ( Player.pos_8 << 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)
) )
all_stats = all_stats.where(BattingStat.player << all_players) all_stats = all_stats.where(BattingStat.player << all_players)
if sort is not None: if sort is not None:
if sort == 'player': if sort == "player":
all_stats = all_stats.order_by(BattingStat.player) all_stats = all_stats.order_by(BattingStat.player)
elif sort == 'team': elif sort == "team":
all_stats = all_stats.order_by(BattingStat.team) all_stats = all_stats.order_by(BattingStat.team)
if group_by is not None: if group_by is not None:
# Use the same fields for GROUP BY as we used for SELECT # Use the same fields for GROUP BY as we used for SELECT
@ -227,56 +287,63 @@ async def get_totalstats(
all_teams = Team.select().where(Team.id << team_id) all_teams = Team.select().where(Team.id << team_id)
all_stats = all_stats.where(BattingStat.team << all_teams) all_stats = all_stats.where(BattingStat.team << all_teams)
elif team_abbrev is not None: elif team_abbrev is not None:
all_teams = Team.select().where(fn.Lower(Team.abbrev) << [x.lower() for x in team_abbrev]) all_teams = Team.select().where(
fn.Lower(Team.abbrev) << [x.lower() for x in team_abbrev]
)
all_stats = all_stats.where(BattingStat.team << all_teams) all_stats = all_stats.where(BattingStat.team << all_teams)
if player_name is not None: if player_name is not None:
all_players = Player.select().where(fn.Lower(Player.name) << [x.lower() for x in player_name]) all_players = Player.select().where(
fn.Lower(Player.name) << [x.lower() for x in player_name]
)
all_stats = all_stats.where(BattingStat.player << all_players) all_stats = all_stats.where(BattingStat.player << all_players)
elif player_id is not None: elif player_id is not None:
all_players = Player.select().where(Player.id << player_id) all_players = Player.select().where(Player.id << player_id)
all_stats = all_stats.where(BattingStat.player << all_players) all_stats = all_stats.where(BattingStat.player << all_players)
return_stats = { return_stats = {"count": all_stats.count(), "stats": []}
'count': all_stats.count(),
'stats': []
}
for x in all_stats: for x in all_stats:
# Handle player field based on grouping with safe access # Handle player field based on grouping with safe access
this_player = 'TOT' this_player = "TOT"
if 'player' in group_by and hasattr(x, 'player'): if "player" in group_by and hasattr(x, "player"):
this_player = x.player_id if short_output else model_to_dict(x.player, recurse=False) this_player = (
x.player_id if short_output else model_to_dict(x.player, recurse=False)
)
# Handle team field based on grouping with safe access # Handle team field based on grouping with safe access
this_team = 'TOT' this_team = "TOT"
if 'team' in group_by and hasattr(x, 'team'): if "team" in group_by and hasattr(x, "team"):
this_team = x.team_id if short_output else model_to_dict(x.team, recurse=False) this_team = (
x.team_id if short_output else model_to_dict(x.team, recurse=False)
return_stats['stats'].append({ )
'player': this_player,
'team': this_team, return_stats["stats"].append(
'pa': x.sum_pa, {
'ab': x.sum_ab, "player": this_player,
'run': x.sum_run, "team": this_team,
'hit': x.sum_hit, "pa": x.sum_pa,
'rbi': x.sum_rbi, "ab": x.sum_ab,
'double': x.sum_double, "run": x.sum_run,
'triple': x.sum_triple, "hit": x.sum_hit,
'hr': x.sum_hr, "rbi": x.sum_rbi,
'bb': x.sum_bb, "double": x.sum_double,
'so': x.sum_so, "triple": x.sum_triple,
'hbp': x.sum_hbp, "hr": x.sum_hr,
'sac': x.sum_sac, "bb": x.sum_bb,
'ibb': x.sum_ibb, "so": x.sum_so,
'gidp': x.sum_gidp, "hbp": x.sum_hbp,
'sb': x.sum_sb, "sac": x.sum_sac,
'cs': x.sum_cs, "ibb": x.sum_ibb,
'bphr': x.sum_bphr, "gidp": x.sum_gidp,
'bpfo': x.sum_bpfo, "sb": x.sum_sb,
'bp1b': x.sum_bp1b, "cs": x.sum_cs,
'bplo': x.sum_bplo "bphr": x.sum_bphr,
}) "bpfo": x.sum_bpfo,
"bp1b": x.sum_bp1b,
"bplo": x.sum_bplo,
}
)
db.close() db.close()
return return_stats return return_stats
@ -287,15 +354,17 @@ async def get_totalstats(
# pass # Keep Career Stats table and recalculate after posting stats # pass # Keep Career Stats table and recalculate after posting stats
@router.patch('/{stat_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/{stat_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def patch_batstats(stat_id: int, new_stats: BatStatModel, token: str = Depends(oauth2_scheme)): async def patch_batstats(
stat_id: int, new_stats: BatStatModel, token: str = Depends(oauth2_scheme)
):
if not valid_token(token): if not valid_token(token):
logger.warning(f'patch_batstats - Bad Token: {token}') logger.warning(f"patch_batstats - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
if BattingStat.get_or_none(BattingStat.id == stat_id) is None: if BattingStat.get_or_none(BattingStat.id == stat_id) is None:
raise HTTPException(status_code=404, detail=f'Stat ID {stat_id} not found') raise HTTPException(status_code=404, detail=f"Stat ID {stat_id} not found")
BattingStat.update(**new_stats.dict()).where(BattingStat.id == stat_id).execute() BattingStat.update(**new_stats.dict()).where(BattingStat.id == stat_id).execute()
r_stat = model_to_dict(BattingStat.get_by_id(stat_id)) r_stat = model_to_dict(BattingStat.get_by_id(stat_id))
@ -303,12 +372,12 @@ async def patch_batstats(stat_id: int, new_stats: BatStatModel, token: str = Dep
return r_stat return r_stat
@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) @router.post("/", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def post_batstats(s_list: BatStatList, token: str = Depends(oauth2_scheme)): async def post_batstats(s_list: BatStatList, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'post_batstats - Bad Token: {token}') logger.warning(f"post_batstats - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
all_stats = [] all_stats = []
@ -316,9 +385,13 @@ async def post_batstats(s_list: BatStatList, token: str = Depends(oauth2_scheme)
team = Team.get_or_none(Team.id == x.team_id) team = Team.get_or_none(Team.id == x.team_id)
this_player = Player.get_or_none(Player.id == x.player_id) this_player = Player.get_or_none(Player.id == x.player_id)
if team is None: if team is None:
raise HTTPException(status_code=404, detail=f'Team ID {x.team_id} not found') raise HTTPException(
status_code=404, detail=f"Team ID {x.team_id} not found"
)
if this_player is None: if this_player is None:
raise HTTPException(status_code=404, detail=f'Player ID {x.player_id} not found') raise HTTPException(
status_code=404, detail=f"Player ID {x.player_id} not found"
)
all_stats.append(BattingStat(**x.dict())) all_stats.append(BattingStat(**x.dict()))
@ -329,4 +402,4 @@ async def post_batstats(s_list: BatStatList, token: str = Depends(oauth2_scheme)
# Update career stats # Update career stats
db.close() db.close()
return f'Added {len(all_stats)} batting lines' return f"Added {len(all_stats)} batting lines"

View File

@ -4,15 +4,17 @@ import logging
import pydantic import pydantic
from ..db_engine import db, Current, model_to_dict from ..db_engine import db, Current, model_to_dict
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors from ..dependencies import (
oauth2_scheme,
logger = logging.getLogger('discord_app') valid_token,
PRIVATE_IN_SCHEMA,
router = APIRouter( handle_db_errors,
prefix='/api/v3/current',
tags=['current']
) )
logger = logging.getLogger("discord_app")
router = APIRouter(prefix="/api/v3/current", tags=["current"])
class CurrentModel(pydantic.BaseModel): class CurrentModel(pydantic.BaseModel):
week: Optional[int] = 0 week: Optional[int] = 0
@ -29,7 +31,7 @@ class CurrentModel(pydantic.BaseModel):
injury_count: Optional[int] = 0 injury_count: Optional[int] = 0
@router.get('') @router.get("")
@handle_db_errors @handle_db_errors
async def get_current(season: Optional[int] = None): async def get_current(season: Optional[int] = None):
if season is not None: if season is not None:
@ -45,21 +47,33 @@ async def get_current(season: Optional[int] = None):
return None return None
@router.patch('/{current_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/{current_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def patch_current( async def patch_current(
current_id: int, season: Optional[int] = None, week: Optional[int] = None, freeze: Optional[bool] = None, current_id: int,
transcount: Optional[int] = None, bstatcount: Optional[int] = None, pstatcount: Optional[int] = None, season: Optional[int] = None,
bet_week: Optional[int] = None, trade_deadline: Optional[int] = None, pick_trade_start: Optional[int] = None, week: Optional[int] = None,
pick_trade_end: Optional[int] = None, injury_count: Optional[int] = None, token: str = Depends(oauth2_scheme)): freeze: Optional[bool] = None,
transcount: Optional[int] = None,
bstatcount: Optional[int] = None,
pstatcount: Optional[int] = None,
bet_week: Optional[int] = None,
trade_deadline: Optional[int] = None,
pick_trade_start: Optional[int] = None,
pick_trade_end: Optional[int] = None,
injury_count: Optional[int] = None,
token: str = Depends(oauth2_scheme),
):
if not valid_token(token): if not valid_token(token):
logger.warning(f'patch_current - Bad Token: {token}') logger.warning(f"patch_current - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
current = Current.get_by_id(current_id) current = Current.get_by_id(current_id)
except Exception as e: except Exception as e:
raise HTTPException(status_code=404, detail=f'Current id {current_id} not found') raise HTTPException(
status_code=404, detail=f"Current id {current_id} not found"
)
if week is not None: if week is not None:
current.week = week current.week = week
@ -90,15 +104,17 @@ async def patch_current(
return r_curr return r_curr
else: else:
db.close() db.close()
raise HTTPException(status_code=500, detail=f'Unable to patch current {current_id}') raise HTTPException(
status_code=500, detail=f"Unable to patch current {current_id}"
)
@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) @router.post("/", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def post_current(new_current: CurrentModel, token: str = Depends(oauth2_scheme)): async def post_current(new_current: CurrentModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'patch_current - Bad Token: {token}') logger.warning(f"patch_current - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_current = Current(**new_current.dict()) this_current = Current(**new_current.dict())
@ -108,17 +124,22 @@ async def post_current(new_current: CurrentModel, token: str = Depends(oauth2_sc
return r_curr return r_curr
else: else:
db.close() db.close()
raise HTTPException(status_code=500, detail=f'Unable to post season {new_current.season} current') raise HTTPException(
status_code=500,
detail=f"Unable to post season {new_current.season} current",
)
@router.delete('/{current_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.delete("/{current_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def delete_current(current_id: int, token: str = Depends(oauth2_scheme)): async def delete_current(current_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'patch_current - Bad Token: {token}') logger.warning(f"patch_current - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
if Current.delete_by_id(current_id) == 1: if Current.delete_by_id(current_id) == 1:
return f'Deleted current ID {current_id}' return f"Deleted current ID {current_id}"
raise HTTPException(status_code=500, detail=f'Unable to delete current {current_id}') raise HTTPException(
status_code=500, detail=f"Unable to delete current {current_id}"
)

File diff suppressed because it is too large Load Diff

View File

@ -4,15 +4,26 @@ import copy
import logging import logging
import pydantic import pydantic
from ..db_engine import db, Decision, StratGame, Player, model_to_dict, chunked, fn, Team from ..db_engine import (
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors db,
Decision,
logger = logging.getLogger('discord_app') StratGame,
Player,
router = APIRouter( model_to_dict,
prefix='/api/v3/decisions', chunked,
tags=['decisions'] fn,
Team,
) )
from ..dependencies import (
oauth2_scheme,
valid_token,
PRIVATE_IN_SCHEMA,
handle_db_errors,
)
logger = logging.getLogger("discord_app")
router = APIRouter(prefix="/api/v3/decisions", tags=["decisions"])
class DecisionModel(pydantic.BaseModel): class DecisionModel(pydantic.BaseModel):
@ -43,17 +54,31 @@ class DecisionReturnList(pydantic.BaseModel):
decisions: list[DecisionModel] decisions: list[DecisionModel]
@router.get('') @router.get("")
@handle_db_errors @handle_db_errors
async def get_decisions( async def get_decisions(
season: list = Query(default=None), week: list = Query(default=None), game_num: list = Query(default=None), season: list = Query(default=None),
s_type: Literal['regular', 'post', 'all', None] = None, team_id: list = Query(default=None), week: list = Query(default=None),
week_start: Optional[int] = None, week_end: Optional[int] = None, win: Optional[int] = None, game_num: list = Query(default=None),
loss: Optional[int] = None, hold: Optional[int] = None, save: Optional[int] = None, s_type: Literal["regular", "post", "all", None] = None,
b_save: Optional[int] = None, irunners: list = Query(default=None), irunners_scored: list = Query(default=None), team_id: list = Query(default=None),
game_id: list = Query(default=None), player_id: list = Query(default=None), week_start: Optional[int] = None,
limit: Optional[int] = None, short_output: Optional[bool] = False): week_end: Optional[int] = None,
all_dec = Decision.select().order_by(-Decision.season, -Decision.week, -Decision.game_num) win: Optional[int] = None,
loss: Optional[int] = None,
hold: Optional[int] = None,
save: Optional[int] = None,
b_save: Optional[int] = None,
irunners: list = Query(default=None),
irunners_scored: list = Query(default=None),
game_id: list = Query(default=None),
player_id: list = Query(default=None),
limit: Optional[int] = None,
short_output: Optional[bool] = False,
):
all_dec = Decision.select().order_by(
-Decision.season, -Decision.week, -Decision.game_num
)
if season is not None: if season is not None:
all_dec = all_dec.where(Decision.season << season) all_dec = all_dec.where(Decision.season << season)
@ -79,7 +104,8 @@ async def get_decisions(
if season is not None and 8 in season or s8_teams: if season is not None and 8 in season or s8_teams:
all_teams = Team.select().where(Team.id << team_id) all_teams = Team.select().where(Team.id << team_id)
all_games = StratGame.select().where( all_games = StratGame.select().where(
(StratGame.away_team << all_teams) | (StratGame.home_team << all_teams)) (StratGame.away_team << all_teams) | (StratGame.home_team << all_teams)
)
all_dec = all_dec.where(Decision.game << all_games) all_dec = all_dec.where(Decision.game << all_games)
else: else:
all_teams = Team.select().where(Team.id << team_id) all_teams = Team.select().where(Team.id << team_id)
@ -115,28 +141,38 @@ async def get_decisions(
all_dec = all_dec.limit(limit) all_dec = all_dec.limit(limit)
return_dec = { return_dec = {
'count': all_dec.count(), "count": all_dec.count(),
'decisions': [model_to_dict(x, recurse=not short_output) for x in all_dec] "decisions": [model_to_dict(x, recurse=not short_output) for x in all_dec],
} }
db.close() db.close()
return return_dec return return_dec
@router.patch('/{decision_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/{decision_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def patch_decision( async def patch_decision(
decision_id: int, win: Optional[int] = None, loss: Optional[int] = None, hold: Optional[int] = None, decision_id: int,
save: Optional[int] = None, b_save: Optional[int] = None, irunners: Optional[int] = None, win: Optional[int] = None,
irunners_scored: Optional[int] = None, rest_ip: Optional[int] = None, rest_required: Optional[int] = None, loss: Optional[int] = None,
token: str = Depends(oauth2_scheme)): hold: Optional[int] = None,
save: Optional[int] = None,
b_save: Optional[int] = None,
irunners: Optional[int] = None,
irunners_scored: Optional[int] = None,
rest_ip: Optional[int] = None,
rest_required: Optional[int] = None,
token: str = Depends(oauth2_scheme),
):
if not valid_token(token): if not valid_token(token):
logger.warning(f'patch_decision - Bad Token: {token}') logger.warning(f"patch_decision - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_dec = Decision.get_or_none(Decision.id == decision_id) this_dec = Decision.get_or_none(Decision.id == decision_id)
if this_dec is None: if this_dec is None:
db.close() db.close()
raise HTTPException(status_code=404, detail=f'Decision ID {decision_id} not found') raise HTTPException(
status_code=404, detail=f"Decision ID {decision_id} not found"
)
if win is not None: if win is not None:
this_dec.win = win this_dec.win = win
@ -163,22 +199,28 @@ async def patch_decision(
return d_result return d_result
else: else:
db.close() db.close()
raise HTTPException(status_code=500, detail=f'Unable to patch decision {decision_id}') raise HTTPException(
status_code=500, detail=f"Unable to patch decision {decision_id}"
)
@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) @router.post("/", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def post_decisions(dec_list: DecisionList, token: str = Depends(oauth2_scheme)): async def post_decisions(dec_list: DecisionList, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'post_decisions - Bad Token: {token}') logger.warning(f"post_decisions - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
new_dec = [] new_dec = []
for x in dec_list.decisions: for x in dec_list.decisions:
if StratGame.get_or_none(StratGame.id == x.game_id) is None: if StratGame.get_or_none(StratGame.id == x.game_id) is None:
raise HTTPException(status_code=404, detail=f'Game ID {x.game_id} not found') raise HTTPException(
status_code=404, detail=f"Game ID {x.game_id} not found"
)
if Player.get_or_none(Player.id == x.pitcher_id) is None: if Player.get_or_none(Player.id == x.pitcher_id) is None:
raise HTTPException(status_code=404, detail=f'Player ID {x.pitcher_id} not found') raise HTTPException(
status_code=404, detail=f"Player ID {x.pitcher_id} not found"
)
new_dec.append(x.dict()) new_dec.append(x.dict())
@ -187,49 +229,53 @@ async def post_decisions(dec_list: DecisionList, token: str = Depends(oauth2_sch
Decision.insert_many(batch).on_conflict_ignore().execute() Decision.insert_many(batch).on_conflict_ignore().execute()
db.close() db.close()
return f'Inserted {len(new_dec)} decisions' return f"Inserted {len(new_dec)} decisions"
@router.delete('/{decision_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.delete("/{decision_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def delete_decision(decision_id: int, token: str = Depends(oauth2_scheme)): async def delete_decision(decision_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'delete_decision - Bad Token: {token}') logger.warning(f"delete_decision - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_dec = Decision.get_or_none(Decision.id == decision_id) this_dec = Decision.get_or_none(Decision.id == decision_id)
if this_dec is None: if this_dec is None:
db.close() db.close()
raise HTTPException(status_code=404, detail=f'Decision ID {decision_id} not found') raise HTTPException(
status_code=404, detail=f"Decision ID {decision_id} not found"
)
count = this_dec.delete_instance() count = this_dec.delete_instance()
db.close() db.close()
if count == 1: if count == 1:
return f'Decision {decision_id} has been deleted' return f"Decision {decision_id} has been deleted"
else: else:
raise HTTPException(status_code=500, detail=f'Decision {decision_id} could not be deleted') raise HTTPException(
status_code=500, detail=f"Decision {decision_id} could not be deleted"
)
@router.delete('/game/{game_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.delete("/game/{game_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def delete_decisions_game(game_id: int, token: str = Depends(oauth2_scheme)): async def delete_decisions_game(game_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'delete_decisions_game - Bad Token: {token}') logger.warning(f"delete_decisions_game - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_game = StratGame.get_or_none(StratGame.id == game_id) this_game = StratGame.get_or_none(StratGame.id == game_id)
if not this_game: if not this_game:
db.close() db.close()
raise HTTPException(status_code=404, detail=f'Game ID {game_id} not found') raise HTTPException(status_code=404, detail=f"Game ID {game_id} not found")
count = Decision.delete().where(Decision.game == this_game).execute() count = Decision.delete().where(Decision.game == this_game).execute()
db.close() db.close()
if count > 0: if count > 0:
return f'Deleted {count} decisions matching Game ID {game_id}' return f"Deleted {count} decisions matching Game ID {game_id}"
else: else:
raise HTTPException(status_code=500, detail=f'No decisions matching Game ID {game_id} were deleted') raise HTTPException(
status_code=500,
detail=f"No decisions matching Game ID {game_id} were deleted",
)

View File

@ -4,15 +4,17 @@ import logging
import pydantic import pydantic
from ..db_engine import db, Division, Team, model_to_dict, chunked, fn from ..db_engine import db, Division, Team, model_to_dict, chunked, fn
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors from ..dependencies import (
oauth2_scheme,
logger = logging.getLogger('discord_app') valid_token,
PRIVATE_IN_SCHEMA,
router = APIRouter( handle_db_errors,
prefix='/api/v3/divisions',
tags=['divisions']
) )
logger = logging.getLogger("discord_app")
router = APIRouter(prefix="/api/v3/divisions", tags=["divisions"])
class DivisionModel(pydantic.BaseModel): class DivisionModel(pydantic.BaseModel):
division_name: str division_name: str
@ -22,11 +24,15 @@ class DivisionModel(pydantic.BaseModel):
season: int = 0 season: int = 0
@router.get('') @router.get("")
@handle_db_errors @handle_db_errors
async def get_divisions( async def get_divisions(
season: int, div_name: Optional[str] = None, div_abbrev: Optional[str] = None, lg_name: Optional[str] = None, season: int,
lg_abbrev: Optional[str] = None): div_name: Optional[str] = None,
div_abbrev: Optional[str] = None,
lg_name: Optional[str] = None,
lg_abbrev: Optional[str] = None,
):
all_divisions = Division.select().where(Division.season == season) all_divisions = Division.select().where(Division.season == season)
if div_name is not None: if div_name is not None:
@ -39,39 +45,48 @@ async def get_divisions(
all_divisions = all_divisions.where(Division.league_abbrev == lg_abbrev) all_divisions = all_divisions.where(Division.league_abbrev == lg_abbrev)
return_div = { return_div = {
'count': all_divisions.count(), "count": all_divisions.count(),
'divisions': [model_to_dict(x) for x in all_divisions] "divisions": [model_to_dict(x) for x in all_divisions],
} }
db.close() db.close()
return return_div return return_div
@router.get('/{division_id}') @router.get("/{division_id}")
@handle_db_errors @handle_db_errors
async def get_one_division(division_id: int): async def get_one_division(division_id: int):
this_div = Division.get_or_none(Division.id == division_id) this_div = Division.get_or_none(Division.id == division_id)
if this_div is None: if this_div is None:
db.close() db.close()
raise HTTPException(status_code=404, detail=f'Division ID {division_id} not found') raise HTTPException(
status_code=404, detail=f"Division ID {division_id} not found"
)
r_div = model_to_dict(this_div) r_div = model_to_dict(this_div)
db.close() db.close()
return r_div return r_div
@router.patch('/{division_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/{division_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def patch_division( async def patch_division(
division_id: int, div_name: Optional[str] = None, div_abbrev: Optional[str] = None, division_id: int,
lg_name: Optional[str] = None, lg_abbrev: Optional[str] = None, token: str = Depends(oauth2_scheme)): div_name: Optional[str] = None,
div_abbrev: Optional[str] = None,
lg_name: Optional[str] = None,
lg_abbrev: Optional[str] = None,
token: str = Depends(oauth2_scheme),
):
if not valid_token(token): if not valid_token(token):
logger.warning(f'patch_division - Bad Token: {token}') logger.warning(f"patch_division - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_div = Division.get_or_none(Division.id == division_id) this_div = Division.get_or_none(Division.id == division_id)
if this_div is None: if this_div is None:
db.close() db.close()
raise HTTPException(status_code=404, detail=f'Division ID {division_id} not found') raise HTTPException(
status_code=404, detail=f"Division ID {division_id} not found"
)
if div_name is not None: if div_name is not None:
this_div.division_name = div_name this_div.division_name = div_name
@ -88,15 +103,19 @@ async def patch_division(
return r_division return r_division
else: else:
db.close() db.close()
raise HTTPException(status_code=500, detail=f'Unable to patch division {division_id}') raise HTTPException(
status_code=500, detail=f"Unable to patch division {division_id}"
)
@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) @router.post("/", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def post_division(new_division: DivisionModel, token: str = Depends(oauth2_scheme)): async def post_division(
new_division: DivisionModel, token: str = Depends(oauth2_scheme)
):
if not valid_token(token): if not valid_token(token):
logger.warning(f'post_division - Bad Token: {token}') logger.warning(f"post_division - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_division = Division(**new_division.dict()) this_division = Division(**new_division.dict())
@ -106,27 +125,29 @@ async def post_division(new_division: DivisionModel, token: str = Depends(oauth2
return r_division return r_division
else: else:
db.close() db.close()
raise HTTPException(status_code=500, detail=f'Unable to post division') raise HTTPException(status_code=500, detail=f"Unable to post division")
@router.delete('/{division_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.delete("/{division_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def delete_division(division_id: int, token: str = Depends(oauth2_scheme)): async def delete_division(division_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'delete_division - Bad Token: {token}') logger.warning(f"delete_division - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_div = Division.get_or_none(Division.id == division_id) this_div = Division.get_or_none(Division.id == division_id)
if this_div is None: if this_div is None:
db.close() db.close()
raise HTTPException(status_code=404, detail=f'Division ID {division_id} not found') raise HTTPException(
status_code=404, detail=f"Division ID {division_id} not found"
)
count = this_div.delete_instance() count = this_div.delete_instance()
db.close() db.close()
if count == 1: if count == 1:
return f'Division {division_id} has been deleted' return f"Division {division_id} has been deleted"
else: else:
raise HTTPException(status_code=500, detail=f'Division {division_id} could not be deleted') raise HTTPException(
status_code=500, detail=f"Division {division_id} could not be deleted"
)

View File

@ -4,15 +4,17 @@ import logging
import pydantic import pydantic
from ..db_engine import db, DraftList, Team, model_to_dict, chunked from ..db_engine import db, DraftList, Team, model_to_dict, chunked
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors from ..dependencies import (
oauth2_scheme,
logger = logging.getLogger('discord_app') valid_token,
PRIVATE_IN_SCHEMA,
router = APIRouter( handle_db_errors,
prefix='/api/v3/draftlist',
tags=['draftlist']
) )
logger = logging.getLogger("discord_app")
router = APIRouter(prefix="/api/v3/draftlist", tags=["draftlist"])
class DraftListModel(pydantic.BaseModel): class DraftListModel(pydantic.BaseModel):
season: int season: int
@ -26,13 +28,16 @@ class DraftListList(pydantic.BaseModel):
draft_list: List[DraftListModel] draft_list: List[DraftListModel]
@router.get('', include_in_schema=PRIVATE_IN_SCHEMA) @router.get("", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def get_draftlist( async def get_draftlist(
season: Optional[int], team_id: list = Query(default=None), token: str = Depends(oauth2_scheme)): season: Optional[int],
team_id: list = Query(default=None),
token: str = Depends(oauth2_scheme),
):
if not valid_token(token): if not valid_token(token):
logger.warning(f'get_draftlist - Bad Token: {token}') logger.warning(f"get_draftlist - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
all_list = DraftList.select() all_list = DraftList.select()
@ -41,47 +46,49 @@ async def get_draftlist(
if team_id is not None: if team_id is not None:
all_list = all_list.where(DraftList.team_id << team_id) all_list = all_list.where(DraftList.team_id << team_id)
r_list = { r_list = {"count": all_list.count(), "picks": [model_to_dict(x) for x in all_list]}
'count': all_list.count(),
'picks': [model_to_dict(x) for x in all_list]
}
db.close() db.close()
return r_list return r_list
@router.get('/team/{team_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.get("/team/{team_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def get_team_draftlist(team_id: int, token: str = Depends(oauth2_scheme)): async def get_team_draftlist(team_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'post_draftlist - Bad Token: {token}') logger.warning(f"post_draftlist - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_team = Team.get_or_none(Team.id == team_id) this_team = Team.get_or_none(Team.id == team_id)
if this_team is None: if this_team is None:
raise HTTPException(status_code=404, detail=f'Team ID {team_id} not found') raise HTTPException(status_code=404, detail=f"Team ID {team_id} not found")
this_list = DraftList.select().where(DraftList.team == this_team) this_list = DraftList.select().where(DraftList.team == this_team)
r_list = { r_list = {
'count': this_list.count(), "count": this_list.count(),
'picks': [model_to_dict(x) for x in this_list] "picks": [model_to_dict(x) for x in this_list],
} }
db.close() db.close()
return r_list return r_list
@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) @router.post("/", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def post_draftlist(draft_list: DraftListList, token: str = Depends(oauth2_scheme)): async def post_draftlist(
draft_list: DraftListList, token: str = Depends(oauth2_scheme)
):
if not valid_token(token): if not valid_token(token):
logger.warning(f'post_draftlist - Bad Token: {token}') logger.warning(f"post_draftlist - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
new_list = [] new_list = []
this_team = Team.get_or_none(Team.id == draft_list.draft_list[0].team_id) this_team = Team.get_or_none(Team.id == draft_list.draft_list[0].team_id)
if this_team is None: if this_team is None:
raise HTTPException(status_code=404, detail=f'Team ID {draft_list.draft_list[0].team_id} not found') raise HTTPException(
status_code=404,
detail=f"Team ID {draft_list.draft_list[0].team_id} not found",
)
DraftList.delete().where(DraftList.team == this_team).execute() DraftList.delete().where(DraftList.team == this_team).execute()
@ -93,16 +100,16 @@ async def post_draftlist(draft_list: DraftListList, token: str = Depends(oauth2_
DraftList.insert_many(batch).on_conflict_ignore().execute() DraftList.insert_many(batch).on_conflict_ignore().execute()
db.close() db.close()
return f'Inserted {len(new_list)} list values' return f"Inserted {len(new_list)} list values"
@router.delete('/team/{team_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.delete("/team/{team_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def delete_draftlist(team_id: int, token: str = Depends(oauth2_scheme)): async def delete_draftlist(team_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'delete_draftlist - Bad Token: {token}') logger.warning(f"delete_draftlist - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
count = DraftList.delete().where(DraftList.team_id == team_id).execute() count = DraftList.delete().where(DraftList.team_id == team_id).execute()
db.close() db.close()
return f'Deleted {count} list values' return f"Deleted {count} list values"

View File

@ -4,15 +4,17 @@ import logging
import pydantic import pydantic
from ..db_engine import db, DraftPick, Team, model_to_dict, chunked from ..db_engine import db, DraftPick, Team, model_to_dict, chunked
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors from ..dependencies import (
oauth2_scheme,
logger = logging.getLogger('discord_app') valid_token,
PRIVATE_IN_SCHEMA,
router = APIRouter( handle_db_errors,
prefix='/api/v3/draftpicks',
tags=['draftpicks']
) )
logger = logging.getLogger("discord_app")
router = APIRouter(prefix="/api/v3/draftpicks", tags=["draftpicks"])
class DraftPickModel(pydantic.BaseModel): class DraftPickModel(pydantic.BaseModel):
overall: Optional[int] = None overall: Optional[int] = None
@ -32,15 +34,26 @@ class DraftPickReturnList(pydantic.BaseModel):
picks: list[DraftPickModel] picks: list[DraftPickModel]
@router.get('') @router.get("")
@handle_db_errors @handle_db_errors
async def get_picks( async def get_picks(
season: int, owner_team_abbrev: list = Query(default=None), orig_team_abbrev: list = Query(default=None), season: int,
owner_team_id: list = Query(default=None), orig_team_id: list = Query(default=None), owner_team_abbrev: list = Query(default=None),
pick_round_start: Optional[int] = None, pick_round_end: Optional[int] = None, traded: Optional[bool] = None, orig_team_abbrev: list = Query(default=None),
overall: Optional[int] = None, overall_start: Optional[int] = None, overall_end: Optional[int] = None, owner_team_id: list = Query(default=None),
short_output: Optional[bool] = False, sort: Optional[str] = None, limit: Optional[int] = None, orig_team_id: list = Query(default=None),
player_id: list = Query(default=None), player_taken: Optional[bool] = None): pick_round_start: Optional[int] = None,
pick_round_end: Optional[int] = None,
traded: Optional[bool] = None,
overall: Optional[int] = None,
overall_start: Optional[int] = None,
overall_end: Optional[int] = None,
short_output: Optional[bool] = False,
sort: Optional[str] = None,
limit: Optional[int] = None,
player_id: list = Query(default=None),
player_taken: Optional[bool] = None,
):
all_picks = DraftPick.select().where(DraftPick.season == season) all_picks = DraftPick.select().where(DraftPick.season == season)
if owner_team_abbrev is not None: if owner_team_abbrev is not None:
@ -61,16 +74,25 @@ async def get_picks(
if owner_team_id is not None: if owner_team_id is not None:
all_picks = all_picks.where( all_picks = all_picks.where(
(DraftPick.owner_id << owner_team_id) | (DraftPick.owner_id << owner_team_id) (DraftPick.owner_id << owner_team_id)
| (DraftPick.owner_id << owner_team_id)
) )
if orig_team_id is not None: if orig_team_id is not None:
all_picks = all_picks.where( all_picks = all_picks.where(
(DraftPick.origowner_id << orig_team_id) | (DraftPick.origowner_id << orig_team_id) (DraftPick.origowner_id << orig_team_id)
| (DraftPick.origowner_id << orig_team_id)
) )
if pick_round_start is not None and pick_round_end is not None and pick_round_end < pick_round_start: if (
raise HTTPException(status_code=400, detail=f'pick_round_end must be greater than or equal to pick_round_start') pick_round_start is not None
and pick_round_end is not None
and pick_round_end < pick_round_start
):
raise HTTPException(
status_code=400,
detail=f"pick_round_end must be greater than or equal to pick_round_start",
)
if player_id is not None: if player_id is not None:
all_picks = all_picks.where(DraftPick.player_id << player_id) all_picks = all_picks.where(DraftPick.player_id << player_id)
@ -92,40 +114,42 @@ async def get_picks(
all_picks = all_picks.limit(limit) all_picks = all_picks.limit(limit)
if sort is not None: if sort is not None:
if sort == 'order-asc': if sort == "order-asc":
all_picks = all_picks.order_by(DraftPick.overall) all_picks = all_picks.order_by(DraftPick.overall)
elif sort == 'order-desc': elif sort == "order-desc":
all_picks = all_picks.order_by(-DraftPick.overall) all_picks = all_picks.order_by(-DraftPick.overall)
return_picks = {'count': all_picks.count(), 'picks': []} return_picks = {"count": all_picks.count(), "picks": []}
for line in all_picks: for line in all_picks:
return_picks['picks'].append(model_to_dict(line, recurse=not short_output)) return_picks["picks"].append(model_to_dict(line, recurse=not short_output))
db.close() db.close()
return return_picks return return_picks
@router.get('/{pick_id}') @router.get("/{pick_id}")
@handle_db_errors @handle_db_errors
async def get_one_pick(pick_id: int, short_output: Optional[bool] = False): async def get_one_pick(pick_id: int, short_output: Optional[bool] = False):
this_pick = DraftPick.get_or_none(DraftPick.id == pick_id) this_pick = DraftPick.get_or_none(DraftPick.id == pick_id)
if this_pick is not None: if this_pick is not None:
r_pick = model_to_dict(this_pick, recurse=not short_output) r_pick = model_to_dict(this_pick, recurse=not short_output)
else: else:
raise HTTPException(status_code=404, detail=f'Pick ID {pick_id} not found') raise HTTPException(status_code=404, detail=f"Pick ID {pick_id} not found")
db.close() db.close()
return r_pick return r_pick
@router.patch('/{pick_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/{pick_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def patch_pick(pick_id: int, new_pick: DraftPickModel, token: str = Depends(oauth2_scheme)): async def patch_pick(
pick_id: int, new_pick: DraftPickModel, token: str = Depends(oauth2_scheme)
):
if not valid_token(token): if not valid_token(token):
logger.warning(f'patch_pick - Bad Token: {token}') logger.warning(f"patch_pick - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
if DraftPick.get_or_none(DraftPick.id == pick_id) is None: if DraftPick.get_or_none(DraftPick.id == pick_id) is None:
raise HTTPException(status_code=404, detail=f'Pick ID {pick_id} not found') raise HTTPException(status_code=404, detail=f"Pick ID {pick_id} not found")
DraftPick.update(**new_pick.dict()).where(DraftPick.id == pick_id).execute() DraftPick.update(**new_pick.dict()).where(DraftPick.id == pick_id).execute()
r_pick = model_to_dict(DraftPick.get_by_id(pick_id)) r_pick = model_to_dict(DraftPick.get_by_id(pick_id))
@ -133,21 +157,23 @@ async def patch_pick(pick_id: int, new_pick: DraftPickModel, token: str = Depend
return r_pick return r_pick
@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) @router.post("/", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def post_picks(p_list: DraftPickList, token: str = Depends(oauth2_scheme)): async def post_picks(p_list: DraftPickList, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'post_picks - Bad Token: {token}') logger.warning(f"post_picks - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
new_picks = [] new_picks = []
for pick in p_list.picks: for pick in p_list.picks:
dupe = DraftPick.get_or_none(DraftPick.season == pick.season, DraftPick.overall == pick.overall) dupe = DraftPick.get_or_none(
DraftPick.season == pick.season, DraftPick.overall == pick.overall
)
if dupe: if dupe:
db.close() db.close()
raise HTTPException( raise HTTPException(
status_code=500, status_code=500,
detail=f'Pick # {pick.overall} already exists for season {pick.season}' detail=f"Pick # {pick.overall} already exists for season {pick.season}",
) )
new_picks.append(pick.dict()) new_picks.append(pick.dict())
@ -157,25 +183,26 @@ async def post_picks(p_list: DraftPickList, token: str = Depends(oauth2_scheme))
DraftPick.insert_many(batch).on_conflict_ignore().execute() DraftPick.insert_many(batch).on_conflict_ignore().execute()
db.close() db.close()
return f'Inserted {len(new_picks)} picks' return f"Inserted {len(new_picks)} picks"
@router.delete('/{pick_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.delete("/{pick_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def delete_pick(pick_id: int, token: str = Depends(oauth2_scheme)): async def delete_pick(pick_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'delete_pick - Bad Token: {token}') logger.warning(f"delete_pick - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_pick = DraftPick.get_or_none(DraftPick.id == pick_id) this_pick = DraftPick.get_or_none(DraftPick.id == pick_id)
if this_pick is None: if this_pick is None:
raise HTTPException(status_code=404, detail=f'Pick ID {pick_id} not found') raise HTTPException(status_code=404, detail=f"Pick ID {pick_id} not found")
count = this_pick.delete_instance() count = this_pick.delete_instance()
db.close() db.close()
if count == 1: if count == 1:
return f'Draft pick {pick_id} has been deleted' return f"Draft pick {pick_id} has been deleted"
else: else:
raise HTTPException(status_code=500, detail=f'Draft pick {pick_id} could not be deleted') raise HTTPException(
status_code=500, detail=f"Draft pick {pick_id} could not be deleted"
)

View File

@ -6,15 +6,17 @@ from pydantic import BaseModel, Field
from playhouse.shortcuts import model_to_dict from playhouse.shortcuts import model_to_dict
from peewee import fn from peewee import fn
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors from ..dependencies import (
oauth2_scheme,
valid_token,
PRIVATE_IN_SCHEMA,
handle_db_errors,
)
from ..db_engine import db, HelpCommand from ..db_engine import db, HelpCommand
logger = logging.getLogger('database_api') logger = logging.getLogger("database_api")
router = APIRouter( router = APIRouter(prefix="/api/v3/help_commands", tags=["help_commands"])
prefix='/api/v3/help_commands',
tags=['help_commands']
)
# Pydantic Models for API # Pydantic Models for API
@ -52,15 +54,16 @@ class HelpCommandStatsResponse(BaseModel):
# API Endpoints # API Endpoints
@router.get('')
@router.get("")
@handle_db_errors @handle_db_errors
async def get_help_commands( async def get_help_commands(
name: Optional[str] = None, name: Optional[str] = None,
category: Optional[str] = None, category: Optional[str] = None,
is_active: Optional[bool] = True, is_active: Optional[bool] = True,
sort: Optional[str] = 'name', sort: Optional[str] = "name",
page: int = Query(1, ge=1), page: int = Query(1, ge=1),
page_size: int = Query(25, ge=1, le=100) page_size: int = Query(25, ge=1, le=100),
): ):
"""Get help commands with filtering and pagination""" """Get help commands with filtering and pagination"""
try: try:
@ -82,16 +85,16 @@ async def get_help_commands(
# Apply sorting # Apply sorting
sort_mapping = { sort_mapping = {
'name': HelpCommand.name, "name": HelpCommand.name,
'title': HelpCommand.title, "title": HelpCommand.title,
'category': HelpCommand.category, "category": HelpCommand.category,
'created_at': HelpCommand.created_at, "created_at": HelpCommand.created_at,
'updated_at': HelpCommand.updated_at, "updated_at": HelpCommand.updated_at,
'view_count': HelpCommand.view_count, "view_count": HelpCommand.view_count,
'display_order': HelpCommand.display_order "display_order": HelpCommand.display_order,
} }
if sort.startswith('-'): if sort.startswith("-"):
sort_field = sort[1:] sort_field = sort[1:]
order_by = sort_mapping.get(sort_field, HelpCommand.name).desc() order_by = sort_mapping.get(sort_field, HelpCommand.name).desc()
else: else:
@ -111,10 +114,10 @@ async def get_help_commands(
for help_cmd in query: for help_cmd in query:
cmd_dict = model_to_dict(help_cmd) cmd_dict = model_to_dict(help_cmd)
# Convert datetime objects to ISO strings # Convert datetime objects to ISO strings
if cmd_dict.get('created_at'): if cmd_dict.get("created_at"):
cmd_dict['created_at'] = cmd_dict['created_at'].isoformat() cmd_dict["created_at"] = cmd_dict["created_at"].isoformat()
if cmd_dict.get('updated_at'): if cmd_dict.get("updated_at"):
cmd_dict['updated_at'] = cmd_dict['updated_at'].isoformat() cmd_dict["updated_at"] = cmd_dict["updated_at"].isoformat()
try: try:
command_model = HelpCommandModel(**cmd_dict) command_model = HelpCommandModel(**cmd_dict)
@ -129,7 +132,7 @@ async def get_help_commands(
page=page, page=page,
page_size=page_size, page_size=page_size,
total_pages=total_pages, total_pages=total_pages,
has_more=page < total_pages has_more=page < total_pages,
) )
except Exception as e: except Exception as e:
@ -139,22 +142,23 @@ async def get_help_commands(
db.close() db.close()
@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) @router.post("/", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def create_help_command_endpoint( async def create_help_command_endpoint(
command: HelpCommandModel, command: HelpCommandModel, token: str = Depends(oauth2_scheme)
token: str = Depends(oauth2_scheme)
): ):
"""Create a new help command""" """Create a new help command"""
if not valid_token(token): if not valid_token(token):
logger.warning(f'create_help_command - Bad Token: {token}') logger.warning(f"create_help_command - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
# Check if command name already exists # Check if command name already exists
existing = HelpCommand.get_by_name(command.name) existing = HelpCommand.get_by_name(command.name)
if existing: if existing:
raise HTTPException(status_code=409, detail=f"Help topic '{command.name}' already exists") raise HTTPException(
status_code=409, detail=f"Help topic '{command.name}' already exists"
)
# Create the command # Create the command
help_cmd = HelpCommand.create( help_cmd = HelpCommand.create(
@ -166,15 +170,15 @@ async def create_help_command_endpoint(
created_at=datetime.now(), created_at=datetime.now(),
is_active=True, is_active=True,
view_count=0, view_count=0,
display_order=command.display_order display_order=command.display_order,
) )
# Return the created command # Return the created command
result = model_to_dict(help_cmd) result = model_to_dict(help_cmd)
if result.get('created_at'): if result.get("created_at"):
result['created_at'] = result['created_at'].isoformat() result["created_at"] = result["created_at"].isoformat()
if result.get('updated_at'): if result.get("updated_at"):
result['updated_at'] = result['updated_at'].isoformat() result["updated_at"] = result["updated_at"].isoformat()
return result return result
@ -187,23 +191,23 @@ async def create_help_command_endpoint(
db.close() db.close()
@router.put('/{command_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.put("/{command_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def update_help_command_endpoint( async def update_help_command_endpoint(
command_id: int, command_id: int, command: HelpCommandModel, token: str = Depends(oauth2_scheme)
command: HelpCommandModel,
token: str = Depends(oauth2_scheme)
): ):
"""Update an existing help command""" """Update an existing help command"""
if not valid_token(token): if not valid_token(token):
logger.warning(f'update_help_command - Bad Token: {token}') logger.warning(f"update_help_command - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
# Get the command # Get the command
help_cmd = HelpCommand.get_or_none(HelpCommand.id == command_id) help_cmd = HelpCommand.get_or_none(HelpCommand.id == command_id)
if not help_cmd: if not help_cmd:
raise HTTPException(status_code=404, detail=f"Help command {command_id} not found") raise HTTPException(
status_code=404, detail=f"Help command {command_id} not found"
)
# Update fields # Update fields
if command.title: if command.title:
@ -222,10 +226,10 @@ async def update_help_command_endpoint(
# Return updated command # Return updated command
result = model_to_dict(help_cmd) result = model_to_dict(help_cmd)
if result.get('created_at'): if result.get("created_at"):
result['created_at'] = result['created_at'].isoformat() result["created_at"] = result["created_at"].isoformat()
if result.get('updated_at'): if result.get("updated_at"):
result['updated_at'] = result['updated_at'].isoformat() result["updated_at"] = result["updated_at"].isoformat()
return result return result
@ -238,32 +242,33 @@ async def update_help_command_endpoint(
db.close() db.close()
@router.patch('/{command_id}/restore', include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/{command_id}/restore", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def restore_help_command_endpoint( async def restore_help_command_endpoint(
command_id: int, command_id: int, token: str = Depends(oauth2_scheme)
token: str = Depends(oauth2_scheme)
): ):
"""Restore a soft-deleted help command""" """Restore a soft-deleted help command"""
if not valid_token(token): if not valid_token(token):
logger.warning(f'restore_help_command - Bad Token: {token}') logger.warning(f"restore_help_command - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
# Get the command # Get the command
help_cmd = HelpCommand.get_or_none(HelpCommand.id == command_id) help_cmd = HelpCommand.get_or_none(HelpCommand.id == command_id)
if not help_cmd: if not help_cmd:
raise HTTPException(status_code=404, detail=f"Help command {command_id} not found") raise HTTPException(
status_code=404, detail=f"Help command {command_id} not found"
)
# Restore the command # Restore the command
help_cmd.restore() help_cmd.restore()
# Return restored command # Return restored command
result = model_to_dict(help_cmd) result = model_to_dict(help_cmd)
if result.get('created_at'): if result.get("created_at"):
result['created_at'] = result['created_at'].isoformat() result["created_at"] = result["created_at"].isoformat()
if result.get('updated_at'): if result.get("updated_at"):
result['updated_at'] = result['updated_at'].isoformat() result["updated_at"] = result["updated_at"].isoformat()
return result return result
@ -276,22 +281,23 @@ async def restore_help_command_endpoint(
db.close() db.close()
@router.delete('/{command_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.delete("/{command_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def delete_help_command_endpoint( async def delete_help_command_endpoint(
command_id: int, command_id: int, token: str = Depends(oauth2_scheme)
token: str = Depends(oauth2_scheme)
): ):
"""Soft delete a help command""" """Soft delete a help command"""
if not valid_token(token): if not valid_token(token):
logger.warning(f'delete_help_command - Bad Token: {token}') logger.warning(f"delete_help_command - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
# Get the command # Get the command
help_cmd = HelpCommand.get_or_none(HelpCommand.id == command_id) help_cmd = HelpCommand.get_or_none(HelpCommand.id == command_id)
if not help_cmd: if not help_cmd:
raise HTTPException(status_code=404, detail=f"Help command {command_id} not found") raise HTTPException(
status_code=404, detail=f"Help command {command_id} not found"
)
# Soft delete the command # Soft delete the command
help_cmd.soft_delete() help_cmd.soft_delete()
@ -307,44 +313,56 @@ async def delete_help_command_endpoint(
db.close() db.close()
@router.get('/stats') @router.get("/stats")
@handle_db_errors @handle_db_errors
async def get_help_command_stats(): async def get_help_command_stats():
"""Get comprehensive statistics about help commands""" """Get comprehensive statistics about help commands"""
try: try:
# Get basic counts # Get basic counts
total_commands = HelpCommand.select().count() total_commands = HelpCommand.select().count()
active_commands = HelpCommand.select().where(HelpCommand.is_active == True).count() active_commands = (
HelpCommand.select().where(HelpCommand.is_active == True).count()
)
# Get total views # Get total views
total_views = HelpCommand.select(fn.SUM(HelpCommand.view_count)).where( total_views = (
HelpCommand.is_active == True HelpCommand.select(fn.SUM(HelpCommand.view_count))
).scalar() or 0 .where(HelpCommand.is_active == True)
.scalar()
or 0
)
# Get most viewed command # Get most viewed command
most_viewed = HelpCommand.get_most_viewed(limit=1).first() most_viewed = HelpCommand.get_most_viewed(limit=1).first()
most_viewed_command = None most_viewed_command = None
if most_viewed: if most_viewed:
most_viewed_dict = model_to_dict(most_viewed) most_viewed_dict = model_to_dict(most_viewed)
if most_viewed_dict.get('created_at'): if most_viewed_dict.get("created_at"):
most_viewed_dict['created_at'] = most_viewed_dict['created_at'].isoformat() most_viewed_dict["created_at"] = most_viewed_dict[
if most_viewed_dict.get('updated_at'): "created_at"
most_viewed_dict['updated_at'] = most_viewed_dict['updated_at'].isoformat() ].isoformat()
if most_viewed_dict.get("updated_at"):
most_viewed_dict["updated_at"] = most_viewed_dict[
"updated_at"
].isoformat()
most_viewed_command = most_viewed_dict most_viewed_command = most_viewed_dict
# Get recent commands count (last 7 days) # Get recent commands count (last 7 days)
week_ago = datetime.now() - timedelta(days=7) week_ago = datetime.now() - timedelta(days=7)
recent_count = HelpCommand.select().where( recent_count = (
(HelpCommand.created_at >= week_ago) & HelpCommand.select()
(HelpCommand.is_active == True) .where(
).count() (HelpCommand.created_at >= week_ago) & (HelpCommand.is_active == True)
)
.count()
)
return HelpCommandStatsResponse( return HelpCommandStatsResponse(
total_commands=total_commands, total_commands=total_commands,
active_commands=active_commands, active_commands=active_commands,
total_views=int(total_views), total_views=int(total_views),
most_viewed_command=most_viewed_command, most_viewed_command=most_viewed_command,
recent_commands_count=recent_count recent_commands_count=recent_count,
) )
except Exception as e: except Exception as e:
@ -355,24 +373,27 @@ async def get_help_command_stats():
# Special endpoints for Discord bot integration # Special endpoints for Discord bot integration
@router.get('/by_name/{command_name}') @router.get("/by_name/{command_name}")
@handle_db_errors @handle_db_errors
async def get_help_command_by_name_endpoint( async def get_help_command_by_name_endpoint(
command_name: str, command_name: str, include_inactive: bool = Query(False)
include_inactive: bool = Query(False)
): ):
"""Get a help command by name (for Discord bot)""" """Get a help command by name (for Discord bot)"""
try: try:
help_cmd = HelpCommand.get_by_name(command_name, include_inactive=include_inactive) help_cmd = HelpCommand.get_by_name(
command_name, include_inactive=include_inactive
)
if not help_cmd: if not help_cmd:
raise HTTPException(status_code=404, detail=f"Help topic '{command_name}' not found") raise HTTPException(
status_code=404, detail=f"Help topic '{command_name}' not found"
)
result = model_to_dict(help_cmd) result = model_to_dict(help_cmd)
if result.get('created_at'): if result.get("created_at"):
result['created_at'] = result['created_at'].isoformat() result["created_at"] = result["created_at"].isoformat()
if result.get('updated_at'): if result.get("updated_at"):
result['updated_at'] = result['updated_at'].isoformat() result["updated_at"] = result["updated_at"].isoformat()
return result return result
@ -385,32 +406,31 @@ async def get_help_command_by_name_endpoint(
db.close() db.close()
@router.patch('/by_name/{command_name}/view', include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/by_name/{command_name}/view", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def increment_view_count( async def increment_view_count(command_name: str, token: str = Depends(oauth2_scheme)):
command_name: str,
token: str = Depends(oauth2_scheme)
):
"""Increment view count for a help command""" """Increment view count for a help command"""
if not valid_token(token): if not valid_token(token):
logger.warning(f'increment_view_count - Bad Token: {token}') logger.warning(f"increment_view_count - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
help_cmd = HelpCommand.get_by_name(command_name) help_cmd = HelpCommand.get_by_name(command_name)
if not help_cmd: if not help_cmd:
raise HTTPException(status_code=404, detail=f"Help topic '{command_name}' not found") raise HTTPException(
status_code=404, detail=f"Help topic '{command_name}' not found"
)
# Increment view count # Increment view count
help_cmd.increment_view_count() help_cmd.increment_view_count()
# Return updated command # Return updated command
result = model_to_dict(help_cmd) result = model_to_dict(help_cmd)
if result.get('created_at'): if result.get("created_at"):
result['created_at'] = result['created_at'].isoformat() result["created_at"] = result["created_at"].isoformat()
if result.get('updated_at'): if result.get("updated_at"):
result['updated_at'] = result['updated_at'].isoformat() result["updated_at"] = result["updated_at"].isoformat()
return result return result
@ -423,11 +443,10 @@ async def increment_view_count(
db.close() db.close()
@router.get('/autocomplete') @router.get("/autocomplete")
@handle_db_errors @handle_db_errors
async def get_help_names_for_autocomplete( async def get_help_names_for_autocomplete(
q: str = Query(""), q: str = Query(""), limit: int = Query(25, ge=1, le=100)
limit: int = Query(25, ge=1, le=100)
): ):
"""Get help command names for Discord autocomplete""" """Get help command names for Discord autocomplete"""
try: try:
@ -438,11 +457,11 @@ async def get_help_names_for_autocomplete(
# Return list of dictionaries with name, title, category # Return list of dictionaries with name, title, category
return { return {
'results': [ "results": [
{ {
'name': help_cmd.name, "name": help_cmd.name,
'title': help_cmd.title, "title": help_cmd.title,
'category': help_cmd.category "category": help_cmd.category,
} }
for help_cmd in results for help_cmd in results
] ]
@ -455,7 +474,7 @@ async def get_help_names_for_autocomplete(
db.close() db.close()
@router.get('/{command_id}') @router.get("/{command_id}")
@handle_db_errors @handle_db_errors
async def get_help_command(command_id: int): async def get_help_command(command_id: int):
"""Get a single help command by ID""" """Get a single help command by ID"""
@ -463,13 +482,15 @@ async def get_help_command(command_id: int):
help_cmd = HelpCommand.get_or_none(HelpCommand.id == command_id) help_cmd = HelpCommand.get_or_none(HelpCommand.id == command_id)
if not help_cmd: if not help_cmd:
raise HTTPException(status_code=404, detail=f"Help command {command_id} not found") raise HTTPException(
status_code=404, detail=f"Help command {command_id} not found"
)
result = model_to_dict(help_cmd) result = model_to_dict(help_cmd)
if result.get('created_at'): if result.get("created_at"):
result['created_at'] = result['created_at'].isoformat() result["created_at"] = result["created_at"].isoformat()
if result.get('updated_at'): if result.get("updated_at"):
result['updated_at'] = result['updated_at'].isoformat() result["updated_at"] = result["updated_at"].isoformat()
return result return result

View File

@ -4,15 +4,17 @@ import logging
import pydantic import pydantic
from ..db_engine import db, Injury, Player, model_to_dict, fn from ..db_engine import db, Injury, Player, model_to_dict, fn
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors from ..dependencies import (
oauth2_scheme,
logger = logging.getLogger('discord_app') valid_token,
PRIVATE_IN_SCHEMA,
router = APIRouter( handle_db_errors,
prefix='/api/v3/injuries',
tags=['injuries']
) )
logger = logging.getLogger("discord_app")
router = APIRouter(prefix="/api/v3/injuries", tags=["injuries"])
class InjuryModel(pydantic.BaseModel): class InjuryModel(pydantic.BaseModel):
season: int season: int
@ -25,12 +27,18 @@ class InjuryModel(pydantic.BaseModel):
is_active: bool = True is_active: bool = True
@router.get('') @router.get("")
@handle_db_errors @handle_db_errors
async def get_injuries( async def get_injuries(
season: list = Query(default=None), player_id: list = Query(default=None), min_games: int = None, season: list = Query(default=None),
max_games: int = None, team_id: list = Query(default=None), is_active: bool = None, player_id: list = Query(default=None),
short_output: bool = False, sort: Optional[str] = 'start-asc'): min_games: int = None,
max_games: int = None,
team_id: list = Query(default=None),
is_active: bool = None,
short_output: bool = False,
sort: Optional[str] = "start-asc",
):
all_injuries = Injury.select() all_injuries = Injury.select()
if season is not None: if season is not None:
@ -47,34 +55,38 @@ async def get_injuries(
all_players = Player.select().where(Player.team_id << team_id) all_players = Player.select().where(Player.team_id << team_id)
all_injuries = all_injuries.where(Injury.player << all_players) all_injuries = all_injuries.where(Injury.player << all_players)
if sort == 'return-asc': if sort == "return-asc":
all_injuries = all_injuries.order_by(Injury.end_week, Injury.end_game) all_injuries = all_injuries.order_by(Injury.end_week, Injury.end_game)
elif sort == 'return-desc': elif sort == "return-desc":
all_injuries = all_injuries.order_by(-Injury.end_week, -Injury.end_game) all_injuries = all_injuries.order_by(-Injury.end_week, -Injury.end_game)
elif sort == 'start-asc': elif sort == "start-asc":
all_injuries = all_injuries.order_by(Injury.start_week, Injury.start_game) all_injuries = all_injuries.order_by(Injury.start_week, Injury.start_game)
elif sort == 'start-desc': elif sort == "start-desc":
all_injuries = all_injuries.order_by(-Injury.start_week, -Injury.start_game) all_injuries = all_injuries.order_by(-Injury.start_week, -Injury.start_game)
return_injuries = { return_injuries = {
'count': all_injuries.count(), "count": all_injuries.count(),
'injuries': [model_to_dict(x, recurse=not short_output) for x in all_injuries] "injuries": [model_to_dict(x, recurse=not short_output) for x in all_injuries],
} }
db.close() db.close()
return return_injuries return return_injuries
@router.patch('/{injury_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/{injury_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def patch_injury(injury_id: int, is_active: Optional[bool] = None, token: str = Depends(oauth2_scheme)): async def patch_injury(
injury_id: int,
is_active: Optional[bool] = None,
token: str = Depends(oauth2_scheme),
):
if not valid_token(token): if not valid_token(token):
logger.warning(f'patch_injury - Bad Token: {token}') logger.warning(f"patch_injury - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_injury = Injury.get_or_none(Injury.id == injury_id) this_injury = Injury.get_or_none(Injury.id == injury_id)
if this_injury is None: if this_injury is None:
db.close() db.close()
raise HTTPException(status_code=404, detail=f'Injury ID {injury_id} not found') raise HTTPException(status_code=404, detail=f"Injury ID {injury_id} not found")
if is_active is not None: if is_active is not None:
this_injury.is_active = is_active this_injury.is_active = is_active
@ -85,15 +97,17 @@ async def patch_injury(injury_id: int, is_active: Optional[bool] = None, token:
return r_injury return r_injury
else: else:
db.close() db.close()
raise HTTPException(status_code=500, detail=f'Unable to patch injury {injury_id}') raise HTTPException(
status_code=500, detail=f"Unable to patch injury {injury_id}"
)
@router.post(f'', include_in_schema=PRIVATE_IN_SCHEMA) @router.post("/", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def post_injury(new_injury: InjuryModel, token: str = Depends(oauth2_scheme)): async def post_injury(new_injury: InjuryModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'post_injury - Bad Token: {token}') logger.warning(f"post_injury - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_injury = Injury(**new_injury.dict()) this_injury = Injury(**new_injury.dict())
@ -103,28 +117,27 @@ async def post_injury(new_injury: InjuryModel, token: str = Depends(oauth2_schem
return r_injury return r_injury
else: else:
db.close() db.close()
raise HTTPException(status_code=500, detail=f'Unable to post injury') raise HTTPException(status_code=500, detail=f"Unable to post injury")
@router.delete('/{injury_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.delete("/{injury_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def delete_injury(injury_id: int, token: str = Depends(oauth2_scheme)): async def delete_injury(injury_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'delete_injury - Bad Token: {token}') logger.warning(f"delete_injury - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_injury = Injury.get_or_none(Injury.id == injury_id) this_injury = Injury.get_or_none(Injury.id == injury_id)
if this_injury is None: if this_injury is None:
db.close() db.close()
raise HTTPException(status_code=404, detail=f'Injury ID {injury_id} not found') raise HTTPException(status_code=404, detail=f"Injury ID {injury_id} not found")
count = this_injury.delete_instance() count = this_injury.delete_instance()
db.close() db.close()
if count == 1: if count == 1:
return f'Injury {injury_id} has been deleted' return f"Injury {injury_id} has been deleted"
else: else:
raise HTTPException(status_code=500, detail=f'Unable to delete injury {injury_id}') raise HTTPException(
status_code=500, detail=f"Unable to delete injury {injury_id}"
)

View File

@ -4,15 +4,17 @@ import logging
import pydantic import pydantic
from ..db_engine import db, Keeper, Player, model_to_dict, chunked, fn from ..db_engine import db, Keeper, Player, model_to_dict, chunked, fn
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors from ..dependencies import (
oauth2_scheme,
logger = logging.getLogger('discord_app') valid_token,
PRIVATE_IN_SCHEMA,
router = APIRouter( handle_db_errors,
prefix='/api/v3/keepers',
tags=['keepers']
) )
logger = logging.getLogger("discord_app")
router = APIRouter(prefix="/api/v3/keepers", tags=["keepers"])
class KeeperModel(pydantic.BaseModel): class KeeperModel(pydantic.BaseModel):
season: int season: int
@ -25,11 +27,14 @@ class KeeperList(pydantic.BaseModel):
keepers: List[KeeperModel] keepers: List[KeeperModel]
@router.get('') @router.get("")
@handle_db_errors @handle_db_errors
async def get_keepers( async def get_keepers(
season: list = Query(default=None), team_id: list = Query(default=None), player_id: list = Query(default=None), season: list = Query(default=None),
short_output: bool = False): team_id: list = Query(default=None),
player_id: list = Query(default=None),
short_output: bool = False,
):
all_keepers = Keeper.select() all_keepers = Keeper.select()
if season is not None: if season is not None:
@ -40,25 +45,29 @@ async def get_keepers(
all_keepers = all_keepers.where(Keeper.player_id << player_id) all_keepers = all_keepers.where(Keeper.player_id << player_id)
return_keepers = { return_keepers = {
'count': all_keepers.count(), "count": all_keepers.count(),
'keepers': [model_to_dict(x, recurse=not short_output) for x in all_keepers] "keepers": [model_to_dict(x, recurse=not short_output) for x in all_keepers],
} }
db.close() db.close()
return return_keepers return return_keepers
@router.patch('/{keeper_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/{keeper_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def patch_keeper( async def patch_keeper(
keeper_id: int, season: Optional[int] = None, team_id: Optional[int] = None, player_id: Optional[int] = None, keeper_id: int,
token: str = Depends(oauth2_scheme)): season: Optional[int] = None,
team_id: Optional[int] = None,
player_id: Optional[int] = None,
token: str = Depends(oauth2_scheme),
):
if not valid_token(token): if not valid_token(token):
logger.warning(f'patch_keeper - Bad Token: {token}') logger.warning(f"patch_keeper - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_keeper = Keeper.get_or_none(Keeper.id == keeper_id) this_keeper = Keeper.get_or_none(Keeper.id == keeper_id)
if not this_keeper: if not this_keeper:
raise HTTPException(status_code=404, detail=f'Keeper ID {keeper_id} not found') raise HTTPException(status_code=404, detail=f"Keeper ID {keeper_id} not found")
if season is not None: if season is not None:
this_keeper.season = season this_keeper.season = season
@ -73,15 +82,17 @@ async def patch_keeper(
return r_keeper return r_keeper
else: else:
db.close() db.close()
raise HTTPException(status_code=500, detail=f'Unable to patch keeper {keeper_id}') raise HTTPException(
status_code=500, detail=f"Unable to patch keeper {keeper_id}"
)
@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) @router.post("/", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def post_keepers(k_list: KeeperList, token: str = Depends(oauth2_scheme)): async def post_keepers(k_list: KeeperList, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'post_keepers - Bad Token: {token}') logger.warning(f"post_keepers - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
new_keepers = [] new_keepers = []
for keeper in k_list.keepers: for keeper in k_list.keepers:
@ -92,26 +103,26 @@ async def post_keepers(k_list: KeeperList, token: str = Depends(oauth2_scheme)):
Keeper.insert_many(batch).on_conflict_ignore().execute() Keeper.insert_many(batch).on_conflict_ignore().execute()
db.close() db.close()
return f'Inserted {len(new_keepers)} keepers' return f"Inserted {len(new_keepers)} keepers"
@router.delete('/{keeper_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.delete("/{keeper_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def delete_keeper(keeper_id: int, token: str = Depends(oauth2_scheme)): async def delete_keeper(keeper_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'delete_keeper - Bad Token: {token}') logger.warning(f"delete_keeper - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_keeper = Keeper.get_or_none(Keeper.id == keeper_id) this_keeper = Keeper.get_or_none(Keeper.id == keeper_id)
if not this_keeper: if not this_keeper:
raise HTTPException(status_code=404, detail=f'Keeper ID {keeper_id} not found') raise HTTPException(status_code=404, detail=f"Keeper ID {keeper_id} not found")
count = this_keeper.delete_instance() count = this_keeper.delete_instance()
db.close() db.close()
if count == 1: if count == 1:
return f'Keeper ID {keeper_id} has been deleted' return f"Keeper ID {keeper_id} has been deleted"
else: else:
raise HTTPException(status_code=500, detail=f'Keeper ID {keeper_id} could not be deleted') raise HTTPException(
status_code=500, detail=f"Keeper ID {keeper_id} could not be deleted"
)

View File

@ -4,15 +4,17 @@ import logging
import pydantic import pydantic
from ..db_engine import db, Manager, Team, Current, model_to_dict, fn from ..db_engine import db, Manager, Team, Current, model_to_dict, fn
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors from ..dependencies import (
oauth2_scheme,
logger = logging.getLogger('discord_app') valid_token,
PRIVATE_IN_SCHEMA,
router = APIRouter( handle_db_errors,
prefix='/api/v3/managers',
tags=['managers']
) )
logger = logging.getLogger("discord_app")
router = APIRouter(prefix="/api/v3/managers", tags=["managers"])
class ManagerModel(pydantic.BaseModel): class ManagerModel(pydantic.BaseModel):
name: str name: str
@ -21,45 +23,45 @@ class ManagerModel(pydantic.BaseModel):
bio: Optional[str] = None bio: Optional[str] = None
@router.get('') @router.get("")
@handle_db_errors @handle_db_errors
async def get_managers( async def get_managers(
name: list = Query(default=None), active: Optional[bool] = None, short_output: Optional[bool] = False): name: list = Query(default=None),
active: Optional[bool] = None,
short_output: Optional[bool] = False,
):
if active is not None: if active is not None:
current = Current.latest() current = Current.latest()
t_query = Team.select_season(current.season) t_query = Team.select_season(current.season)
t_query = t_query.where( t_query = t_query.where(
~(Team.abbrev.endswith('IL')) & ~(Team.abbrev.endswith('MiL')) ~(Team.abbrev.endswith("IL")) & ~(Team.abbrev.endswith("MiL"))
) )
logger.info(f'tquery: {t_query}') logger.info(f"tquery: {t_query}")
a_mgr = [] a_mgr = []
i_mgr = [] i_mgr = []
for x in t_query: for x in t_query:
logger.info(f'Team: {x.abbrev} / mgr1: {x.manager1} / mgr2: {x.manager2}') logger.info(f"Team: {x.abbrev} / mgr1: {x.manager1} / mgr2: {x.manager2}")
if x.manager1 is not None: if x.manager1 is not None:
a_mgr.append(x.manager1) a_mgr.append(x.manager1)
logger.info(f'appending {x.manager1.name}') logger.info(f"appending {x.manager1.name}")
if x.manager2 is not None: if x.manager2 is not None:
a_mgr.append(x.manager2) a_mgr.append(x.manager2)
logger.info(f'appending {x.manager2.name}') logger.info(f"appending {x.manager2.name}")
logger.info(f'a_mgr: {a_mgr}') logger.info(f"a_mgr: {a_mgr}")
if active: if active:
final_mgrs = [model_to_dict(y, recurse=not short_output) for y in a_mgr] final_mgrs = [model_to_dict(y, recurse=not short_output) for y in a_mgr]
else: else:
logger.info(f'checking inactive') logger.info(f"checking inactive")
for z in Manager.select(): for z in Manager.select():
logger.info(f'checking: {z.name}') logger.info(f"checking: {z.name}")
if z not in a_mgr: if z not in a_mgr:
logger.info(f'+inactive: {z.name}') logger.info(f"+inactive: {z.name}")
i_mgr.append(z) i_mgr.append(z)
final_mgrs = [model_to_dict(y, recurse=not short_output) for y in i_mgr] final_mgrs = [model_to_dict(y, recurse=not short_output) for y in i_mgr]
return_managers = { return_managers = {"count": len(final_mgrs), "managers": final_mgrs}
'count': len(final_mgrs),
'managers': final_mgrs
}
else: else:
all_managers = Manager.select() all_managers = Manager.select()
@ -68,15 +70,17 @@ async def get_managers(
all_managers = all_managers.where(fn.Lower(Manager.name) << name_list) all_managers = all_managers.where(fn.Lower(Manager.name) << name_list)
return_managers = { return_managers = {
'count': all_managers.count(), "count": all_managers.count(),
'managers': [model_to_dict(x, recurse=not short_output) for x in all_managers] "managers": [
model_to_dict(x, recurse=not short_output) for x in all_managers
],
} }
db.close() db.close()
return return_managers return return_managers
@router.get('/{manager_id}') @router.get("/{manager_id}")
@handle_db_errors @handle_db_errors
async def get_one_manager(manager_id: int, short_output: Optional[bool] = False): async def get_one_manager(manager_id: int, short_output: Optional[bool] = False):
this_manager = Manager.get_or_none(Manager.id == manager_id) this_manager = Manager.get_or_none(Manager.id == manager_id)
@ -85,22 +89,29 @@ async def get_one_manager(manager_id: int, short_output: Optional[bool] = False)
db.close() db.close()
return r_manager return r_manager
else: else:
raise HTTPException(status_code=404, detail=f'Manager {manager_id} not found') raise HTTPException(status_code=404, detail=f"Manager {manager_id} not found")
@router.patch('/{manager_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/{manager_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def patch_manager( async def patch_manager(
manager_id: int, name: Optional[str] = None, image: Optional[str] = None, headline: Optional[str] = None, manager_id: int,
bio: Optional[str] = None, token: str = Depends(oauth2_scheme)): name: Optional[str] = None,
image: Optional[str] = None,
headline: Optional[str] = None,
bio: Optional[str] = None,
token: str = Depends(oauth2_scheme),
):
if not valid_token(token): if not valid_token(token):
logger.warning(f'patch_manager - Bad Token: {token}') logger.warning(f"patch_manager - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_manager = Manager.get_or_none(Manager.id == manager_id) this_manager = Manager.get_or_none(Manager.id == manager_id)
if this_manager is None: if this_manager is None:
db.close() db.close()
raise HTTPException(status_code=404, detail=f'Manager ID {manager_id} not found') raise HTTPException(
status_code=404, detail=f"Manager ID {manager_id} not found"
)
if name is not None: if name is not None:
this_manager.name = name this_manager.name = name
@ -117,15 +128,17 @@ async def patch_manager(
return r_manager return r_manager
else: else:
db.close() db.close()
raise HTTPException(status_code=500, detail=f'Unable to patch manager {this_manager}') raise HTTPException(
status_code=500, detail=f"Unable to patch manager {this_manager}"
)
@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) @router.post("/", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def post_manager(new_manager: ManagerModel, token: str = Depends(oauth2_scheme)): async def post_manager(new_manager: ManagerModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'post_manager - Bad Token: {token}') logger.warning(f"post_manager - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_manager = Manager(**new_manager.dict()) this_manager = Manager(**new_manager.dict())
@ -135,25 +148,31 @@ async def post_manager(new_manager: ManagerModel, token: str = Depends(oauth2_sc
return r_manager return r_manager
else: else:
db.close() db.close()
raise HTTPException(status_code=500, detail=f'Unable to post manager {this_manager.name}') raise HTTPException(
status_code=500, detail=f"Unable to post manager {this_manager.name}"
)
@router.delete('/{manager_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.delete("/{manager_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def delete_manager(manager_id: int, token: str = Depends(oauth2_scheme)): async def delete_manager(manager_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'delete_manager - Bad Token: {token}') logger.warning(f"delete_manager - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_manager = Manager.get_or_none(Manager.id == manager_id) this_manager = Manager.get_or_none(Manager.id == manager_id)
if this_manager is None: if this_manager is None:
db.close() db.close()
raise HTTPException(status_code=404, detail=f'Manager ID {manager_id} not found') raise HTTPException(
status_code=404, detail=f"Manager ID {manager_id} not found"
)
count = this_manager.delete_instance() count = this_manager.delete_instance()
db.close() db.close()
if count == 1: if count == 1:
return f'Manager {manager_id} has been deleted' return f"Manager {manager_id} has been deleted"
else: else:
raise HTTPException(status_code=500, detail=f'Manager {manager_id} could not be deleted') raise HTTPException(
status_code=500, detail=f"Manager {manager_id} could not be deleted"
)

View File

@ -6,15 +6,27 @@ from typing import List, Optional, Literal
import logging import logging
import pydantic import pydantic
from ..db_engine import db, PitchingStat, Team, Player, Current, model_to_dict, chunked, fn, per_season_weeks from ..db_engine import (
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors db,
PitchingStat,
logger = logging.getLogger('discord_app') Team,
Player,
router = APIRouter( Current,
prefix='/api/v3/pitchingstats', model_to_dict,
tags=['pitchingstats'] chunked,
fn,
per_season_weeks,
) )
from ..dependencies import (
oauth2_scheme,
valid_token,
PRIVATE_IN_SCHEMA,
handle_db_errors,
)
logger = logging.getLogger("discord_app")
router = APIRouter(prefix="/api/v3/pitchingstats", tags=["pitchingstats"])
class PitStatModel(pydantic.BaseModel): class PitStatModel(pydantic.BaseModel):
@ -48,29 +60,37 @@ class PitStatList(pydantic.BaseModel):
stats: List[PitStatModel] stats: List[PitStatModel]
@router.get('') @router.get("")
@handle_db_errors @handle_db_errors
async def get_pitstats( async def get_pitstats(
season: int, s_type: Optional[str] = 'regular', team_abbrev: list = Query(default=None), season: int,
player_name: list = Query(default=None), player_id: list = Query(default=None), s_type: Optional[str] = "regular",
week_start: Optional[int] = None, week_end: Optional[int] = None, game_num: list = Query(default=None), team_abbrev: list = Query(default=None),
limit: Optional[int] = None, ip_min: Optional[float] = None, sort: Optional[str] = None, player_name: list = Query(default=None),
short_output: Optional[bool] = True): player_id: list = Query(default=None),
if 'post' in s_type.lower(): week_start: Optional[int] = None,
week_end: Optional[int] = None,
game_num: list = Query(default=None),
limit: Optional[int] = None,
ip_min: Optional[float] = None,
sort: Optional[str] = None,
short_output: Optional[bool] = True,
):
if "post" in s_type.lower():
all_stats = PitchingStat.post_season(season) all_stats = PitchingStat.post_season(season)
if all_stats.count() == 0: if all_stats.count() == 0:
db.close() db.close()
return {'count': 0, 'stats': []} return {"count": 0, "stats": []}
elif s_type.lower() in ['combined', 'total', 'all']: elif s_type.lower() in ["combined", "total", "all"]:
all_stats = PitchingStat.combined_season(season) all_stats = PitchingStat.combined_season(season)
if all_stats.count() == 0: if all_stats.count() == 0:
db.close() db.close()
return {'count': 0, 'stats': []} return {"count": 0, "stats": []}
else: else:
all_stats = PitchingStat.regular_season(season) all_stats = PitchingStat.regular_season(season)
if all_stats.count() == 0: if all_stats.count() == 0:
db.close() db.close()
return {'count': 0, 'stats': []} return {"count": 0, "stats": []}
if team_abbrev is not None: if team_abbrev is not None:
t_query = Team.select().where(Team.abbrev << [x.upper() for x in team_abbrev]) t_query = Team.select().where(Team.abbrev << [x.upper() for x in team_abbrev])
@ -79,7 +99,9 @@ async def get_pitstats(
if player_id: if player_id:
all_stats = all_stats.where(PitchingStat.player_id << player_id) all_stats = all_stats.where(PitchingStat.player_id << player_id)
else: else:
p_query = Player.select_season(season).where(fn.Lower(Player.name) << [x.lower() for x in player_name]) p_query = Player.select_season(season).where(
fn.Lower(Player.name) << [x.lower() for x in player_name]
)
all_stats = all_stats.where(PitchingStat.player << p_query) all_stats = all_stats.where(PitchingStat.player << p_query)
if game_num: if game_num:
all_stats = all_stats.where(PitchingStat.game == game_num) all_stats = all_stats.where(PitchingStat.game == game_num)
@ -96,7 +118,7 @@ async def get_pitstats(
db.close() db.close()
raise HTTPException( raise HTTPException(
status_code=404, status_code=404,
detail=f'Start week {start} is after end week {end} - cannot pull stats' detail=f"Start week {start} is after end week {end} - cannot pull stats",
) )
all_stats = all_stats.where( all_stats = all_stats.where(
(PitchingStat.week >= start) & (PitchingStat.week <= end) (PitchingStat.week >= start) & (PitchingStat.week <= end)
@ -105,58 +127,81 @@ async def get_pitstats(
if limit: if limit:
all_stats = all_stats.limit(limit) all_stats = all_stats.limit(limit)
if sort: if sort:
if sort == 'newest': if sort == "newest":
all_stats = all_stats.order_by(-PitchingStat.week, -PitchingStat.game) all_stats = all_stats.order_by(-PitchingStat.week, -PitchingStat.game)
return_stats = { return_stats = {
'count': all_stats.count(), "count": all_stats.count(),
'stats': [model_to_dict(x, recurse=not short_output) for x in all_stats] "stats": [model_to_dict(x, recurse=not short_output) for x in all_stats],
} }
db.close() db.close()
return return_stats return return_stats
@router.get('/totals') @router.get("/totals")
@handle_db_errors @handle_db_errors
async def get_totalstats( async def get_totalstats(
season: int, s_type: Literal['regular', 'post', 'total', None] = None, team_abbrev: list = Query(default=None), season: int,
team_id: list = Query(default=None), player_name: list = Query(default=None), s_type: Literal["regular", "post", "total", None] = None,
week_start: Optional[int] = None, week_end: Optional[int] = None, game_num: list = Query(default=None), team_abbrev: list = Query(default=None),
is_sp: Optional[bool] = None, ip_min: Optional[float] = 0.25, sort: Optional[str] = None, team_id: list = Query(default=None),
player_id: list = Query(default=None), short_output: Optional[bool] = False, player_name: list = Query(default=None),
group_by: Literal['team', 'player', 'playerteam'] = 'player', week: list = Query(default=None)): week_start: Optional[int] = None,
week_end: Optional[int] = None,
game_num: list = Query(default=None),
is_sp: Optional[bool] = None,
ip_min: Optional[float] = 0.25,
sort: Optional[str] = None,
player_id: list = Query(default=None),
short_output: Optional[bool] = False,
group_by: Literal["team", "player", "playerteam"] = "player",
week: list = Query(default=None),
):
if sum(1 for x in [s_type, (week_start or week_end), week] if x is not None) > 1: if sum(1 for x in [s_type, (week_start or week_end), week] if x is not None) > 1:
raise HTTPException(status_code=400, detail=f'Only one of s_type, week_start/week_end, or week may be used.') raise HTTPException(
status_code=400,
detail=f"Only one of s_type, week_start/week_end, or week may be used.",
)
# Build SELECT fields conditionally based on group_by to match GROUP BY exactly # Build SELECT fields conditionally based on group_by to match GROUP BY exactly
select_fields = [] select_fields = []
if group_by == 'player': if group_by == "player":
select_fields = [PitchingStat.player] select_fields = [PitchingStat.player]
elif group_by == 'team': elif group_by == "team":
select_fields = [PitchingStat.team] select_fields = [PitchingStat.team]
elif group_by == 'playerteam': elif group_by == "playerteam":
select_fields = [PitchingStat.player, PitchingStat.team] select_fields = [PitchingStat.player, PitchingStat.team]
else: else:
# Default case # Default case
select_fields = [PitchingStat.player] select_fields = [PitchingStat.player]
all_stats = ( all_stats = (
PitchingStat PitchingStat.select(
.select(*select_fields, *select_fields,
fn.SUM(PitchingStat.ip).alias('sum_ip'), fn.SUM(PitchingStat.ip).alias("sum_ip"),
fn.SUM(PitchingStat.hit).alias('sum_hit'), fn.SUM(PitchingStat.run).alias('sum_run'), fn.SUM(PitchingStat.hit).alias("sum_hit"),
fn.SUM(PitchingStat.erun).alias('sum_erun'), fn.SUM(PitchingStat.so).alias('sum_so'), fn.SUM(PitchingStat.run).alias("sum_run"),
fn.SUM(PitchingStat.bb).alias('sum_bb'), fn.SUM(PitchingStat.hbp).alias('sum_hbp'), fn.SUM(PitchingStat.erun).alias("sum_erun"),
fn.SUM(PitchingStat.wp).alias('sum_wp'), fn.SUM(PitchingStat.balk).alias('sum_balk'), fn.SUM(PitchingStat.so).alias("sum_so"),
fn.SUM(PitchingStat.hr).alias('sum_hr'), fn.SUM(PitchingStat.ir).alias('sum_ir'), fn.SUM(PitchingStat.bb).alias("sum_bb"),
fn.SUM(PitchingStat.win).alias('sum_win'), fn.SUM(PitchingStat.loss).alias('sum_loss'), fn.SUM(PitchingStat.hbp).alias("sum_hbp"),
fn.SUM(PitchingStat.hold).alias('sum_hold'), fn.SUM(PitchingStat.sv).alias('sum_sv'), fn.SUM(PitchingStat.wp).alias("sum_wp"),
fn.SUM(PitchingStat.bsv).alias('sum_bsv'), fn.SUM(PitchingStat.irs).alias('sum_irs'), fn.SUM(PitchingStat.balk).alias("sum_balk"),
fn.SUM(PitchingStat.gs).alias('sum_gs'), fn.COUNT(PitchingStat.game).alias('sum_games')) fn.SUM(PitchingStat.hr).alias("sum_hr"),
.where(PitchingStat.season == season) fn.SUM(PitchingStat.ir).alias("sum_ir"),
.having(fn.SUM(PitchingStat.ip) >= ip_min) fn.SUM(PitchingStat.win).alias("sum_win"),
fn.SUM(PitchingStat.loss).alias("sum_loss"),
fn.SUM(PitchingStat.hold).alias("sum_hold"),
fn.SUM(PitchingStat.sv).alias("sum_sv"),
fn.SUM(PitchingStat.bsv).alias("sum_bsv"),
fn.SUM(PitchingStat.irs).alias("sum_irs"),
fn.SUM(PitchingStat.gs).alias("sum_gs"),
fn.COUNT(PitchingStat.game).alias("sum_games"),
)
.where(PitchingStat.season == season)
.having(fn.SUM(PitchingStat.ip) >= ip_min)
) )
if True in [s_type is not None, week_start is not None, week_end is not None]: if True in [s_type is not None, week_start is not None, week_end is not None]:
@ -166,16 +211,20 @@ async def get_totalstats(
elif week_start is not None or week_end is not None: elif week_start is not None or week_end is not None:
if week_start is None or week_end is None: if week_start is None or week_end is None:
raise HTTPException( raise HTTPException(
status_code=400, detail='Both week_start and week_end must be included if either is used.' status_code=400,
detail="Both week_start and week_end must be included if either is used.",
)
weeks["start"] = week_start
if week_end < weeks["start"]:
raise HTTPException(
status_code=400,
detail="week_end must be greater than or equal to week_start",
) )
weeks['start'] = week_start
if week_end < weeks['start']:
raise HTTPException(status_code=400, detail='week_end must be greater than or equal to week_start')
else: else:
weeks['end'] = week_end weeks["end"] = week_end
all_stats = all_stats.where( all_stats = all_stats.where(
(PitchingStat.week >= weeks['start']) & (PitchingStat.week <= weeks['end']) (PitchingStat.week >= weeks["start"]) & (PitchingStat.week <= weeks["end"])
) )
elif week is not None: elif week is not None:
@ -189,9 +238,9 @@ async def get_totalstats(
if not is_sp: if not is_sp:
all_stats = all_stats.where(PitchingStat.gs == 0) all_stats = all_stats.where(PitchingStat.gs == 0)
if sort is not None: if sort is not None:
if sort == 'player': if sort == "player":
all_stats = all_stats.order_by(PitchingStat.player) all_stats = all_stats.order_by(PitchingStat.player)
elif sort == 'team': elif sort == "team":
all_stats = all_stats.order_by(PitchingStat.team) all_stats = all_stats.order_by(PitchingStat.team)
if group_by is not None: if group_by is not None:
# Use the same fields for GROUP BY as we used for SELECT # Use the same fields for GROUP BY as we used for SELECT
@ -200,67 +249,76 @@ async def get_totalstats(
all_teams = Team.select().where(Team.id << team_id) all_teams = Team.select().where(Team.id << team_id)
all_stats = all_stats.where(PitchingStat.team << all_teams) all_stats = all_stats.where(PitchingStat.team << all_teams)
elif team_abbrev is not None: elif team_abbrev is not None:
all_teams = Team.select().where(fn.Lower(Team.abbrev) << [x.lower() for x in team_abbrev]) all_teams = Team.select().where(
fn.Lower(Team.abbrev) << [x.lower() for x in team_abbrev]
)
all_stats = all_stats.where(PitchingStat.team << all_teams) all_stats = all_stats.where(PitchingStat.team << all_teams)
if player_name is not None: if player_name is not None:
all_players = Player.select().where(fn.Lower(Player.name) << [x.lower() for x in player_name]) all_players = Player.select().where(
fn.Lower(Player.name) << [x.lower() for x in player_name]
)
all_stats = all_stats.where(PitchingStat.player << all_players) all_stats = all_stats.where(PitchingStat.player << all_players)
elif player_id is not None: elif player_id is not None:
all_players = Player.select().where(Player.id << player_id) all_players = Player.select().where(Player.id << player_id)
all_stats = all_stats.where(PitchingStat.player << all_players) all_stats = all_stats.where(PitchingStat.player << all_players)
return_stats = { return_stats = {"count": all_stats.count(), "stats": []}
'count': all_stats.count(),
'stats': []
}
for x in all_stats: for x in all_stats:
# Handle player field based on grouping with safe access # Handle player field based on grouping with safe access
this_player = 'TOT' this_player = "TOT"
if 'player' in group_by and hasattr(x, 'player'): if "player" in group_by and hasattr(x, "player"):
this_player = x.player_id if short_output else model_to_dict(x.player, recurse=False) this_player = (
x.player_id if short_output else model_to_dict(x.player, recurse=False)
)
# Handle team field based on grouping with safe access # Handle team field based on grouping with safe access
this_team = 'TOT' this_team = "TOT"
if 'team' in group_by and hasattr(x, 'team'): if "team" in group_by and hasattr(x, "team"):
this_team = x.team_id if short_output else model_to_dict(x.team, recurse=False) this_team = (
x.team_id if short_output else model_to_dict(x.team, recurse=False)
return_stats['stats'].append({ )
'player': this_player,
'team': this_team, return_stats["stats"].append(
'ip': x.sum_ip, {
'hit': x.sum_hit, "player": this_player,
'run': x.sum_run, "team": this_team,
'erun': x.sum_erun, "ip": x.sum_ip,
'so': x.sum_so, "hit": x.sum_hit,
'bb': x.sum_bb, "run": x.sum_run,
'hbp': x.sum_hbp, "erun": x.sum_erun,
'wp': x.sum_wp, "so": x.sum_so,
'balk': x.sum_balk, "bb": x.sum_bb,
'hr': x.sum_hr, "hbp": x.sum_hbp,
'ir': x.sum_ir, "wp": x.sum_wp,
'irs': x.sum_irs, "balk": x.sum_balk,
'gs': x.sum_gs, "hr": x.sum_hr,
'games': x.sum_games, "ir": x.sum_ir,
'win': x.sum_win, "irs": x.sum_irs,
'loss': x.sum_loss, "gs": x.sum_gs,
'hold': x.sum_hold, "games": x.sum_games,
'sv': x.sum_sv, "win": x.sum_win,
'bsv': x.sum_bsv "loss": x.sum_loss,
}) "hold": x.sum_hold,
"sv": x.sum_sv,
"bsv": x.sum_bsv,
}
)
db.close() db.close()
return return_stats return return_stats
@router.patch('/{stat_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/{stat_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def patch_pitstats(stat_id: int, new_stats: PitStatModel, token: str = Depends(oauth2_scheme)): async def patch_pitstats(
stat_id: int, new_stats: PitStatModel, token: str = Depends(oauth2_scheme)
):
if not valid_token(token): if not valid_token(token):
logger.warning(f'patch_pitstats - Bad Token: {token}') logger.warning(f"patch_pitstats - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
if PitchingStat.get_or_none(PitchingStat.id == stat_id) is None: if PitchingStat.get_or_none(PitchingStat.id == stat_id) is None:
raise HTTPException(status_code=404, detail=f'Stat ID {stat_id} not found') raise HTTPException(status_code=404, detail=f"Stat ID {stat_id} not found")
PitchingStat.update(**new_stats.dict()).where(PitchingStat.id == stat_id).execute() PitchingStat.update(**new_stats.dict()).where(PitchingStat.id == stat_id).execute()
r_stat = model_to_dict(PitchingStat.get_by_id(stat_id)) r_stat = model_to_dict(PitchingStat.get_by_id(stat_id))
@ -268,12 +326,12 @@ async def patch_pitstats(stat_id: int, new_stats: PitStatModel, token: str = Dep
return r_stat return r_stat
@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) @router.post("/", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def post_pitstats(s_list: PitStatList, token: str = Depends(oauth2_scheme)): async def post_pitstats(s_list: PitStatList, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'post_pitstats - Bad Token: {token}') logger.warning(f"post_pitstats - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
all_stats = [] all_stats = []
@ -281,9 +339,13 @@ async def post_pitstats(s_list: PitStatList, token: str = Depends(oauth2_scheme)
team = Team.get_or_none(Team.id == x.team_id) team = Team.get_or_none(Team.id == x.team_id)
this_player = Player.get_or_none(Player.id == x.player_id) this_player = Player.get_or_none(Player.id == x.player_id)
if team is None: if team is None:
raise HTTPException(status_code=404, detail=f'Team ID {x.team_id} not found') raise HTTPException(
status_code=404, detail=f"Team ID {x.team_id} not found"
)
if this_player is None: if this_player is None:
raise HTTPException(status_code=404, detail=f'Player ID {x.player_id} not found') raise HTTPException(
status_code=404, detail=f"Player ID {x.player_id} not found"
)
all_stats.append(PitchingStat(**x.dict())) all_stats.append(PitchingStat(**x.dict()))
@ -292,4 +354,4 @@ async def post_pitstats(s_list: PitStatList, token: str = Depends(oauth2_scheme)
PitchingStat.insert_many(batch).on_conflict_ignore().execute() PitchingStat.insert_many(batch).on_conflict_ignore().execute()
db.close() db.close()
return f'Added {len(all_stats)} batting lines' return f"Added {len(all_stats)} batting lines"

View File

@ -24,8 +24,12 @@ async def get_players(
strat_code: list = Query(default=None), strat_code: list = Query(default=None),
is_injured: Optional[bool] = None, is_injured: Optional[bool] = None,
sort: Optional[str] = None, sort: Optional[str] = None,
limit: Optional[int] = Query(default=None, ge=1, description="Maximum number of results to return"), limit: Optional[int] = Query(
offset: Optional[int] = Query(default=None, ge=0, description="Number of results to skip for pagination"), default=None, ge=1, description="Maximum number of results to return"
),
offset: Optional[int] = Query(
default=None, ge=0, description="Number of results to skip for pagination"
),
short_output: Optional[bool] = False, short_output: Optional[bool] = False,
csv: Optional[bool] = False, csv: Optional[bool] = False,
): ):
@ -41,9 +45,9 @@ async def get_players(
limit=limit, limit=limit,
offset=offset, offset=offset,
short_output=short_output or False, short_output=short_output or False,
as_csv=csv or False as_csv=csv or False,
) )
if csv: if csv:
return Response(content=result, media_type="text/csv") return Response(content=result, media_type="text/csv")
return result return result
@ -54,35 +58,29 @@ async def get_players(
@cache_result(ttl=15 * 60, key_prefix="players-search") @cache_result(ttl=15 * 60, key_prefix="players-search")
async def search_players( async def search_players(
q: str = Query(..., description="Search query for player name"), q: str = Query(..., description="Search query for player name"),
season: Optional[int] = Query(default=None, description="Season to search (0 for all)"), season: Optional[int] = Query(
default=None, description="Season to search (0 for all)"
),
limit: int = Query(default=10, ge=1, le=50), limit: int = Query(default=10, ge=1, le=50),
short_output: bool = False, short_output: bool = False,
): ):
"""Search players by name with fuzzy matching.""" """Search players by name with fuzzy matching."""
return PlayerService.search_players( return PlayerService.search_players(
query_str=q, query_str=q, season=season, limit=limit, short_output=short_output
season=season,
limit=limit,
short_output=short_output
) )
@router.get("/{player_id}") @router.get("/{player_id}")
@handle_db_errors @handle_db_errors
@cache_result(ttl=30 * 60, key_prefix="player") @cache_result(ttl=30 * 60, key_prefix="player")
async def get_one_player( async def get_one_player(player_id: int, short_output: Optional[bool] = False):
player_id: int,
short_output: Optional[bool] = False
):
"""Get a single player by ID.""" """Get a single player by ID."""
return PlayerService.get_player(player_id, short_output=short_output or False) return PlayerService.get_player(player_id, short_output=short_output or False)
@router.put("/{player_id}") @router.put("/{player_id}")
async def put_player( async def put_player(
player_id: int, player_id: int, new_player: dict, token: str = Depends(oauth2_scheme)
new_player: dict,
token: str = Depends(oauth2_scheme)
): ):
"""Update a player (full replacement).""" """Update a player (full replacement)."""
return PlayerService.update_player(player_id, new_player, token) return PlayerService.update_player(player_id, new_player, token)
@ -120,14 +118,28 @@ async def patch_player(
data = { data = {
key: value key: value
for key, value in { for key, value in {
'name': name, 'wara': wara, 'image': image, 'image2': image2, "name": name,
'team_id': team_id, 'season': season, "wara": wara,
'pos_1': pos_1, 'pos_2': pos_2, 'pos_3': pos_3, 'pos_4': pos_4, "image": image,
'pos_5': pos_5, 'pos_6': pos_6, 'pos_7': pos_7, 'pos_8': pos_8, "image2": image2,
'vanity_card': vanity_card, 'headshot': headshot, "team_id": team_id,
'il_return': il_return, 'demotion_week': demotion_week, "season": season,
'strat_code': strat_code, 'bbref_id': bbref_id, "pos_1": pos_1,
'injury_rating': injury_rating, 'sbaref_id': sbaref_id, "pos_2": pos_2,
"pos_3": pos_3,
"pos_4": pos_4,
"pos_5": pos_5,
"pos_6": pos_6,
"pos_7": pos_7,
"pos_8": pos_8,
"vanity_card": vanity_card,
"headshot": headshot,
"il_return": il_return,
"demotion_week": demotion_week,
"strat_code": strat_code,
"bbref_id": bbref_id,
"injury_rating": injury_rating,
"sbaref_id": sbaref_id,
}.items() }.items()
if value is not None if value is not None
} }
@ -135,19 +147,13 @@ async def patch_player(
return PlayerService.patch_player(player_id, data, token) return PlayerService.patch_player(player_id, data, token)
@router.post("") @router.post("/")
async def post_players( async def post_players(p_list: dict, token: str = Depends(oauth2_scheme)):
p_list: dict,
token: str = Depends(oauth2_scheme)
):
"""Create multiple players.""" """Create multiple players."""
return PlayerService.create_players(p_list.get("players", []), token) return PlayerService.create_players(p_list.get("players", []), token)
@router.delete("/{player_id}") @router.delete("/{player_id}")
async def delete_player( async def delete_player(player_id: int, token: str = Depends(oauth2_scheme)):
player_id: int,
token: str = Depends(oauth2_scheme)
):
"""Delete a player.""" """Delete a player."""
return PlayerService.delete_player(player_id, token) return PlayerService.delete_player(player_id, token)

View File

@ -4,15 +4,17 @@ import logging
import pydantic import pydantic
from ..db_engine import db, Result, Team, model_to_dict, chunked from ..db_engine import db, Result, Team, model_to_dict, chunked
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors from ..dependencies import (
oauth2_scheme,
logger = logging.getLogger('discord_app') valid_token,
PRIVATE_IN_SCHEMA,
router = APIRouter( handle_db_errors,
prefix='/api/v3/results',
tags=['results']
) )
logger = logging.getLogger("discord_app")
router = APIRouter(prefix="/api/v3/results", tags=["results"])
class ResultModel(pydantic.BaseModel): class ResultModel(pydantic.BaseModel):
week: int week: int
@ -29,13 +31,18 @@ class ResultList(pydantic.BaseModel):
results: List[ResultModel] results: List[ResultModel]
@router.get('') @router.get("")
@handle_db_errors @handle_db_errors
async def get_results( async def get_results(
season: int, team_abbrev: list = Query(default=None), week_start: Optional[int] = None, season: int,
week_end: Optional[int] = None, game_num: list = Query(default=None), team_abbrev: list = Query(default=None),
away_abbrev: list = Query(default=None), home_abbrev: list = Query(default=None), week_start: Optional[int] = None,
short_output: Optional[bool] = False): week_end: Optional[int] = None,
game_num: list = Query(default=None),
away_abbrev: list = Query(default=None),
home_abbrev: list = Query(default=None),
short_output: Optional[bool] = False,
):
all_results = Result.select_season(season) all_results = Result.select_season(season)
if team_abbrev is not None: if team_abbrev is not None:
@ -68,14 +75,14 @@ async def get_results(
all_results = all_results.where(Result.week <= week_end) all_results = all_results.where(Result.week <= week_end)
return_results = { return_results = {
'count': all_results.count(), "count": all_results.count(),
'results': [model_to_dict(x, recurse=not short_output) for x in all_results] "results": [model_to_dict(x, recurse=not short_output) for x in all_results],
} }
db.close() db.close()
return return_results return return_results
@router.get('/{result_id}') @router.get("/{result_id}")
@handle_db_errors @handle_db_errors
async def get_one_result(result_id: int, short_output: Optional[bool] = False): async def get_one_result(result_id: int, short_output: Optional[bool] = False):
this_result = Result.get_or_none(Result.id == result_id) this_result = Result.get_or_none(Result.id == result_id)
@ -87,20 +94,27 @@ async def get_one_result(result_id: int, short_output: Optional[bool] = False):
return r_result return r_result
@router.patch('/{result_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/{result_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def patch_result( async def patch_result(
result_id: int, week_num: Optional[int] = None, game_num: Optional[int] = None, result_id: int,
away_team_id: Optional[int] = None, home_team_id: Optional[int] = None, away_score: Optional[int] = None, week_num: Optional[int] = None,
home_score: Optional[int] = None, season: Optional[int] = None, scorecard_url: Optional[str] = None, game_num: Optional[int] = None,
token: str = Depends(oauth2_scheme)): away_team_id: Optional[int] = None,
home_team_id: Optional[int] = None,
away_score: Optional[int] = None,
home_score: Optional[int] = None,
season: Optional[int] = None,
scorecard_url: Optional[str] = None,
token: str = Depends(oauth2_scheme),
):
if not valid_token(token): if not valid_token(token):
logger.warning(f'patch_player - Bad Token: {token}') logger.warning(f"patch_player - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_result = Result.get_or_none(Result.id == result_id) this_result = Result.get_or_none(Result.id == result_id)
if this_result is None: if this_result is None:
raise HTTPException(status_code=404, detail=f'Result ID {result_id} not found') raise HTTPException(status_code=404, detail=f"Result ID {result_id} not found")
if week_num is not None: if week_num is not None:
this_result.week = week_num this_result.week = week_num
@ -132,22 +146,28 @@ async def patch_result(
return r_result return r_result
else: else:
db.close() db.close()
raise HTTPException(status_code=500, detail=f'Unable to patch result {result_id}') raise HTTPException(
status_code=500, detail=f"Unable to patch result {result_id}"
)
@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) @router.post("/", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def post_results(result_list: ResultList, token: str = Depends(oauth2_scheme)): async def post_results(result_list: ResultList, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'patch_player - Bad Token: {token}') logger.warning(f"patch_player - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
new_results = [] new_results = []
for x in result_list.results: for x in result_list.results:
if Team.get_or_none(Team.id == x.awayteam_id) is None: if Team.get_or_none(Team.id == x.awayteam_id) is None:
raise HTTPException(status_code=404, detail=f'Team ID {x.awayteam_id} not found') raise HTTPException(
status_code=404, detail=f"Team ID {x.awayteam_id} not found"
)
if Team.get_or_none(Team.id == x.hometeam_id) is None: if Team.get_or_none(Team.id == x.hometeam_id) is None:
raise HTTPException(status_code=404, detail=f'Team ID {x.hometeam_id} not found') raise HTTPException(
status_code=404, detail=f"Team ID {x.hometeam_id} not found"
)
new_results.append(x.dict()) new_results.append(x.dict())
@ -156,27 +176,27 @@ async def post_results(result_list: ResultList, token: str = Depends(oauth2_sche
Result.insert_many(batch).on_conflict_ignore().execute() Result.insert_many(batch).on_conflict_ignore().execute()
db.close() db.close()
return f'Inserted {len(new_results)} results' return f"Inserted {len(new_results)} results"
@router.delete('/{result_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.delete("/{result_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def delete_result(result_id: int, token: str = Depends(oauth2_scheme)): async def delete_result(result_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'delete_result - Bad Token: {token}') logger.warning(f"delete_result - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_result = Result.get_or_none(Result.id == result_id) this_result = Result.get_or_none(Result.id == result_id)
if not this_result: if not this_result:
db.close() db.close()
raise HTTPException(status_code=404, detail=f'Result ID {result_id} not found') raise HTTPException(status_code=404, detail=f"Result ID {result_id} not found")
count = this_result.delete_instance() count = this_result.delete_instance()
db.close() db.close()
if count == 1: if count == 1:
return f'Result {result_id} has been deleted' return f"Result {result_id} has been deleted"
else: else:
raise HTTPException(status_code=500, detail=f'Result {result_id} could not be deleted') raise HTTPException(
status_code=500, detail=f"Result {result_id} could not be deleted"
)

View File

@ -7,15 +7,17 @@ import logging
import pydantic import pydantic
from ..db_engine import db, SbaPlayer, model_to_dict, fn, chunked, query_to_csv from ..db_engine import db, SbaPlayer, model_to_dict, fn, chunked, query_to_csv
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors from ..dependencies import (
oauth2_scheme,
logger = logging.getLogger('discord_app') valid_token,
PRIVATE_IN_SCHEMA,
router = APIRouter( handle_db_errors,
prefix='/api/v3/sbaplayers',
tags=['sbaplayers']
) )
logger = logging.getLogger("discord_app")
router = APIRouter(prefix="/api/v3/sbaplayers", tags=["sbaplayers"])
class SbaPlayerModel(pydantic.BaseModel): class SbaPlayerModel(pydantic.BaseModel):
first_name: str first_name: str
@ -30,19 +32,26 @@ class PlayerList(pydantic.BaseModel):
players: List[SbaPlayerModel] players: List[SbaPlayerModel]
@router.get('') @router.get("")
@handle_db_errors @handle_db_errors
async def get_players( async def get_players(
full_name: list = Query(default=None), first_name: list = Query(default=None), full_name: list = Query(default=None),
last_name: list = Query(default=None), key_fangraphs: list = Query(default=None), first_name: list = Query(default=None),
key_bbref: list = Query(default=None), key_retro: list = Query(default=None), last_name: list = Query(default=None),
key_mlbam: list = Query(default=None), sort: Optional[str] = None, csv: Optional[bool] = False): key_fangraphs: list = Query(default=None),
key_bbref: list = Query(default=None),
key_retro: list = Query(default=None),
key_mlbam: list = Query(default=None),
sort: Optional[str] = None,
csv: Optional[bool] = False,
):
all_players = SbaPlayer.select() all_players = SbaPlayer.select()
if full_name is not None: if full_name is not None:
name_list = [x.lower() for x in full_name] name_list = [x.lower() for x in full_name]
all_players = all_players.where( all_players = all_players.where(
fn.lower(SbaPlayer.first_name) + ' ' + fn.lower(SbaPlayer.last_name) << name_list fn.lower(SbaPlayer.first_name) + " " + fn.lower(SbaPlayer.last_name)
<< name_list
) )
if first_name is not None: if first_name is not None:
name_list = [x.lower() for x in first_name] name_list = [x.lower() for x in first_name]
@ -60,76 +69,87 @@ async def get_players(
all_players = all_players.where(fn.lower(SbaPlayer.key_retro) << name_list) all_players = all_players.where(fn.lower(SbaPlayer.key_retro) << name_list)
if key_mlbam is not None: if key_mlbam is not None:
all_players = all_players.where(SbaPlayer.key_mlbam << key_mlbam) all_players = all_players.where(SbaPlayer.key_mlbam << key_mlbam)
if sort is not None: if sort is not None:
if sort in ['firstname-asc', 'fullname-asc']: if sort in ["firstname-asc", "fullname-asc"]:
all_players = all_players.order_by(SbaPlayer.first_name) all_players = all_players.order_by(SbaPlayer.first_name)
elif sort in ['firstname-desc', 'fullname-desc']: elif sort in ["firstname-desc", "fullname-desc"]:
all_players = all_players.order_by(-SbaPlayer.first_name) all_players = all_players.order_by(-SbaPlayer.first_name)
elif sort == 'lastname-asc': elif sort == "lastname-asc":
all_players = all_players.order_by(SbaPlayer.last_name) all_players = all_players.order_by(SbaPlayer.last_name)
elif sort == 'lastname-desc': elif sort == "lastname-desc":
all_players = all_players.order_by(-SbaPlayer.last_name) all_players = all_players.order_by(-SbaPlayer.last_name)
elif sort == 'fangraphs-asc': elif sort == "fangraphs-asc":
all_players = all_players.order_by(SbaPlayer.key_fangraphs) all_players = all_players.order_by(SbaPlayer.key_fangraphs)
elif sort == 'fangraphs-desc': elif sort == "fangraphs-desc":
all_players = all_players.order_by(-SbaPlayer.key_fangraphs) all_players = all_players.order_by(-SbaPlayer.key_fangraphs)
elif sort == 'bbref-asc': elif sort == "bbref-asc":
all_players = all_players.order_by(SbaPlayer.key_bbref) all_players = all_players.order_by(SbaPlayer.key_bbref)
elif sort == 'bbref-desc': elif sort == "bbref-desc":
all_players = all_players.order_by(-SbaPlayer.key_bbref) all_players = all_players.order_by(-SbaPlayer.key_bbref)
elif sort == 'retro-asc': elif sort == "retro-asc":
all_players = all_players.order_by(SbaPlayer.key_retro) all_players = all_players.order_by(SbaPlayer.key_retro)
elif sort == 'retro-desc': elif sort == "retro-desc":
all_players = all_players.order_by(-SbaPlayer.key_retro) all_players = all_players.order_by(-SbaPlayer.key_retro)
elif sort == 'mlbam-asc': elif sort == "mlbam-asc":
all_players = all_players.order_by(SbaPlayer.key_mlbam) all_players = all_players.order_by(SbaPlayer.key_mlbam)
elif sort == 'mlbam-desc': elif sort == "mlbam-desc":
all_players = all_players.order_by(-SbaPlayer.key_mlbam) all_players = all_players.order_by(-SbaPlayer.key_mlbam)
if csv: if csv:
return_val = query_to_csv(all_players) return_val = query_to_csv(all_players)
db.close() db.close()
return Response(content=return_val, media_type='text/csv') return Response(content=return_val, media_type="text/csv")
return_val = {'count': all_players.count(), 'players': [ return_val = {
model_to_dict(x) for x in all_players "count": all_players.count(),
]} "players": [model_to_dict(x) for x in all_players],
}
db.close() db.close()
return return_val return return_val
@router.get('/{player_id}') @router.get("/{player_id}")
@handle_db_errors @handle_db_errors
async def get_one_player(player_id: int): async def get_one_player(player_id: int):
this_player = SbaPlayer.get_or_none(SbaPlayer.id == player_id) this_player = SbaPlayer.get_or_none(SbaPlayer.id == player_id)
if this_player is None: if this_player is None:
db.close() db.close()
raise HTTPException(status_code=404, detail=f'SbaPlayer id {player_id} not found') raise HTTPException(
status_code=404, detail=f"SbaPlayer id {player_id} not found"
)
r_data = model_to_dict(this_player) r_data = model_to_dict(this_player)
db.close() db.close()
return r_data return r_data
@router.patch('/{player_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/{player_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def patch_player( async def patch_player(
player_id: int, first_name: Optional[str] = None, last_name: Optional[str] = None, player_id: int,
key_fangraphs: Optional[str] = None, key_bbref: Optional[str] = None, key_retro: Optional[str] = None, first_name: Optional[str] = None,
key_mlbam: Optional[str] = None, token: str = Depends(oauth2_scheme)): last_name: Optional[str] = None,
key_fangraphs: Optional[str] = None,
key_bbref: Optional[str] = None,
key_retro: Optional[str] = None,
key_mlbam: Optional[str] = None,
token: str = Depends(oauth2_scheme),
):
if not valid_token(token): if not valid_token(token):
logging.warning(f'Bad Token: {token}') logging.warning(f"Bad Token: {token}")
db.close() db.close()
raise HTTPException( raise HTTPException(
status_code=401, status_code=401,
detail='You are not authorized to patch mlb players. This event has been logged.' detail="You are not authorized to patch mlb players. This event has been logged.",
) )
this_player = SbaPlayer.get_or_none(SbaPlayer.id == player_id) this_player = SbaPlayer.get_or_none(SbaPlayer.id == player_id)
if this_player is None: if this_player is None:
db.close() db.close()
raise HTTPException(status_code=404, detail=f'SbaPlayer id {player_id} not found') raise HTTPException(
status_code=404, detail=f"SbaPlayer id {player_id} not found"
)
if first_name is not None: if first_name is not None:
this_player.first_name = first_name this_player.first_name = first_name
@ -152,33 +172,38 @@ async def patch_player(
db.close() db.close()
raise HTTPException( raise HTTPException(
status_code=418, status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that player' detail="Well slap my ass and call me a teapot; I could not save that player",
) )
@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) @router.post("/", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def post_players(players: PlayerList, token: str = Depends(oauth2_scheme)): async def post_players(players: PlayerList, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logging.warning(f'Bad Token: {token}') logging.warning(f"Bad Token: {token}")
db.close() db.close()
raise HTTPException( raise HTTPException(
status_code=401, status_code=401,
detail='You are not authorized to post mlb players. This event has been logged.' detail="You are not authorized to post mlb players. This event has been logged.",
) )
new_players = [] new_players = []
for x in players.players: for x in players.players:
dupes = SbaPlayer.select().where( dupes = SbaPlayer.select().where(
((SbaPlayer.key_fangraphs == x.key_fangraphs) & (x.key_fangraphs is not None)) | ((SbaPlayer.key_mlbam == x.key_mlbam) & (x.key_mlbam is not None)) | (
((SbaPlayer.key_retro == x.key_retro) & (x.key_retro is not None)) | ((SbaPlayer.key_bbref == x.key_bbref) & (x.key_bbref is not None)) (SbaPlayer.key_fangraphs == x.key_fangraphs)
& (x.key_fangraphs is not None)
)
| ((SbaPlayer.key_mlbam == x.key_mlbam) & (x.key_mlbam is not None))
| ((SbaPlayer.key_retro == x.key_retro) & (x.key_retro is not None))
| ((SbaPlayer.key_bbref == x.key_bbref) & (x.key_bbref is not None))
) )
if dupes.count() > 0: if dupes.count() > 0:
logger.error(f'Found a dupe for {x}') logger.error(f"Found a dupe for {x}")
db.close() db.close()
raise HTTPException( raise HTTPException(
status_code=400, status_code=400,
detail=f'{x.first_name} {x.last_name} has a key already in the database' detail=f"{x.first_name} {x.last_name} has a key already in the database",
) )
new_players.append(x.model_dump()) new_players.append(x.model_dump())
@ -188,32 +213,33 @@ async def post_players(players: PlayerList, token: str = Depends(oauth2_scheme))
SbaPlayer.insert_many(batch).on_conflict_ignore().execute() SbaPlayer.insert_many(batch).on_conflict_ignore().execute()
db.close() db.close()
return f'Inserted {len(new_players)} new MLB players' return f"Inserted {len(new_players)} new MLB players"
@router.post('/one', include_in_schema=PRIVATE_IN_SCHEMA) @router.post("/one", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def post_one_player(player: SbaPlayerModel, token: str = Depends(oauth2_scheme)): async def post_one_player(player: SbaPlayerModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logging.warning(f'Bad Token: {token}') logging.warning(f"Bad Token: {token}")
db.close() db.close()
raise HTTPException( raise HTTPException(
status_code=401, status_code=401,
detail='You are not authorized to post mlb players. This event has been logged.' detail="You are not authorized to post mlb players. This event has been logged.",
) )
dupes = SbaPlayer.select().where( dupes = SbaPlayer.select().where(
(SbaPlayer.key_fangraphs == player.key_fangraphs) | (SbaPlayer.key_mlbam == player.key_mlbam) | (SbaPlayer.key_fangraphs == player.key_fangraphs)
(SbaPlayer.key_bbref == player.key_bbref) | (SbaPlayer.key_mlbam == player.key_mlbam)
| (SbaPlayer.key_bbref == player.key_bbref)
) )
if dupes.count() > 0: if dupes.count() > 0:
logging.info(f'POST /SbaPlayers/one - dupes found:') logging.info(f"POST /SbaPlayers/one - dupes found:")
for x in dupes: for x in dupes:
logging.info(f'{x}') logging.info(f"{x}")
db.close() db.close()
raise HTTPException( raise HTTPException(
status_code=400, status_code=400,
detail=f'{player.first_name} {player.last_name} has a key already in the database' detail=f"{player.first_name} {player.last_name} has a key already in the database",
) )
new_player = SbaPlayer(**player.dict()) new_player = SbaPlayer(**player.dict())
@ -226,30 +252,34 @@ async def post_one_player(player: SbaPlayerModel, token: str = Depends(oauth2_sc
db.close() db.close()
raise HTTPException( raise HTTPException(
status_code=418, status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that player' detail="Well slap my ass and call me a teapot; I could not save that player",
) )
@router.delete('/{player_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.delete("/{player_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def delete_player(player_id: int, token: str = Depends(oauth2_scheme)): async def delete_player(player_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logging.warning(f'Bad Token: {token}') logging.warning(f"Bad Token: {token}")
db.close() db.close()
raise HTTPException( raise HTTPException(
status_code=401, status_code=401,
detail='You are not authorized to delete mlb players. This event has been logged.' detail="You are not authorized to delete mlb players. This event has been logged.",
) )
this_player = SbaPlayer.get_or_none(SbaPlayer.id == player_id) this_player = SbaPlayer.get_or_none(SbaPlayer.id == player_id)
if this_player is None: if this_player is None:
db.close() db.close()
raise HTTPException(status_code=404, detail=f'SbaPlayer id {player_id} not found') raise HTTPException(
status_code=404, detail=f"SbaPlayer id {player_id} not found"
)
count = this_player.delete_instance() count = this_player.delete_instance()
db.close() db.close()
if count == 1: if count == 1:
return f'Player {player_id} has been deleted' return f"Player {player_id} has been deleted"
else: else:
raise HTTPException(status_code=500, detail=f'Player {player_id} was not deleted') raise HTTPException(
status_code=500, detail=f"Player {player_id} was not deleted"
)

View File

@ -4,15 +4,17 @@ import logging
import pydantic import pydantic
from ..db_engine import db, Schedule, Team, model_to_dict, chunked from ..db_engine import db, Schedule, Team, model_to_dict, chunked
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors from ..dependencies import (
oauth2_scheme,
logger = logging.getLogger('discord_app') valid_token,
PRIVATE_IN_SCHEMA,
router = APIRouter( handle_db_errors,
prefix='/api/v3/schedules',
tags=['schedules']
) )
logger = logging.getLogger("discord_app")
router = APIRouter(prefix="/api/v3/schedules", tags=["schedules"])
class ScheduleModel(pydantic.BaseModel): class ScheduleModel(pydantic.BaseModel):
week: int week: int
@ -26,12 +28,17 @@ class ScheduleList(pydantic.BaseModel):
schedules: List[ScheduleModel] schedules: List[ScheduleModel]
@router.get('') @router.get("")
@handle_db_errors @handle_db_errors
async def get_schedules( async def get_schedules(
season: int, team_abbrev: list = Query(default=None), away_abbrev: list = Query(default=None), season: int,
home_abbrev: list = Query(default=None), week_start: Optional[int] = None, week_end: Optional[int] = None, team_abbrev: list = Query(default=None),
short_output: Optional[bool] = True): away_abbrev: list = Query(default=None),
home_abbrev: list = Query(default=None),
week_start: Optional[int] = None,
week_end: Optional[int] = None,
short_output: Optional[bool] = True,
):
all_sched = Schedule.select_season(season) all_sched = Schedule.select_season(season)
if team_abbrev is not None: if team_abbrev is not None:
@ -63,14 +70,14 @@ async def get_schedules(
all_sched = all_sched.order_by(Schedule.id) all_sched = all_sched.order_by(Schedule.id)
return_sched = { return_sched = {
'count': all_sched.count(), "count": all_sched.count(),
'schedules': [model_to_dict(x, recurse=not short_output) for x in all_sched] "schedules": [model_to_dict(x, recurse=not short_output) for x in all_sched],
} }
db.close() db.close()
return return_sched return return_sched
@router.get('/{schedule_id}') @router.get("/{schedule_id}")
@handle_db_errors @handle_db_errors
async def get_one_schedule(schedule_id: int): async def get_one_schedule(schedule_id: int):
this_sched = Schedule.get_or_none(Schedule.id == schedule_id) this_sched = Schedule.get_or_none(Schedule.id == schedule_id)
@ -82,19 +89,26 @@ async def get_one_schedule(schedule_id: int):
return r_sched return r_sched
@router.patch('/{schedule_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/{schedule_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def patch_schedule( async def patch_schedule(
schedule_id: int, week: list = Query(default=None), awayteam_id: Optional[int] = None, schedule_id: int,
hometeam_id: Optional[int] = None, gamecount: Optional[int] = None, season: Optional[int] = None, week: list = Query(default=None),
token: str = Depends(oauth2_scheme)): awayteam_id: Optional[int] = None,
hometeam_id: Optional[int] = None,
gamecount: Optional[int] = None,
season: Optional[int] = None,
token: str = Depends(oauth2_scheme),
):
if not valid_token(token): if not valid_token(token):
logger.warning(f'patch_schedule - Bad Token: {token}') logger.warning(f"patch_schedule - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_sched = Schedule.get_or_none(Schedule.id == schedule_id) this_sched = Schedule.get_or_none(Schedule.id == schedule_id)
if this_sched is None: if this_sched is None:
raise HTTPException(status_code=404, detail=f'Schedule ID {schedule_id} not found') raise HTTPException(
status_code=404, detail=f"Schedule ID {schedule_id} not found"
)
if week is not None: if week is not None:
this_sched.week = week this_sched.week = week
@ -117,22 +131,28 @@ async def patch_schedule(
return r_sched return r_sched
else: else:
db.close() db.close()
raise HTTPException(status_code=500, detail=f'Unable to patch schedule {schedule_id}') raise HTTPException(
status_code=500, detail=f"Unable to patch schedule {schedule_id}"
)
@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) @router.post("/", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def post_schedules(sched_list: ScheduleList, token: str = Depends(oauth2_scheme)): async def post_schedules(sched_list: ScheduleList, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'post_schedules - Bad Token: {token}') logger.warning(f"post_schedules - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
new_sched = [] new_sched = []
for x in sched_list.schedules: for x in sched_list.schedules:
if Team.get_or_none(Team.id == x.awayteam_id) is None: if Team.get_or_none(Team.id == x.awayteam_id) is None:
raise HTTPException(status_code=404, detail=f'Team ID {x.awayteam_id} not found') raise HTTPException(
status_code=404, detail=f"Team ID {x.awayteam_id} not found"
)
if Team.get_or_none(Team.id == x.hometeam_id) is None: if Team.get_or_none(Team.id == x.hometeam_id) is None:
raise HTTPException(status_code=404, detail=f'Team ID {x.hometeam_id} not found') raise HTTPException(
status_code=404, detail=f"Team ID {x.hometeam_id} not found"
)
new_sched.append(x.dict()) new_sched.append(x.dict())
@ -141,24 +161,28 @@ async def post_schedules(sched_list: ScheduleList, token: str = Depends(oauth2_s
Schedule.insert_many(batch).on_conflict_ignore().execute() Schedule.insert_many(batch).on_conflict_ignore().execute()
db.close() db.close()
return f'Inserted {len(new_sched)} schedules' return f"Inserted {len(new_sched)} schedules"
@router.delete('/{schedule_id}', include_in_schema=PRIVATE_IN_SCHEMA) @router.delete("/{schedule_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def delete_schedule(schedule_id: int, token: str = Depends(oauth2_scheme)): async def delete_schedule(schedule_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning(f'delete_schedule - Bad Token: {token}') logger.warning(f"delete_schedule - Bad Token: {token}")
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail="Unauthorized")
this_sched = Schedule.get_or_none(Schedule.id == schedule_id) this_sched = Schedule.get_or_none(Schedule.id == schedule_id)
if this_sched is None: if this_sched is None:
raise HTTPException(status_code=404, detail=f'Schedule ID {schedule_id} not found') raise HTTPException(
status_code=404, detail=f"Schedule ID {schedule_id} not found"
)
count = this_sched.delete_instance() count = this_sched.delete_instance()
db.close() db.close()
if count == 1: if count == 1:
return f'Schedule {this_sched} has been deleted' return f"Schedule {this_sched} has been deleted"
else: else:
raise HTTPException(status_code=500, detail=f'Schedule {this_sched} could not be deleted') raise HTTPException(
status_code=500, detail=f"Schedule {this_sched} could not be deleted"
)

View File

@ -230,7 +230,7 @@ async def patch_game(
raise HTTPException(status_code=500, detail=f"Unable to patch game {game_id}") raise HTTPException(status_code=500, detail=f"Unable to patch game {game_id}")
@router.post("", include_in_schema=PRIVATE_IN_SCHEMA) @router.post("/", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def post_games(game_list: GameList, token: str = Depends(oauth2_scheme)) -> Any: async def post_games(game_list: GameList, token: str = Depends(oauth2_scheme)) -> Any:
if not valid_token(token): if not valid_token(token):

View File

@ -6,16 +6,21 @@ Thin HTTP layer using TeamService for business logic.
from fastapi import APIRouter, Query, Response, Depends from fastapi import APIRouter, Query, Response, Depends
from typing import List, Optional, Literal from typing import List, Optional, Literal
from ..dependencies import oauth2_scheme, PRIVATE_IN_SCHEMA, handle_db_errors, cache_result from ..dependencies import (
oauth2_scheme,
PRIVATE_IN_SCHEMA,
handle_db_errors,
cache_result,
)
from ..services.base import BaseService from ..services.base import BaseService
from ..services.team_service import TeamService from ..services.team_service import TeamService
router = APIRouter(prefix='/api/v3/teams', tags=['teams']) router = APIRouter(prefix="/api/v3/teams", tags=["teams"])
@router.get('') @router.get("")
@handle_db_errors @handle_db_errors
@cache_result(ttl=10*60, key_prefix='teams') @cache_result(ttl=10 * 60, key_prefix="teams")
async def get_teams( async def get_teams(
season: Optional[int] = None, season: Optional[int] = None,
owner_id: list = Query(default=None), owner_id: list = Query(default=None),
@ -23,7 +28,7 @@ async def get_teams(
team_abbrev: list = Query(default=None), team_abbrev: list = Query(default=None),
active_only: Optional[bool] = False, active_only: Optional[bool] = False,
short_output: Optional[bool] = False, short_output: Optional[bool] = False,
csv: Optional[bool] = False csv: Optional[bool] = False,
): ):
"""Get teams with filtering.""" """Get teams with filtering."""
result = TeamService.get_teams( result = TeamService.get_teams(
@ -33,46 +38,41 @@ async def get_teams(
team_abbrev=team_abbrev if team_abbrev else None, team_abbrev=team_abbrev if team_abbrev else None,
active_only=active_only or False, active_only=active_only or False,
short_output=short_output or False, short_output=short_output or False,
as_csv=csv or False as_csv=csv or False,
) )
if csv: if csv:
return Response(content=result, media_type='text/csv') return Response(content=result, media_type="text/csv")
return result return result
@router.get('/{team_id}') @router.get("/{team_id}")
@handle_db_errors @handle_db_errors
@cache_result(ttl=30*60, key_prefix='team') @cache_result(ttl=30 * 60, key_prefix="team")
async def get_one_team(team_id: int): async def get_one_team(team_id: int):
"""Get a single team by ID.""" """Get a single team by ID."""
return TeamService.get_team(team_id) return TeamService.get_team(team_id)
@router.get('/{team_id}/roster') @router.get("/{team_id}/roster")
@handle_db_errors @handle_db_errors
@cache_result(ttl=30*60, key_prefix='team-roster') @cache_result(ttl=30 * 60, key_prefix="team-roster")
async def get_team_roster_default( async def get_team_roster_default(team_id: int, sort: Optional[str] = None):
team_id: int,
sort: Optional[str] = None
):
"""Get team roster with IL lists (defaults to current season).""" """Get team roster with IL lists (defaults to current season)."""
return TeamService.get_team_roster(team_id, 'current', sort=sort) return TeamService.get_team_roster(team_id, "current", sort=sort)
@router.get('/{team_id}/roster/{which}') @router.get("/{team_id}/roster/{which}")
@handle_db_errors @handle_db_errors
@cache_result(ttl=30*60, key_prefix='team-roster') @cache_result(ttl=30 * 60, key_prefix="team-roster")
async def get_team_roster( async def get_team_roster(
team_id: int, team_id: int, which: Literal["current", "next"], sort: Optional[str] = None
which: Literal['current', 'next'],
sort: Optional[str] = None
): ):
"""Get team roster with IL lists.""" """Get team roster with IL lists."""
return TeamService.get_team_roster(team_id, which, sort=sort) return TeamService.get_team_roster(team_id, which, sort=sort)
@router.patch('/{team_id}') @router.patch("/{team_id}")
async def patch_team( async def patch_team(
team_id: int, team_id: int,
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
@ -95,31 +95,33 @@ async def patch_team(
data = { data = {
key: value key: value
for key, value in { for key, value in {
'manager1_id': manager1_id, 'manager2_id': manager2_id, "manager1_id": manager1_id,
'gmid': gmid, 'gmid2': gmid2, 'mascot': mascot, "manager2_id": manager2_id,
'stadium': stadium, 'thumbnail': thumbnail, 'color': color, "gmid": gmid,
'abbrev': abbrev, 'sname': sname, 'lname': lname, "gmid2": gmid2,
'dice_color': dice_color, 'division_id': division_id, "mascot": mascot,
"stadium": stadium,
"thumbnail": thumbnail,
"color": color,
"abbrev": abbrev,
"sname": sname,
"lname": lname,
"dice_color": dice_color,
"division_id": division_id,
}.items() }.items()
if value is not None if value is not None
} }
return TeamService.update_team(team_id, data, token) return TeamService.update_team(team_id, data, token)
@router.post('') @router.post("/")
async def post_teams( async def post_teams(team_list: dict, token: str = Depends(oauth2_scheme)):
team_list: dict,
token: str = Depends(oauth2_scheme)
):
"""Create multiple teams.""" """Create multiple teams."""
return TeamService.create_teams(team_list.get("teams", []), token) return TeamService.create_teams(team_list.get("teams", []), token)
@router.delete('/{team_id}') @router.delete("/{team_id}")
async def delete_team( async def delete_team(team_id: int, token: str = Depends(oauth2_scheme)):
team_id: int,
token: str = Depends(oauth2_scheme)
):
"""Delete a team.""" """Delete a team."""
return TeamService.delete_team(team_id, token) return TeamService.delete_team(team_id, token)

View File

@ -132,7 +132,7 @@ async def patch_transactions(
return f"Updated {these_moves.count()} transactions" return f"Updated {these_moves.count()} transactions"
@router.post("", include_in_schema=PRIVATE_IN_SCHEMA) @router.post("/", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def post_transactions( async def post_transactions(
moves: TransactionList, token: str = Depends(oauth2_scheme) moves: TransactionList, token: str = Depends(oauth2_scheme)