Compare commits

..

No commits in common. "main" and "issue/71-refactor-manual-db-close-calls-to-middleware-based" have entirely different histories.

37 changed files with 176 additions and 462 deletions

View File

@ -1,13 +1,11 @@
# Gitea Actions: Docker Build, Push, and Notify # Gitea Actions: Docker Build, Push, and Notify
# #
# CI/CD pipeline for Major Domo Database API: # CI/CD pipeline for Major Domo Database API:
# - Triggered by pushing a CalVer tag (e.g., 2026.4.5) or the 'dev' tag # - Triggered by pushing a CalVer tag (e.g., 2026.4.5)
# - CalVer tags push with version + "production" Docker tags # - Builds Docker image and pushes to Docker Hub with version + latest tags
# - "dev" tag pushes with "dev" Docker tag for the dev environment
# - Sends Discord notifications on success/failure # - Sends Discord notifications on success/failure
# #
# To release: git tag -a 2026.4.5 -m "description" && git push origin 2026.4.5 # To release: git tag -a 2026.4.5 -m "description" && git push origin 2026.4.5
# To test: git tag -f dev && git push -f origin dev
name: Build Docker Image name: Build Docker Image
@ -15,7 +13,6 @@ on:
push: push:
tags: tags:
- '20*' # matches CalVer tags like 2026.4.5 - '20*' # matches CalVer tags like 2026.4.5
- 'dev'
jobs: jobs:
build: build:
@ -35,11 +32,6 @@ jobs:
echo "version=$VERSION" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "sha_short=$SHA_SHORT" >> $GITHUB_OUTPUT echo "sha_short=$SHA_SHORT" >> $GITHUB_OUTPUT
echo "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT echo "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT
if [ "$VERSION" = "dev" ]; then
echo "environment=dev" >> $GITHUB_OUTPUT
else
echo "environment=production" >> $GITHUB_OUTPUT
fi
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: https://github.com/docker/setup-buildx-action@v3 uses: https://github.com/docker/setup-buildx-action@v3
@ -57,9 +49,7 @@ jobs:
push: true push: true
tags: | tags: |
manticorum67/major-domo-database:${{ steps.version.outputs.version }} manticorum67/major-domo-database:${{ steps.version.outputs.version }}
manticorum67/major-domo-database:${{ steps.version.outputs.environment }} manticorum67/major-domo-database:latest
build-args: |
BUILD_VERSION=${{ steps.version.outputs.version }}
cache-from: type=registry,ref=manticorum67/major-domo-database:buildcache cache-from: type=registry,ref=manticorum67/major-domo-database:buildcache
cache-to: type=registry,ref=manticorum67/major-domo-database:buildcache,mode=max cache-to: type=registry,ref=manticorum67/major-domo-database:buildcache,mode=max
@ -71,7 +61,7 @@ jobs:
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "**Image Tags:**" >> $GITHUB_STEP_SUMMARY echo "**Image Tags:**" >> $GITHUB_STEP_SUMMARY
echo "- \`manticorum67/major-domo-database:${{ steps.version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY echo "- \`manticorum67/major-domo-database:${{ steps.version.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`manticorum67/major-domo-database:${{ steps.version.outputs.environment }}\`" >> $GITHUB_STEP_SUMMARY echo "- \`manticorum67/major-domo-database:latest\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "**Build Details:**" >> $GITHUB_STEP_SUMMARY echo "**Build Details:**" >> $GITHUB_STEP_SUMMARY
echo "- Commit: \`${{ steps.version.outputs.sha_short }}\`" >> $GITHUB_STEP_SUMMARY echo "- Commit: \`${{ steps.version.outputs.sha_short }}\`" >> $GITHUB_STEP_SUMMARY

1
.gitignore vendored
View File

@ -55,6 +55,7 @@ Include/
pyvenv.cfg pyvenv.cfg
db_engine.py db_engine.py
main.py main.py
migrations.py
db_engine.py db_engine.py
sba_master.db sba_master.db
db_engine.py db_engine.py

View File

@ -1,21 +1,16 @@
# Use official Python slim image # Use specific version for reproducible builds
FROM python:3.12-slim FROM tiangolo/uvicorn-gunicorn-fastapi:python3.11
# Build-time version arg — passed by CI from the git tag
ARG BUILD_VERSION=dev
LABEL org.opencontainers.image.version=$BUILD_VERSION
# Set Python optimizations # Set Python optimizations
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONDONTWRITEBYTECODE=1
ENV PIP_NO_CACHE_DIR=1 ENV PIP_NO_CACHE_DIR=1
# Bake the CalVer version into the image so it's readable at runtime
ENV APP_VERSION=$BUILD_VERSION
WORKDIR /usr/src/app WORKDIR /usr/src/app
# Install system dependencies (PostgreSQL client libraries) # Install system dependencies (PostgreSQL client libraries)
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
libpq-dev \
curl \ curl \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
@ -25,19 +20,11 @@ RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r requirements.txt pip install --no-cache-dir -r requirements.txt
# Copy application code # Copy application code
COPY ./app /usr/src/app/app COPY ./app /app/app
# Create non-root user and set up directories for volumes # Create directories for volumes
RUN addgroup --system appuser && \ RUN mkdir -p /usr/src/app/storage
adduser --system --ingroup appuser appuser && \
mkdir -p /usr/src/app/storage /usr/src/app/logs && \
chown -R appuser:appuser /usr/src/app
USER appuser
# Health check # Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1 CMD curl -f http://localhost:80/api/v3/current || exit 1
# Start uvicorn
ENV WEB_WORKERS=2
CMD ["sh", "-c", "exec uvicorn app.main:app --host 0.0.0.0 --port 8080 --workers $WEB_WORKERS"]

View File

@ -8,29 +8,38 @@ from typing import Literal, List, Optional
from pandas import DataFrame from pandas import DataFrame
from peewee import * from peewee import *
from peewee import ModelSelect from peewee import ModelSelect
from playhouse.pool import PooledPostgresqlDatabase
from playhouse.shortcuts import model_to_dict from playhouse.shortcuts import model_to_dict
_postgres_password = os.environ.get("POSTGRES_PASSWORD") # Database configuration - supports both SQLite and PostgreSQL
if _postgres_password is None: DATABASE_TYPE = os.environ.get("DATABASE_TYPE", "sqlite")
raise RuntimeError(
"POSTGRES_PASSWORD environment variable is not set. " if DATABASE_TYPE.lower() == "postgresql":
"This variable is required when DATABASE_TYPE=postgresql." from playhouse.pool import PooledPostgresqlDatabase
_postgres_password = os.environ.get("POSTGRES_PASSWORD")
if _postgres_password is None:
raise RuntimeError(
"POSTGRES_PASSWORD environment variable is not set. "
"This variable is required when DATABASE_TYPE=postgresql."
)
db = PooledPostgresqlDatabase(
os.environ.get("POSTGRES_DB", "sba_master"),
user=os.environ.get("POSTGRES_USER", "sba_admin"),
password=_postgres_password,
host=os.environ.get("POSTGRES_HOST", "sba_postgres"),
port=int(os.environ.get("POSTGRES_PORT", "5432")),
max_connections=20,
stale_timeout=300, # 5 minutes
timeout=0,
autoconnect=True,
autorollback=True, # Automatically rollback failed transactions
)
else:
# Default SQLite configuration
db = SqliteDatabase(
"storage/sba_master.db",
pragmas={"journal_mode": "wal", "cache_size": -1 * 64000, "synchronous": 0},
) )
db = PooledPostgresqlDatabase(
os.environ.get("POSTGRES_DB", "sba_master"),
user=os.environ.get("POSTGRES_USER", "sba_admin"),
password=_postgres_password,
host=os.environ.get("POSTGRES_HOST", "sba_postgres"),
port=int(os.environ.get("POSTGRES_PORT", "5432")),
max_connections=20,
stale_timeout=300, # 5 minutes
timeout=5,
autoconnect=False,
autorollback=True, # Automatically rollback failed transactions
)
date = f"{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}" date = f"{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}"
logger = logging.getLogger("discord_app") logger = logging.getLogger("discord_app")
@ -514,12 +523,12 @@ class Team(BaseModel):
all_drops = Transaction.select_season(Current.latest().season).where( all_drops = Transaction.select_season(Current.latest().season).where(
(Transaction.oldteam == self) (Transaction.oldteam == self)
& (Transaction.week == current.week + 1) & (Transaction.week == current.week + 1)
& (Transaction.cancelled == False) & (Transaction.cancelled == 0)
) )
all_adds = Transaction.select_season(Current.latest().season).where( all_adds = Transaction.select_season(Current.latest().season).where(
(Transaction.newteam == self) (Transaction.newteam == self)
& (Transaction.week == current.week + 1) & (Transaction.week == current.week + 1)
& (Transaction.cancelled == False) & (Transaction.cancelled == 0)
) )
for move in all_drops: for move in all_drops:
@ -597,12 +606,12 @@ class Team(BaseModel):
all_drops = Transaction.select_season(Current.latest().season).where( all_drops = Transaction.select_season(Current.latest().season).where(
(Transaction.oldteam == sil_team) (Transaction.oldteam == sil_team)
& (Transaction.week == current.week + 1) & (Transaction.week == current.week + 1)
& (Transaction.cancelled == False) & (Transaction.cancelled == 0)
) )
all_adds = Transaction.select_season(Current.latest().season).where( all_adds = Transaction.select_season(Current.latest().season).where(
(Transaction.newteam == sil_team) (Transaction.newteam == sil_team)
& (Transaction.week == current.week + 1) & (Transaction.week == current.week + 1)
& (Transaction.cancelled == False) & (Transaction.cancelled == 0)
) )
for move in all_drops: for move in all_drops:
@ -680,12 +689,12 @@ class Team(BaseModel):
all_drops = Transaction.select_season(Current.latest().season).where( all_drops = Transaction.select_season(Current.latest().season).where(
(Transaction.oldteam == lil_team) (Transaction.oldteam == lil_team)
& (Transaction.week == current.week + 1) & (Transaction.week == current.week + 1)
& (Transaction.cancelled == False) & (Transaction.cancelled == 0)
) )
all_adds = Transaction.select_season(Current.latest().season).where( all_adds = Transaction.select_season(Current.latest().season).where(
(Transaction.newteam == lil_team) (Transaction.newteam == lil_team)
& (Transaction.week == current.week + 1) & (Transaction.week == current.week + 1)
& (Transaction.cancelled == False) & (Transaction.cancelled == 0)
) )
for move in all_drops: for move in all_drops:
@ -1356,10 +1365,6 @@ class PitchingStat(BaseModel):
def select_season(season): def select_season(season):
return PitchingStat.select().where(PitchingStat.season == season) return PitchingStat.select().where(PitchingStat.season == season)
@staticmethod
def combined_season(season):
return PitchingStat.select().where(PitchingStat.season == season)
@staticmethod @staticmethod
def regular_season(season): def regular_season(season):
if season == 1: if season == 1:
@ -1524,18 +1529,7 @@ class Standings(BaseModel):
with db.atomic(): with db.atomic():
Standings.bulk_create(create_teams) Standings.bulk_create(create_teams)
# Pre-fetch all data needed for in-memory processing (avoids N+1 queries) # Iterate through each individual result
standings_by_team_id = {
s.team_id: s
for s in Standings.select().where(Standings.team << s_teams)
}
teams_by_id = {t.id: t for t in Team.select().where(Team.season == season)}
divisions_by_id = {
d.id: d
for d in Division.select().where(Division.season == season)
}
# Iterate through each individual result, tallying wins/losses in memory
# for game in Result.select_season(season).where(Result.week <= 22): # for game in Result.select_season(season).where(Result.week <= 22):
for game in ( for game in (
StratGame.select() StratGame.select()
@ -1546,121 +1540,8 @@ class Standings(BaseModel):
) )
.order_by(StratGame.week, StratGame.game_num) .order_by(StratGame.week, StratGame.game_num)
): ):
away_stan = standings_by_team_id.get(game.away_team_id) # tally win and loss for each standings object
home_stan = standings_by_team_id.get(game.home_team_id) game.update_standings()
away_team_obj = teams_by_id.get(game.away_team_id)
home_team_obj = teams_by_id.get(game.home_team_id)
if None in (away_stan, home_stan, away_team_obj, home_team_obj):
continue
away_div = divisions_by_id.get(away_team_obj.division_id)
home_div = divisions_by_id.get(home_team_obj.division_id)
if away_div is None or home_div is None:
continue
# Home Team Won
if game.home_score > game.away_score:
home_stan.wins += 1
home_stan.home_wins += 1
away_stan.losses += 1
away_stan.away_losses += 1
if home_stan.streak_wl == 'w':
home_stan.streak_num += 1
else:
home_stan.streak_wl = 'w'
home_stan.streak_num = 1
if away_stan.streak_wl == 'l':
away_stan.streak_num += 1
else:
away_stan.streak_wl = 'l'
away_stan.streak_num = 1
if game.home_score == game.away_score + 1:
home_stan.one_run_wins += 1
away_stan.one_run_losses += 1
if away_div.division_abbrev == 'TC':
home_stan.div1_wins += 1
elif away_div.division_abbrev == 'ETSOS':
home_stan.div2_wins += 1
elif away_div.division_abbrev == 'APL':
home_stan.div3_wins += 1
elif away_div.division_abbrev == 'BBC':
home_stan.div4_wins += 1
if home_div.division_abbrev == 'TC':
away_stan.div1_losses += 1
elif home_div.division_abbrev == 'ETSOS':
away_stan.div2_losses += 1
elif home_div.division_abbrev == 'APL':
away_stan.div3_losses += 1
elif home_div.division_abbrev == 'BBC':
away_stan.div4_losses += 1
home_stan.run_diff += game.home_score - game.away_score
away_stan.run_diff -= game.home_score - game.away_score
# Away Team Won
else:
home_stan.losses += 1
home_stan.home_losses += 1
away_stan.wins += 1
away_stan.away_wins += 1
if home_stan.streak_wl == 'l':
home_stan.streak_num += 1
else:
home_stan.streak_wl = 'l'
home_stan.streak_num = 1
if away_stan.streak_wl == 'w':
away_stan.streak_num += 1
else:
away_stan.streak_wl = 'w'
away_stan.streak_num = 1
if game.away_score == game.home_score + 1:
home_stan.one_run_losses += 1
away_stan.one_run_wins += 1
if away_div.division_abbrev == 'TC':
home_stan.div1_losses += 1
elif away_div.division_abbrev == 'ETSOS':
home_stan.div2_losses += 1
elif away_div.division_abbrev == 'APL':
home_stan.div3_losses += 1
elif away_div.division_abbrev == 'BBC':
home_stan.div4_losses += 1
if home_div.division_abbrev == 'TC':
away_stan.div1_wins += 1
elif home_div.division_abbrev == 'ETSOS':
away_stan.div2_wins += 1
elif home_div.division_abbrev == 'APL':
away_stan.div3_wins += 1
elif home_div.division_abbrev == 'BBC':
away_stan.div4_wins += 1
home_stan.run_diff -= game.away_score - game.home_score
away_stan.run_diff += game.away_score - game.home_score
# Bulk save all modified standings
with db.atomic():
Standings.bulk_update(
list(standings_by_team_id.values()),
fields=[
Standings.wins, Standings.losses,
Standings.home_wins, Standings.home_losses,
Standings.away_wins, Standings.away_losses,
Standings.one_run_wins, Standings.one_run_losses,
Standings.streak_wl, Standings.streak_num,
Standings.run_diff,
Standings.div1_wins, Standings.div1_losses,
Standings.div2_wins, Standings.div2_losses,
Standings.div3_wins, Standings.div3_losses,
Standings.div4_wins, Standings.div4_losses,
]
)
# Set pythag record and iterate through last 8 games for last8 record # Set pythag record and iterate through last 8 games for last8 record
for team in all_teams: for team in all_teams:
@ -1709,7 +1590,9 @@ class BattingCareer(BaseModel):
@staticmethod @staticmethod
def recalculate(): def recalculate():
# Wipe existing data # Wipe existing data
BattingCareer.delete().execute() delete_lines = BattingCareer.select()
for line in delete_lines:
line.delete_instance()
# For each seasonstat, find career or create new and increment # For each seasonstat, find career or create new and increment
for this_season in BattingSeason.select().where( for this_season in BattingSeason.select().where(
@ -1772,7 +1655,9 @@ class PitchingCareer(BaseModel):
@staticmethod @staticmethod
def recalculate(): def recalculate():
# Wipe existing data # Wipe existing data
PitchingCareer.delete().execute() delete_lines = PitchingCareer.select()
for line in delete_lines:
line.delete_instance()
# For each seasonstat, find career or create new and increment # For each seasonstat, find career or create new and increment
for this_season in PitchingSeason.select().where( for this_season in PitchingSeason.select().where(
@ -1824,7 +1709,9 @@ class FieldingCareer(BaseModel):
@staticmethod @staticmethod
def recalculate(): def recalculate():
# Wipe existing data # Wipe existing data
FieldingCareer.delete().execute() delete_lines = FieldingCareer.select()
for line in delete_lines:
line.delete_instance()
# For each seasonstat, find career or create new and increment # For each seasonstat, find career or create new and increment
for this_season in FieldingSeason.select().where( for this_season in FieldingSeason.select().where(
@ -2482,12 +2369,6 @@ class StratGame(BaseModel):
home_stan.save() home_stan.save()
away_stan.save() away_stan.save()
class Meta:
indexes = (
(("season",), False),
(("season", "week", "game_num"), False),
)
class StratPlay(BaseModel): class StratPlay(BaseModel):
game = ForeignKeyField(StratGame) game = ForeignKeyField(StratGame)
@ -2562,14 +2443,6 @@ class StratPlay(BaseModel):
re24_primary = FloatField(null=True) re24_primary = FloatField(null=True)
re24_running = FloatField(null=True) re24_running = FloatField(null=True)
class Meta:
indexes = (
(("game",), False),
(("batter",), False),
(("pitcher",), False),
(("runner",), False),
)
class Decision(BaseModel): class Decision(BaseModel):
game = ForeignKeyField(StratGame) game = ForeignKeyField(StratGame)
@ -2592,7 +2465,8 @@ class Decision(BaseModel):
class CustomCommandCreator(BaseModel): class CustomCommandCreator(BaseModel):
"""Model for custom command creators.""" """Model for custom command creators."""
discord_id = BigIntegerField(unique=True)
discord_id = CharField(max_length=20, unique=True) # Discord snowflake ID as string
username = CharField(max_length=32) username = CharField(max_length=32)
display_name = CharField(max_length=32, null=True) display_name = CharField(max_length=32, null=True)
created_at = DateTimeField() created_at = DateTimeField()

View File

@ -8,8 +8,6 @@ from fastapi import Depends, FastAPI, Request
from fastapi.openapi.docs import get_swagger_ui_html from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.openapi.utils import get_openapi from fastapi.openapi.utils import get_openapi
from .db_engine import db
# from fastapi.openapi.docs import get_swagger_ui_html # from fastapi.openapi.docs import get_swagger_ui_html
# from fastapi.openapi.utils import get_openapi # from fastapi.openapi.utils import get_openapi
@ -70,17 +68,6 @@ app = FastAPI(
) )
@app.middleware("http")
async def db_connection_middleware(request: Request, call_next):
db.connect(reuse_if_open=True)
try:
response = await call_next(request)
finally:
if not db.is_closed():
db.close()
return response
logger.info(f"Starting up now...") logger.info(f"Starting up now...")
@ -139,11 +126,6 @@ app.include_router(views.router)
logger.info(f"Loaded all routers.") logger.info(f"Loaded all routers.")
@app.get("/health")
async def health():
return {"status": "ok", "version": os.environ.get("APP_VERSION", "dev")}
@app.get("/api/docs", include_in_schema=False) @app.get("/api/docs", include_in_schema=False)
async def get_docs(req: Request): async def get_docs(req: Request):
logger.debug(req.scope) logger.debug(req.scope)

View File

@ -106,7 +106,7 @@ async def patch_award(
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_player - Bad 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)
@ -141,7 +141,7 @@ async def patch_award(
@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("patch_player - Bad 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 = []
@ -172,7 +172,7 @@ async def post_award(award_list: AwardList, token: str = Depends(oauth2_scheme))
status_code=404, detail=f"Team ID {x.team_id} not found" status_code=404, detail=f"Team ID {x.team_id} not found"
) )
new_awards.append(x.model_dump()) new_awards.append(x.dict())
with db.atomic(): with db.atomic():
for batch in chunked(new_awards, 15): for batch in chunked(new_awards, 15):
@ -185,7 +185,7 @@ async def post_award(award_list: AwardList, token: str = Depends(oauth2_scheme))
@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("patch_player - Bad 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)

View File

@ -360,15 +360,13 @@ async def patch_batstats(
stat_id: int, new_stats: BatStatModel, token: str = Depends(oauth2_scheme) stat_id: int, new_stats: BatStatModel, token: str = Depends(oauth2_scheme)
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_batstats - Bad 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.model_dump()).where( BattingStat.update(**new_stats.dict()).where(BattingStat.id == stat_id).execute()
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))
return r_stat return r_stat
@ -377,7 +375,7 @@ async def patch_batstats(
@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("post_batstats - Bad 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 = []
@ -405,7 +403,7 @@ async def post_batstats(s_list: BatStatList, token: str = Depends(oauth2_scheme)
status_code=404, detail=f"Player ID {x.player_id} not found" status_code=404, detail=f"Player ID {x.player_id} not found"
) )
all_stats.append(BattingStat(**x.model_dump())) all_stats.append(BattingStat(**x.dict()))
with db.atomic(): with db.atomic():
for batch in chunked(all_stats, 15): for batch in chunked(all_stats, 15):

View File

@ -64,7 +64,7 @@ async def patch_current(
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_current - Bad 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:
@ -110,10 +110,10 @@ async def patch_current(
@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("patch_current - Bad 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.model_dump()) this_current = Current(**new_current.dict())
if this_current.save(): if this_current.save():
r_curr = model_to_dict(this_current) r_curr = model_to_dict(this_current)
@ -129,7 +129,7 @@ async def post_current(new_current: CurrentModel, token: str = Depends(oauth2_sc
@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("patch_current - Bad 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:

View File

@ -175,7 +175,7 @@ def delete_custom_command(command_id: int):
def get_creator_by_discord_id(discord_id: int): def get_creator_by_discord_id(discord_id: int):
"""Get a creator by Discord ID""" """Get a creator by Discord ID"""
creator = CustomCommandCreator.get_or_none( creator = CustomCommandCreator.get_or_none(
CustomCommandCreator.discord_id == discord_id CustomCommandCreator.discord_id == str(discord_id)
) )
if creator: if creator:
return model_to_dict(creator) return model_to_dict(creator)
@ -375,7 +375,7 @@ async def create_custom_command_endpoint(
): ):
"""Create a new custom command""" """Create a new custom command"""
if not valid_token(token): if not valid_token(token):
logger.warning("create_custom_command - Bad Token") logger.warning(f"create_custom_command - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
@ -436,7 +436,7 @@ async def update_custom_command_endpoint(
): ):
"""Update an existing custom command""" """Update an existing custom command"""
if not valid_token(token): if not valid_token(token):
logger.warning("update_custom_command - Bad Token") logger.warning(f"update_custom_command - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
@ -502,7 +502,7 @@ async def patch_custom_command(
): ):
"""Partially update a custom command""" """Partially update a custom command"""
if not valid_token(token): if not valid_token(token):
logger.warning("patch_custom_command - Bad Token") logger.warning(f"patch_custom_command - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
@ -578,7 +578,7 @@ async def delete_custom_command_endpoint(
): ):
"""Delete a custom command""" """Delete a custom command"""
if not valid_token(token): if not valid_token(token):
logger.warning("delete_custom_command - Bad Token") logger.warning(f"delete_custom_command - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
@ -682,7 +682,7 @@ async def create_creator_endpoint(
): ):
"""Create a new command creator""" """Create a new command creator"""
if not valid_token(token): if not valid_token(token):
logger.warning("create_creator - Bad Token") logger.warning(f"create_creator - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
@ -914,7 +914,7 @@ async def execute_custom_command(
): ):
"""Execute a custom command and update usage statistics""" """Execute a custom command and update usage statistics"""
if not valid_token(token): if not valid_token(token):
logger.warning("execute_custom_command - Bad Token") logger.warning(f"execute_custom_command - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
try: try:

View File

@ -162,7 +162,7 @@ async def patch_decision(
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_decision - Bad 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)
@ -203,7 +203,7 @@ async def patch_decision(
@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("post_decisions - Bad 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 = []
@ -217,7 +217,7 @@ async def post_decisions(dec_list: DecisionList, token: str = Depends(oauth2_sch
status_code=404, detail=f"Player ID {x.pitcher_id} not found" status_code=404, detail=f"Player ID {x.pitcher_id} not found"
) )
new_dec.append(x.model_dump()) new_dec.append(x.dict())
with db.atomic(): with db.atomic():
for batch in chunked(new_dec, 10): for batch in chunked(new_dec, 10):
@ -230,7 +230,7 @@ async def post_decisions(dec_list: DecisionList, token: str = Depends(oauth2_sch
@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("delete_decision - Bad 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)
@ -253,7 +253,7 @@ async def delete_decision(decision_id: int, token: str = Depends(oauth2_scheme))
@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("delete_decisions_game - Bad 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)

View File

@ -82,7 +82,7 @@ async def patch_division(
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_division - Bad 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)
@ -115,10 +115,10 @@ async def post_division(
new_division: DivisionModel, token: str = Depends(oauth2_scheme) new_division: DivisionModel, token: str = Depends(oauth2_scheme)
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("post_division - Bad 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.model_dump()) this_division = Division(**new_division.dict())
if this_division.save() == 1: if this_division.save() == 1:
r_division = model_to_dict(this_division) r_division = model_to_dict(this_division)
@ -131,7 +131,7 @@ async def post_division(
@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("delete_division - Bad 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)

View File

@ -44,7 +44,7 @@ async def patch_draftdata(
pick_deadline: Optional[datetime.datetime] = None, result_channel: Optional[int] = None, pick_deadline: Optional[datetime.datetime] = None, result_channel: Optional[int] = None,
ping_channel: Optional[int] = None, pick_minutes: Optional[int] = None, token: str = Depends(oauth2_scheme)): ping_channel: Optional[int] = None, pick_minutes: Optional[int] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning('patch_draftdata - Bad Token') logger.warning(f'patch_draftdata - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized') raise HTTPException(status_code=401, detail='Unauthorized')
draft_data = DraftData.get_or_none(DraftData.id == data_id) draft_data = DraftData.get_or_none(DraftData.id == data_id)

View File

@ -40,7 +40,7 @@ async def get_draftlist(
offset: int = Query(default=0, ge=0), offset: int = Query(default=0, ge=0),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("get_draftlist - Bad 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()
@ -62,7 +62,7 @@ async def get_draftlist(
@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("post_draftlist - Bad 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)
@ -84,7 +84,7 @@ async def post_draftlist(
draft_list: DraftListList, token: str = Depends(oauth2_scheme) draft_list: DraftListList, token: str = Depends(oauth2_scheme)
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("post_draftlist - Bad 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 = []
@ -98,7 +98,7 @@ async def post_draftlist(
DraftList.delete().where(DraftList.team == this_team).execute() DraftList.delete().where(DraftList.team == this_team).execute()
for x in draft_list.draft_list: for x in draft_list.draft_list:
new_list.append(x.model_dump()) new_list.append(x.dict())
with db.atomic(): with db.atomic():
for batch in chunked(new_list, 15): for batch in chunked(new_list, 15):
@ -111,7 +111,7 @@ async def post_draftlist(
@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("delete_draftlist - Bad 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()

View File

@ -144,13 +144,13 @@ async def patch_pick(
pick_id: int, new_pick: DraftPickModel, token: str = Depends(oauth2_scheme) pick_id: int, new_pick: DraftPickModel, token: str = Depends(oauth2_scheme)
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_pick - Bad 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.model_dump()).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))
return r_pick return r_pick
@ -159,7 +159,7 @@ async def patch_pick(
@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("post_picks - Bad 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 = []
@ -173,7 +173,7 @@ async def post_picks(p_list: DraftPickList, token: str = Depends(oauth2_scheme))
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.model_dump()) new_picks.append(pick.dict())
with db.atomic(): with db.atomic():
for batch in chunked(new_picks, 15): for batch in chunked(new_picks, 15):
@ -186,7 +186,7 @@ async def post_picks(p_list: DraftPickList, token: str = Depends(oauth2_scheme))
@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("delete_pick - Bad 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)

View File

@ -276,4 +276,5 @@ async def get_totalstats(
} }
) )
return_stats["count"] = len(return_stats["stats"])
return return_stats return return_stats

View File

@ -147,7 +147,7 @@ async def create_help_command_endpoint(
): ):
"""Create a new help command""" """Create a new help command"""
if not valid_token(token): if not valid_token(token):
logger.warning("create_help_command - Bad 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:
@ -194,7 +194,7 @@ async def update_help_command_endpoint(
): ):
"""Update an existing help command""" """Update an existing help command"""
if not valid_token(token): if not valid_token(token):
logger.warning("update_help_command - Bad 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:
@ -243,7 +243,7 @@ async def restore_help_command_endpoint(
): ):
"""Restore a soft-deleted help command""" """Restore a soft-deleted help command"""
if not valid_token(token): if not valid_token(token):
logger.warning("restore_help_command - Bad 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:
@ -280,7 +280,7 @@ async def delete_help_command_endpoint(
): ):
"""Soft delete a help command""" """Soft delete a help command"""
if not valid_token(token): if not valid_token(token):
logger.warning("delete_help_command - Bad 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:
@ -397,7 +397,7 @@ async def get_help_command_by_name_endpoint(
async def increment_view_count(command_name: str, token: str = Depends(oauth2_scheme)): async def increment_view_count(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("increment_view_count - Bad 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:

View File

@ -86,7 +86,7 @@ async def patch_injury(
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_injury - Bad 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)
@ -109,10 +109,10 @@ async def patch_injury(
@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("post_injury - Bad 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.model_dump()) this_injury = Injury(**new_injury.dict())
if this_injury.save(): if this_injury.save():
r_injury = model_to_dict(this_injury) r_injury = model_to_dict(this_injury)
@ -125,7 +125,7 @@ async def post_injury(new_injury: InjuryModel, token: str = Depends(oauth2_schem
@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("delete_injury - Bad 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)

View File

@ -68,7 +68,7 @@ async def patch_keeper(
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_keeper - Bad 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)
@ -95,12 +95,12 @@ async def patch_keeper(
@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("post_keepers - Bad 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:
new_keepers.append(keeper.model_dump()) new_keepers.append(keeper.dict())
with db.atomic(): with db.atomic():
for batch in chunked(new_keepers, 14): for batch in chunked(new_keepers, 14):
@ -113,7 +113,7 @@ async def post_keepers(k_list: KeeperList, token: str = Depends(oauth2_scheme)):
@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("delete_keeper - Bad 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)

View File

@ -109,7 +109,7 @@ async def patch_manager(
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_manager - Bad 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)
@ -140,10 +140,10 @@ async def patch_manager(
@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("post_manager - Bad 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.model_dump()) this_manager = Manager(**new_manager.dict())
if this_manager.save(): if this_manager.save():
r_manager = model_to_dict(this_manager) r_manager = model_to_dict(this_manager)
@ -158,7 +158,7 @@ async def post_manager(new_manager: ManagerModel, token: str = Depends(oauth2_sc
@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("delete_manager - Bad 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)

View File

@ -311,15 +311,13 @@ async def patch_pitstats(
stat_id: int, new_stats: PitStatModel, token: str = Depends(oauth2_scheme) stat_id: int, new_stats: PitStatModel, token: str = Depends(oauth2_scheme)
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_pitstats - Bad 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.model_dump()).where( PitchingStat.update(**new_stats.dict()).where(PitchingStat.id == stat_id).execute()
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))
return r_stat return r_stat
@ -328,7 +326,7 @@ async def patch_pitstats(
@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("post_pitstats - Bad 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 = []
@ -345,7 +343,7 @@ async def post_pitstats(s_list: PitStatList, token: str = Depends(oauth2_scheme)
status_code=404, detail=f"Player ID {x.player_id} not found" status_code=404, detail=f"Player ID {x.player_id} not found"
) )
all_stats.append(PitchingStat(**x.model_dump())) all_stats.append(PitchingStat(**x.dict()))
with db.atomic(): with db.atomic():
for batch in chunked(all_stats, 15): for batch in chunked(all_stats, 15):

View File

@ -4,7 +4,7 @@ Thin HTTP layer using PlayerService for business logic.
""" """
from fastapi import APIRouter, Query, Response, Depends from fastapi import APIRouter, Query, Response, Depends
from typing import Literal, Optional, List from typing import Optional, List
from ..dependencies import ( from ..dependencies import (
oauth2_scheme, oauth2_scheme,
@ -27,10 +27,8 @@ async def get_players(
pos: list = Query(default=None), pos: list = Query(default=None),
strat_code: list = Query(default=None), strat_code: list = Query(default=None),
is_injured: Optional[bool] = None, is_injured: Optional[bool] = None,
sort: Optional[Literal["cost-asc", "cost-desc", "name-asc", "name-desc"]] = None, sort: Optional[str] = None,
limit: Optional[int] = Query( limit: Optional[int] = Query(default=None, ge=1),
default=None, ge=1, description="Maximum number of results to return"
),
offset: Optional[int] = Query( offset: Optional[int] = Query(
default=None, ge=0, description="Number of results to skip for pagination" default=None, ge=0, description="Number of results to skip for pagination"
), ),

View File

@ -114,7 +114,7 @@ async def patch_result(
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_player - Bad 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)
@ -158,7 +158,7 @@ async def patch_result(
@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("patch_player - Bad 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 = []
@ -183,7 +183,7 @@ async def post_results(result_list: ResultList, token: str = Depends(oauth2_sche
status_code=404, detail=f"Team ID {x.hometeam_id} not found" status_code=404, detail=f"Team ID {x.hometeam_id} not found"
) )
new_results.append(x.model_dump()) new_results.append(x.dict())
with db.atomic(): with db.atomic():
for batch in chunked(new_results, 15): for batch in chunked(new_results, 15):
@ -196,7 +196,7 @@ async def post_results(result_list: ResultList, token: str = Depends(oauth2_sche
@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("delete_result - Bad 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)

View File

@ -140,7 +140,7 @@ async def patch_player(
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logging.warning("Bad Token") logging.warning(f"Bad Token: {token}")
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.",
@ -179,7 +179,7 @@ async def patch_player(
@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("Bad Token") logging.warning(f"Bad Token: {token}")
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.",
@ -216,7 +216,7 @@ async def post_players(players: PlayerList, token: str = Depends(oauth2_scheme))
@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("Bad Token") logging.warning(f"Bad Token: {token}")
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.",
@ -236,7 +236,7 @@ async def post_one_player(player: SbaPlayerModel, token: str = Depends(oauth2_sc
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.model_dump()) new_player = SbaPlayer(**player.dict())
saved = new_player.save() saved = new_player.save()
if saved == 1: if saved == 1:
return_val = model_to_dict(new_player) return_val = model_to_dict(new_player)
@ -252,7 +252,7 @@ async def post_one_player(player: SbaPlayerModel, token: str = Depends(oauth2_sc
@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("Bad Token") logging.warning(f"Bad Token: {token}")
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.",

View File

@ -106,7 +106,7 @@ async def patch_schedule(
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_schedule - Bad 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)
@ -143,7 +143,7 @@ async def patch_schedule(
@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("post_schedules - Bad 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 = []
@ -168,7 +168,7 @@ async def post_schedules(sched_list: ScheduleList, token: str = Depends(oauth2_s
status_code=404, detail=f"Team ID {x.hometeam_id} not found" status_code=404, detail=f"Team ID {x.hometeam_id} not found"
) )
new_sched.append(x.model_dump()) new_sched.append(x.dict())
with db.atomic(): with db.atomic():
for batch in chunked(new_sched, 15): for batch in chunked(new_sched, 15):
@ -181,7 +181,7 @@ async def post_schedules(sched_list: ScheduleList, token: str = Depends(oauth2_s
@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("delete_schedule - Bad 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)

View File

@ -87,13 +87,13 @@ async def get_team_standings(team_id: int):
@router.patch("/{stan_id}", include_in_schema=PRIVATE_IN_SCHEMA) @router.patch("/{stan_id}", include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors @handle_db_errors
async def patch_standings( async def patch_standings(
stan_id: int, stan_id,
wins: Optional[int] = None, wins: Optional[int] = None,
losses: Optional[int] = None, losses: Optional[int] = None,
token: str = Depends(oauth2_scheme), token: str = Depends(oauth2_scheme),
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_standings - Bad Token") logger.warning(f"patch_standings - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
try: try:
@ -115,7 +115,7 @@ async def patch_standings(
@handle_db_errors @handle_db_errors
async def post_standings(season: int, token: str = Depends(oauth2_scheme)): async def post_standings(season: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("post_standings - Bad Token") logger.warning(f"post_standings - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
new_teams = [] new_teams = []
@ -134,7 +134,7 @@ async def post_standings(season: int, token: str = Depends(oauth2_scheme)):
@handle_db_errors @handle_db_errors
async def recalculate_standings(season: int, token: str = Depends(oauth2_scheme)): async def recalculate_standings(season: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("recalculate_standings - Bad Token") logger.warning(f"recalculate_standings - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
code = Standings.recalculate(season) code = Standings.recalculate(season)

View File

@ -156,7 +156,7 @@ async def patch_game(
scorecard_url: Optional[str] = None, scorecard_url: Optional[str] = None,
) -> Any: ) -> Any:
if not valid_token(token): if not valid_token(token):
logger.warning("patch_game - Bad Token") logger.warning(f"patch_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)
@ -236,7 +236,7 @@ async def patch_game(
@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):
logger.warning("post_games - Bad Token") logger.warning(f"post_games - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
new_games = [] new_games = []
@ -250,7 +250,7 @@ async def post_games(game_list: GameList, token: str = Depends(oauth2_scheme)) -
status_code=404, detail=f"Team ID {x.home_team_id} not found" status_code=404, detail=f"Team ID {x.home_team_id} not found"
) )
new_games.append(x.model_dump()) new_games.append(x.dict())
with db.atomic(): with db.atomic():
for batch in chunked(new_games, 16): for batch in chunked(new_games, 16):
@ -263,7 +263,7 @@ async def post_games(game_list: GameList, token: str = Depends(oauth2_scheme)) -
@handle_db_errors @handle_db_errors
async def wipe_game(game_id: int, token: str = Depends(oauth2_scheme)) -> Any: async def wipe_game(game_id: int, token: str = Depends(oauth2_scheme)) -> Any:
if not valid_token(token): if not valid_token(token):
logger.warning("wipe_game - Bad Token") logger.warning(f"wipe_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)
@ -287,7 +287,7 @@ async def wipe_game(game_id: int, token: str = Depends(oauth2_scheme)) -> Any:
@handle_db_errors @handle_db_errors
async def delete_game(game_id: int, token: str = Depends(oauth2_scheme)) -> Any: async def delete_game(game_id: int, token: str = Depends(oauth2_scheme)) -> Any:
if not valid_token(token): if not valid_token(token):
logger.warning("delete_game - Bad Token") logger.warning(f"delete_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)

View File

@ -17,6 +17,7 @@ from ...dependencies import (
add_cache_headers, add_cache_headers,
cache_result, cache_result,
handle_db_errors, handle_db_errors,
MAX_LIMIT,
DEFAULT_LIMIT, DEFAULT_LIMIT,
) )
from .common import build_season_games from .common import build_season_games
@ -57,7 +58,7 @@ async def get_batting_totals(
risp: Optional[bool] = None, risp: Optional[bool] = None,
inning: list = Query(default=None), inning: list = Query(default=None),
sort: Optional[str] = None, sort: Optional[str] = None,
limit: int = Query(default=DEFAULT_LIMIT, ge=1, le=1000), limit: int = Query(default=DEFAULT_LIMIT, ge=1, le=MAX_LIMIT),
short_output: Optional[bool] = False, short_output: Optional[bool] = False,
page_num: Optional[int] = 1, page_num: Optional[int] = 1,
week_start: Optional[int] = None, week_start: Optional[int] = None,

View File

@ -31,13 +31,13 @@ async def patch_play(
play_id: int, new_play: PlayModel, token: str = Depends(oauth2_scheme) play_id: int, new_play: PlayModel, token: str = Depends(oauth2_scheme)
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_play - Bad Token") logger.warning(f"patch_play - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
if StratPlay.get_or_none(StratPlay.id == play_id) is None: if StratPlay.get_or_none(StratPlay.id == play_id) is None:
raise HTTPException(status_code=404, detail=f"Play ID {play_id} not found") raise HTTPException(status_code=404, detail=f"Play ID {play_id} not found")
StratPlay.update(**new_play.model_dump()).where(StratPlay.id == play_id).execute() StratPlay.update(**new_play.dict()).where(StratPlay.id == play_id).execute()
r_play = model_to_dict(StratPlay.get_by_id(play_id)) r_play = model_to_dict(StratPlay.get_by_id(play_id))
return r_play return r_play
@ -46,7 +46,7 @@ async def patch_play(
@handle_db_errors @handle_db_errors
async def post_plays(p_list: PlayList, token: str = Depends(oauth2_scheme)): async def post_plays(p_list: PlayList, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("post_plays - Bad Token") logger.warning(f"post_plays - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
new_plays = [] new_plays = []
@ -84,7 +84,7 @@ async def post_plays(p_list: PlayList, token: str = Depends(oauth2_scheme)):
if this_play.pa == 0: if this_play.pa == 0:
this_play.batter_final = None this_play.batter_final = None
new_plays.append(this_play.model_dump()) new_plays.append(this_play.dict())
with db.atomic(): with db.atomic():
for batch in chunked(new_plays, 20): for batch in chunked(new_plays, 20):
@ -97,7 +97,7 @@ async def post_plays(p_list: PlayList, token: str = Depends(oauth2_scheme)):
@handle_db_errors @handle_db_errors
async def delete_play(play_id: int, token: str = Depends(oauth2_scheme)): async def delete_play(play_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("delete_play - Bad Token") logger.warning(f"delete_play - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
this_play = StratPlay.get_or_none(StratPlay.id == play_id) this_play = StratPlay.get_or_none(StratPlay.id == play_id)
@ -118,7 +118,7 @@ async def delete_play(play_id: int, token: str = Depends(oauth2_scheme)):
@handle_db_errors @handle_db_errors
async def delete_plays_game(game_id: int, token: str = Depends(oauth2_scheme)): async def delete_plays_game(game_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("delete_plays_game - Bad Token") logger.warning(f"delete_plays_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)
@ -139,7 +139,7 @@ async def delete_plays_game(game_id: int, token: str = Depends(oauth2_scheme)):
@handle_db_errors @handle_db_errors
async def post_erun_check(token: str = Depends(oauth2_scheme)): async def post_erun_check(token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("post_erun_check - Bad Token") logger.warning(f"post_erun_check - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
all_plays = StratPlay.update(run=1).where( all_plays = StratPlay.update(run=1).where(

View File

@ -17,6 +17,7 @@ from ...dependencies import (
handle_db_errors, handle_db_errors,
add_cache_headers, add_cache_headers,
cache_result, cache_result,
MAX_LIMIT,
DEFAULT_LIMIT, DEFAULT_LIMIT,
) )
from .common import build_season_games from .common import build_season_games
@ -56,7 +57,7 @@ async def get_fielding_totals(
team_id: list = Query(default=None), team_id: list = Query(default=None),
manager_id: list = Query(default=None), manager_id: list = Query(default=None),
sort: Optional[str] = None, sort: Optional[str] = None,
limit: int = Query(default=DEFAULT_LIMIT, ge=1, le=1000), limit: int = Query(default=DEFAULT_LIMIT, ge=1, le=MAX_LIMIT),
short_output: Optional[bool] = False, short_output: Optional[bool] = False,
page_num: Optional[int] = 1, page_num: Optional[int] = 1,
): ):

View File

@ -20,6 +20,7 @@ from ...dependencies import (
handle_db_errors, handle_db_errors,
add_cache_headers, add_cache_headers,
cache_result, cache_result,
MAX_LIMIT,
DEFAULT_LIMIT, DEFAULT_LIMIT,
) )
from .common import build_season_games from .common import build_season_games
@ -56,7 +57,7 @@ async def get_pitching_totals(
risp: Optional[bool] = None, risp: Optional[bool] = None,
inning: list = Query(default=None), inning: list = Query(default=None),
sort: Optional[str] = None, sort: Optional[str] = None,
limit: int = Query(default=DEFAULT_LIMIT, ge=1, le=1000), limit: int = Query(default=DEFAULT_LIMIT, ge=1, le=MAX_LIMIT),
short_output: Optional[bool] = False, short_output: Optional[bool] = False,
csv: Optional[bool] = False, csv: Optional[bool] = False,
page_num: Optional[int] = 1, page_num: Optional[int] = 1,

View File

@ -78,14 +78,14 @@ async def get_transactions(
transactions = transactions.where(Transaction.player << these_players) transactions = transactions.where(Transaction.player << these_players)
if cancelled: if cancelled:
transactions = transactions.where(Transaction.cancelled == True) transactions = transactions.where(Transaction.cancelled == 1)
else: else:
transactions = transactions.where(Transaction.cancelled == False) transactions = transactions.where(Transaction.cancelled == 0)
if frozen: if frozen:
transactions = transactions.where(Transaction.frozen == True) transactions = transactions.where(Transaction.frozen == 1)
else: else:
transactions = transactions.where(Transaction.frozen == False) transactions = transactions.where(Transaction.frozen == 0)
transactions = transactions.order_by(-Transaction.week, Transaction.moveid) transactions = transactions.order_by(-Transaction.week, Transaction.moveid)
@ -113,7 +113,7 @@ async def patch_transactions(
cancelled: Optional[bool] = None, cancelled: Optional[bool] = None,
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("patch_transactions - Bad Token") logger.warning(f"patch_transactions - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
these_moves = Transaction.select().where(Transaction.moveid == move_id) these_moves = Transaction.select().where(Transaction.moveid == move_id)
@ -138,7 +138,7 @@ async def post_transactions(
moves: TransactionList, token: str = Depends(oauth2_scheme) moves: TransactionList, token: str = Depends(oauth2_scheme)
): ):
if not valid_token(token): if not valid_token(token):
logger.warning("post_transactions - Bad Token") logger.warning(f"post_transactions - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
all_moves = [] all_moves = []
@ -172,7 +172,7 @@ async def post_transactions(
status_code=404, detail=f"Player ID {x.player_id} not found" status_code=404, detail=f"Player ID {x.player_id} not found"
) )
all_moves.append(x.model_dump()) all_moves.append(x.dict())
with db.atomic(): with db.atomic():
for batch in chunked(all_moves, 15): for batch in chunked(all_moves, 15):
@ -185,7 +185,7 @@ async def post_transactions(
@handle_db_errors @handle_db_errors
async def delete_transactions(move_id, token: str = Depends(oauth2_scheme)): async def delete_transactions(move_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token): if not valid_token(token):
logger.warning("delete_transactions - Bad Token") logger.warning(f"delete_transactions - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
delete_query = Transaction.delete().where(Transaction.moveid == move_id) delete_query = Transaction.delete().where(Transaction.moveid == move_id)

View File

@ -124,7 +124,7 @@ async def refresh_season_batting_stats(
Useful for full season updates. Useful for full season updates.
""" """
if not valid_token(token): if not valid_token(token):
logger.warning("refresh_season_batting_stats - Bad Token") logger.warning(f"refresh_season_batting_stats - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
logger.info(f"Refreshing all batting stats for season {season}") logger.info(f"Refreshing all batting stats for season {season}")
@ -270,7 +270,7 @@ async def refresh_season_pitching_stats(
Private endpoint - not included in public API documentation. Private endpoint - not included in public API documentation.
""" """
if not valid_token(token): if not valid_token(token):
logger.warning("refresh_season_batting_stats - Bad Token") logger.warning(f"refresh_season_batting_stats - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
logger.info(f"Refreshing season {season} pitching stats") logger.info(f"Refreshing season {season} pitching stats")
@ -318,7 +318,7 @@ async def get_admin_cache_stats(token: str = Depends(oauth2_scheme)) -> dict:
Private endpoint - requires authentication. Private endpoint - requires authentication.
""" """
if not valid_token(token): if not valid_token(token):
logger.warning("get_admin_cache_stats - Bad Token") logger.warning(f"get_admin_cache_stats - Bad Token: {token}")
raise HTTPException(status_code=401, detail="Unauthorized") raise HTTPException(status_code=401, detail="Unauthorized")
logger.info("Getting cache statistics") logger.info("Getting cache statistics")

View File

@ -14,7 +14,7 @@ services:
- ./storage:/usr/src/app/storage - ./storage:/usr/src/app/storage
- ./logs:/usr/src/app/logs - ./logs:/usr/src/app/logs
ports: ports:
- 801:8080 - 801:80
networks: networks:
- default - default
# - nginx-proxy-manager_npm_network # Commented for local testing # - nginx-proxy-manager_npm_network # Commented for local testing
@ -23,6 +23,9 @@ services:
- LOG_LEVEL=${LOG_LEVEL} - LOG_LEVEL=${LOG_LEVEL}
- API_TOKEN=${API_TOKEN} - API_TOKEN=${API_TOKEN}
- TZ=${TZ} - TZ=${TZ}
- WORKERS_PER_CORE=1.5
- TIMEOUT=120
- GRACEFUL_TIMEOUT=120
- DATABASE_TYPE=postgresql - DATABASE_TYPE=postgresql
- POSTGRES_HOST=sba_postgres - POSTGRES_HOST=sba_postgres
- POSTGRES_DB=${SBA_DATABASE} - POSTGRES_DB=${SBA_DATABASE}

View File

@ -1,88 +0,0 @@
#!/usr/bin/env python3
"""Apply pending SQL migrations and record them in schema_versions.
Usage:
python migrations.py
Connects to PostgreSQL using the same environment variables as the API:
POSTGRES_DB (default: sba_master)
POSTGRES_USER (default: sba_admin)
POSTGRES_PASSWORD (required)
POSTGRES_HOST (default: sba_postgres)
POSTGRES_PORT (default: 5432)
On first run against an existing database, all migrations will be applied.
All migration files use IF NOT EXISTS guards so re-applying is safe.
"""
import os
import sys
from pathlib import Path
import psycopg2
MIGRATIONS_DIR = Path(__file__).parent / "migrations"
_CREATE_SCHEMA_VERSIONS = """
CREATE TABLE IF NOT EXISTS schema_versions (
filename VARCHAR(255) PRIMARY KEY,
applied_at TIMESTAMP NOT NULL DEFAULT NOW()
);
"""
def _get_connection():
password = os.environ.get("POSTGRES_PASSWORD")
if password is None:
raise RuntimeError("POSTGRES_PASSWORD environment variable is not set")
return psycopg2.connect(
dbname=os.environ.get("POSTGRES_DB", "sba_master"),
user=os.environ.get("POSTGRES_USER", "sba_admin"),
password=password,
host=os.environ.get("POSTGRES_HOST", "sba_postgres"),
port=int(os.environ.get("POSTGRES_PORT", "5432")),
)
def main():
conn = _get_connection()
try:
with conn:
with conn.cursor() as cur:
cur.execute(_CREATE_SCHEMA_VERSIONS)
with conn.cursor() as cur:
cur.execute("SELECT filename FROM schema_versions")
applied = {row[0] for row in cur.fetchall()}
migration_files = sorted(MIGRATIONS_DIR.glob("*.sql"))
pending = [f for f in migration_files if f.name not in applied]
if not pending:
print("No pending migrations.")
return
for migration_file in pending:
print(f"Applying {migration_file.name} ...", end=" ", flush=True)
sql = migration_file.read_text()
with conn:
with conn.cursor() as cur:
cur.execute(sql)
cur.execute(
"INSERT INTO schema_versions (filename) VALUES (%s)",
(migration_file.name,),
)
print("done")
print(f"\nApplied {len(pending)} migration(s).")
finally:
conn.close()
if __name__ == "__main__":
try:
main()
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)

View File

@ -1,9 +0,0 @@
-- Migration: Add schema_versions table for migration tracking
-- Date: 2026-03-27
-- Description: Creates a table to record which SQL migrations have been applied,
-- preventing double-application and missed migrations across environments.
CREATE TABLE IF NOT EXISTS schema_versions (
filename VARCHAR(255) PRIMARY KEY,
applied_at TIMESTAMP NOT NULL DEFAULT NOW()
);

View File

@ -1,24 +0,0 @@
-- Migration: Add missing indexes on foreign key columns in stratplay and stratgame
-- Created: 2026-03-27
--
-- PostgreSQL does not auto-index foreign key columns. These tables are the
-- highest-volume tables in the schema and are filtered/joined on these columns
-- in batting, pitching, and running stats aggregation and standings recalculation.
-- stratplay: FK join column
CREATE INDEX IF NOT EXISTS idx_stratplay_game_id ON stratplay(game_id);
-- stratplay: filtered in batting stats aggregation
CREATE INDEX IF NOT EXISTS idx_stratplay_batter_id ON stratplay(batter_id);
-- stratplay: filtered in pitching stats aggregation
CREATE INDEX IF NOT EXISTS idx_stratplay_pitcher_id ON stratplay(pitcher_id);
-- stratplay: filtered in running stats
CREATE INDEX IF NOT EXISTS idx_stratplay_runner_id ON stratplay(runner_id);
-- stratgame: heavily filtered by season
CREATE INDEX IF NOT EXISTS idx_stratgame_season ON stratgame(season);
-- stratgame: standings recalculation query ordering
CREATE INDEX IF NOT EXISTS idx_stratgame_season_week_game_num ON stratgame(season, week, game_num);

View File

@ -569,7 +569,7 @@ class TestGroupBySbaPlayer:
# Get per-season rows # Get per-season rows
r_seasons = requests.get( r_seasons = requests.get(
f"{api}/api/v3/plays/batting", f"{api}/api/v3/plays/batting",
params={"group_by": "player", "sbaplayer_id": 1, "limit": 500}, params={"group_by": "player", "sbaplayer_id": 1, "limit": 999},
timeout=15, timeout=15,
) )
assert r_seasons.status_code == 200 assert r_seasons.status_code == 200