Merge pull request 'Release: Scouting API, roster refactor, and bug fixes' (#61) from next-release into main
Some checks failed
Build Docker Image / build (push) Failing after 7m5s

Reviewed-on: #61
This commit is contained in:
cal 2026-03-09 14:12:41 +00:00
commit 7b8b7b9c01
40 changed files with 924 additions and 1123 deletions

4
.env
View File

@ -59,9 +59,9 @@ API_TOKEN=Tp3aO3jhYve5NJF1IqOmJTmk
# PRIVATE_IN_SCHEMA=true
# Testing mode
# Set to 'False' to use development database URL (pddev.manticorum.com)
# Set to 'True' to use development database URL (pddev.manticorum.com)
# Leave unset or set to any other value for production
TESTING=TRUE
TESTING=True
# =============================================================================
# EXAMPLE CONFIGURATIONS

View File

@ -3,6 +3,7 @@
# CI/CD pipeline for Paper Dynasty Database API:
# - Builds Docker images on every push/PR
# - Auto-generates CalVer version (YYYY.MM.BUILD) on main branch merges
# - Supports multi-channel releases: stable (main), rc (next-release), dev (PRs)
# - Pushes to Docker Hub and creates git tag on main
# - Sends Discord notifications on success/failure
@ -12,6 +13,7 @@ on:
push:
branches:
- main
- next-release
pull_request:
branches:
- main
@ -39,30 +41,20 @@ jobs:
id: calver
uses: cal/gitea-actions/calver@main
# Dev build: push with dev + dev-SHA tags (PR/feature branches)
- name: Build Docker image (dev)
if: github.ref != 'refs/heads/main'
uses: https://github.com/docker/build-push-action@v5
- name: Resolve Docker tags
id: tags
uses: cal/gitea-actions/docker-tags@main
with:
context: .
push: true
tags: |
manticorum67/paper-dynasty-database:dev
manticorum67/paper-dynasty-database:dev-${{ steps.calver.outputs.sha_short }}
cache-from: type=registry,ref=manticorum67/paper-dynasty-database:buildcache
cache-to: type=registry,ref=manticorum67/paper-dynasty-database:buildcache,mode=max
image: manticorum67/paper-dynasty-database
version: ${{ steps.calver.outputs.version }}
sha_short: ${{ steps.calver.outputs.sha_short }}
# Production build: push with latest + CalVer tags (main only)
- name: Build Docker image (production)
if: github.ref == 'refs/heads/main'
- name: Build and push Docker image
uses: https://github.com/docker/build-push-action@v5
with:
context: .
push: true
tags: |
manticorum67/paper-dynasty-database:latest
manticorum67/paper-dynasty-database:${{ steps.calver.outputs.version }}
manticorum67/paper-dynasty-database:${{ steps.calver.outputs.version_sha }}
tags: ${{ steps.tags.outputs.tags }}
cache-from: type=registry,ref=manticorum67/paper-dynasty-database:buildcache
cache-to: type=registry,ref=manticorum67/paper-dynasty-database:buildcache,mode=max
@ -77,38 +69,35 @@ jobs:
run: |
echo "## Docker Build Successful" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Channel:** \`${{ steps.tags.outputs.channel }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Image Tags:**" >> $GITHUB_STEP_SUMMARY
echo "- \`manticorum67/paper-dynasty-database:latest\`" >> $GITHUB_STEP_SUMMARY
echo "- \`manticorum67/paper-dynasty-database:${{ steps.calver.outputs.version }}\`" >> $GITHUB_STEP_SUMMARY
echo "- \`manticorum67/paper-dynasty-database:${{ steps.calver.outputs.version_sha }}\`" >> $GITHUB_STEP_SUMMARY
IFS=',' read -ra TAG_ARRAY <<< "${{ steps.tags.outputs.tags }}"
for tag in "${TAG_ARRAY[@]}"; do
echo "- \`${tag}\`" >> $GITHUB_STEP_SUMMARY
done
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Build Details:**" >> $GITHUB_STEP_SUMMARY
echo "- Branch: \`${{ steps.calver.outputs.branch }}\`" >> $GITHUB_STEP_SUMMARY
echo "- Commit: \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
echo "- Timestamp: \`${{ steps.calver.outputs.timestamp }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
echo "Pushed to Docker Hub!" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Pull with: \`docker pull manticorum67/paper-dynasty-database:latest\`" >> $GITHUB_STEP_SUMMARY
else
echo "_PR build - image not pushed to Docker Hub_" >> $GITHUB_STEP_SUMMARY
fi
echo "Pull with: \`docker pull manticorum67/paper-dynasty-database:${{ steps.tags.outputs.primary_tag }}\`" >> $GITHUB_STEP_SUMMARY
- name: Discord Notification - Success
if: success() && github.ref == 'refs/heads/main'
if: success() && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/next-release')
uses: cal/gitea-actions/discord-notify@main
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK }}
title: "Paper Dynasty Database"
status: success
version: ${{ steps.calver.outputs.version }}
image_tag: ${{ steps.calver.outputs.version_sha }}
image_tag: ${{ steps.tags.outputs.primary_tag }}
commit_sha: ${{ steps.calver.outputs.sha_short }}
timestamp: ${{ steps.calver.outputs.timestamp }}
- name: Discord Notification - Failure
if: failure() && github.ref == 'refs/heads/main'
if: failure() && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/next-release')
uses: cal/gitea-actions/discord-notify@main
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK }}

View File

@ -51,6 +51,13 @@ docker build -t paper-dynasty-db . # Build image
- DB connection errors → verify `POSTGRES_HOST` points to correct container name
- **CI/CD**: Gitea Actions on PR to `main` — builds Docker image, auto-generates CalVer version (`YYYY.MM.BUILD`) on merge
### Release Workflow
1. Create feature/fix branches off `next-release` (e.g., `fix/card-pricing`)
2. When done, merge the branch into `next-release` — this is the staging branch where changes accumulate
3. When ready to release, open a PR from `next-release``main`
4. CI builds Docker image on PR; CalVer tag is created on merge
5. Deploy the new image to production
## Important
- Docker image installs only Playwright Chromium (not all browsers) to optimize size

View File

@ -30,20 +30,20 @@ if DATABASE_TYPE.lower() == "postgresql":
autorollback=True, # Automatically rollback failed transactions
)
else:
# Default SQLite configuration for local development
# SQLite configuration for local development only.
# Production always uses PostgreSQL (see DATABASE_TYPE env var).
#
# synchronous=0 (OFF): SQLite skips fsync() after every write, maximising
# throughput at the cost of durability — a hard crash could corrupt the DB.
# This is an acceptable trade-off in dev where data loss is tolerable and
# write speed matters. WAL journal mode reduces (but does not eliminate)
# the corruption window by keeping the main database file consistent while
# writes land in the WAL file first.
db = SqliteDatabase(
"storage/pd_master.db",
pragmas={"journal_mode": "wal", "cache_size": -1 * 64000, "synchronous": 0},
)
date = f"{datetime.now().year}-{datetime.now().month}-{datetime.now().day}"
log_level = logging.INFO if os.environ.get("LOG_LEVEL") == "INFO" else "WARN"
logging.basicConfig(
filename=f"logs/database/{date}.log",
format="%(asctime)s - database - %(levelname)s - %(message)s",
level=log_level,
)
# 2025, 2005
ranked_cardsets = [24, 25, 26, 27, 28, 29]
LIVE_CARDSET_ID = 27
@ -498,51 +498,34 @@ class Roster(BaseModel):
team = ForeignKeyField(Team)
name = CharField()
roster_num = IntegerField()
card_1 = ForeignKeyField(Card)
card_2 = ForeignKeyField(Card)
card_3 = ForeignKeyField(Card)
card_4 = ForeignKeyField(Card)
card_5 = ForeignKeyField(Card)
card_6 = ForeignKeyField(Card)
card_7 = ForeignKeyField(Card)
card_8 = ForeignKeyField(Card)
card_9 = ForeignKeyField(Card)
card_10 = ForeignKeyField(Card)
card_11 = ForeignKeyField(Card)
card_12 = ForeignKeyField(Card)
card_13 = ForeignKeyField(Card)
card_14 = ForeignKeyField(Card)
card_15 = ForeignKeyField(Card)
card_16 = ForeignKeyField(Card)
card_17 = ForeignKeyField(Card)
card_18 = ForeignKeyField(Card)
card_19 = ForeignKeyField(Card)
card_20 = ForeignKeyField(Card)
card_21 = ForeignKeyField(Card)
card_22 = ForeignKeyField(Card)
card_23 = ForeignKeyField(Card)
card_24 = ForeignKeyField(Card)
card_25 = ForeignKeyField(Card)
card_26 = ForeignKeyField(Card)
def __str__(self):
return f"{self.team} Roster"
# def get_cards(self, team):
# all_cards = Card.select().where(Card.roster == self)
# this_roster = []
# return [this_roster.card1, this_roster.card2, this_roster.card3, this_roster.card4, this_roster.card5,
# this_roster.card6, this_roster.card7, this_roster.card8, this_roster.card9, this_roster.card10,
# this_roster.card11, this_roster.card12, this_roster.card13, this_roster.card14, this_roster.card15,
# this_roster.card16, this_roster.card17, this_roster.card18, this_roster.card19, this_roster.card20,
# this_roster.card21, this_roster.card22, this_roster.card23, this_roster.card24, this_roster.card25,
# this_roster.card26]
def get_cards(self):
return (
Card.select()
.join(RosterSlot)
.where(RosterSlot.roster == self)
.order_by(RosterSlot.slot)
)
class Meta:
database = db
table_name = "roster"
class RosterSlot(BaseModel):
roster = ForeignKeyField(Roster, backref="slots")
slot = IntegerField()
card = ForeignKeyField(Card, backref="roster_slots")
class Meta:
database = db
table_name = "rosterslot"
indexes = ((("roster", "slot"), True),)
class Result(BaseModel):
away_team = ForeignKeyField(Team)
home_team = ForeignKeyField(Team)
@ -744,6 +727,7 @@ if not SKIP_TABLE_CREATION:
db.create_tables(
[
Roster,
RosterSlot,
BattingStat,
PitchingStat,
Result,
@ -925,7 +909,13 @@ CardPosition.add_index(pos_index)
if not SKIP_TABLE_CREATION:
db.create_tables(
[BattingCard, BattingCardRatings, PitchingCard, PitchingCardRatings, CardPosition],
[
BattingCard,
BattingCardRatings,
PitchingCard,
PitchingCardRatings,
CardPosition,
],
safe=True,
)
@ -1064,6 +1054,41 @@ if not SKIP_TABLE_CREATION:
db.create_tables([StratGame, StratPlay, Decision], safe=True)
class ScoutOpportunity(BaseModel):
pack = ForeignKeyField(Pack, null=True)
opener_team = ForeignKeyField(Team)
card_ids = CharField() # JSON array of card IDs
expires_at = BigIntegerField()
created = BigIntegerField()
class Meta:
database = db
table_name = "scout_opportunity"
class ScoutClaim(BaseModel):
scout_opportunity = ForeignKeyField(ScoutOpportunity)
card = ForeignKeyField(Card)
claimed_by_team = ForeignKeyField(Team)
created = BigIntegerField()
class Meta:
database = db
table_name = "scout_claim"
scout_claim_index = ModelIndex(
ScoutClaim,
(ScoutClaim.scout_opportunity, ScoutClaim.claimed_by_team),
unique=True,
)
ScoutClaim.add_index(scout_claim_index)
if not SKIP_TABLE_CREATION:
db.create_tables([ScoutOpportunity, ScoutClaim], safe=True)
db.close()
# scout_db = SqliteDatabase(

View File

@ -1,74 +1,72 @@
import datetime
import hmac
import logging
import os
import requests
from fastapi.security import OAuth2PasswordBearer
date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}'
LOG_DATA = {
'filename': f'logs/database/{date}.log',
'format': '%(asctime)s - database - %(levelname)s - %(message)s',
'log_level': logging.INFO if os.environ.get('LOG_LEVEL') == 'INFO' else 'WARN'
}
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
master_debug = False
DB_URL = 'https://pd.manticorum.com/api/'
DB_URL = "https://pd.manticorum.com/api/"
AUTH_TOKEN = f'{os.environ.get("API_TOKEN")}'
AUTH_HEADER = {'Authorization': f'Bearer {AUTH_TOKEN}'}
AUTH_HEADER = {"Authorization": f"Bearer {AUTH_TOKEN}"}
priv_help = False if not os.environ.get('PRIVATE_IN_SCHEMA') else os.environ.get('PRIVATE_IN_SCHEMA').upper()
priv_help = (
False
if not os.environ.get("PRIVATE_IN_SCHEMA")
else os.environ.get("PRIVATE_IN_SCHEMA").upper()
)
PRIVATE_IN_SCHEMA = True if priv_help else False
if os.environ.get('TESTING') == 'False':
DB_URL = 'https://pddev.manticorum.com/api/'
if os.environ.get("TESTING") == "True":
DB_URL = "https://pddev.manticorum.com/api/"
def valid_token(token):
return token == AUTH_TOKEN
return hmac.compare_digest(token, AUTH_TOKEN)
def int_timestamp(datetime_obj: datetime) -> int:
return int(datetime.datetime.timestamp(datetime_obj) * 1000)
def mround(x, prec=2, base=.05):
def mround(x, prec=2, base=0.05):
return round(base * round(float(x) / base), prec)
def param_char(other_params):
if other_params:
return '&'
return "&"
else:
return '?'
return "?"
def get_req_url(endpoint: str, api_ver: int = 2, object_id: int = None, params: list = None):
def get_req_url(
endpoint: str, api_ver: int = 2, object_id: int = None, params: list = None
):
req_url = f'{DB_URL}/v{api_ver}/{endpoint}{"/" if object_id is not None else ""}{object_id if object_id is not None else ""}'
if params:
other_params = False
for x in params:
req_url += f'{param_char(other_params)}{x[0]}={x[1]}'
req_url += f"{param_char(other_params)}{x[0]}={x[1]}"
other_params = True
return req_url
async def db_get(endpoint: str, api_ver: int = 2, object_id: int = None, params: list = None, none_okay: bool = True,
timeout: int = 3):
async def db_get(
endpoint: str,
api_ver: int = 2,
object_id: int = None,
params: list = None,
none_okay: bool = True,
timeout: int = 3,
):
req_url = get_req_url(endpoint, api_ver=api_ver, object_id=object_id, params=params)
log_string = f'get:\n{endpoint} id: {object_id} params: {params}'
log_string = f"get:\n{endpoint} id: {object_id} params: {params}"
logging.info(log_string) if master_debug else logging.debug(log_string)
retries = 0
@ -77,37 +75,51 @@ async def db_get(endpoint: str, api_ver: int = 2, object_id: int = None, params:
resp = requests.get(req_url, timeout=timeout)
break
except requests.ReadTimeout as e:
logging.error(f'Get Timeout: {req_url} / retries: {retries} / timeout: {timeout}')
logging.error(
f"Get Timeout: {req_url} / retries: {retries} / timeout: {timeout}"
)
if retries > 1:
raise ConnectionError(f'DB: The internet was a bit too slow for me to grab the data I needed. Please '
f'hang on a few extra seconds and try again.')
raise ConnectionError(
f"DB: The internet was a bit too slow for me to grab the data I needed. Please "
f"hang on a few extra seconds and try again."
)
timeout += [2, 5][retries]
retries += 1
if resp.status_code == 200:
data = resp.json()
log_string = f'{data}'
log_string = f"{data}"
if master_debug:
logging.info(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}')
logging.info(
f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}'
)
else:
logging.debug(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}')
logging.debug(
f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}'
)
return data
elif none_okay:
data = resp.json()
log_string = f'{data}'
log_string = f"{data}"
if master_debug:
logging.info(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}')
logging.info(
f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}'
)
else:
logging.debug(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}')
logging.debug(
f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}'
)
return None
else:
logging.warning(resp.text)
raise ValueError(f'DB: {resp.text}')
raise ValueError(f"DB: {resp.text}")
async def db_patch(endpoint: str, object_id: int, params: list, api_ver: int = 2, timeout: int = 3):
async def db_patch(
endpoint: str, object_id: int, params: list, api_ver: int = 2, timeout: int = 3
):
req_url = get_req_url(endpoint, api_ver=api_ver, object_id=object_id, params=params)
log_string = f'patch:\n{endpoint} {params}'
log_string = f"patch:\n{endpoint} {params}"
logging.info(log_string) if master_debug else logging.debug(log_string)
retries = 0
@ -116,60 +128,80 @@ async def db_patch(endpoint: str, object_id: int, params: list, api_ver: int = 2
resp = requests.patch(req_url, headers=AUTH_HEADER, timeout=timeout)
break
except requests.Timeout as e:
logging.error(f'Patch Timeout: {req_url} / retries: {retries} / timeout: {timeout}')
logging.error(
f"Patch Timeout: {req_url} / retries: {retries} / timeout: {timeout}"
)
if retries > 1:
raise ConnectionError(f'DB: The internet was a bit too slow for me to grab the data I needed. Please '
f'hang on a few extra seconds and try again.')
raise ConnectionError(
f"DB: The internet was a bit too slow for me to grab the data I needed. Please "
f"hang on a few extra seconds and try again."
)
timeout += [min(3, timeout), min(5, timeout)][retries]
retries += 1
if resp.status_code == 200:
data = resp.json()
log_string = f'{data}'
log_string = f"{data}"
if master_debug:
logging.info(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}')
logging.info(
f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}'
)
else:
logging.debug(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}')
logging.debug(
f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}'
)
return data
else:
logging.warning(resp.text)
raise ValueError(f'DB: {resp.text}')
raise ValueError(f"DB: {resp.text}")
async def db_post(endpoint: str, api_ver: int = 2, payload: dict = None, timeout: int = 3):
async def db_post(
endpoint: str, api_ver: int = 2, payload: dict = None, timeout: int = 3
):
req_url = get_req_url(endpoint, api_ver=api_ver)
log_string = f'post:\n{endpoint} payload: {payload}\ntype: {type(payload)}'
log_string = f"post:\n{endpoint} payload: {payload}\ntype: {type(payload)}"
logging.info(log_string) if master_debug else logging.debug(log_string)
retries = 0
while True:
try:
resp = requests.post(req_url, json=payload, headers=AUTH_HEADER, timeout=timeout)
resp = requests.post(
req_url, json=payload, headers=AUTH_HEADER, timeout=timeout
)
break
except requests.Timeout as e:
logging.error(f'Post Timeout: {req_url} / retries: {retries} / timeout: {timeout}')
logging.error(
f"Post Timeout: {req_url} / retries: {retries} / timeout: {timeout}"
)
if retries > 1:
raise ConnectionError(f'DB: The internet was a bit too slow for me to grab the data I needed. Please '
f'hang on a few extra seconds and try again.')
raise ConnectionError(
f"DB: The internet was a bit too slow for me to grab the data I needed. Please "
f"hang on a few extra seconds and try again."
)
timeout += [min(3, timeout), min(5, timeout)][retries]
retries += 1
if resp.status_code == 200:
data = resp.json()
log_string = f'{data}'
log_string = f"{data}"
if master_debug:
logging.info(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}')
logging.info(
f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}'
)
else:
logging.debug(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}')
logging.debug(
f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}'
)
return data
else:
logging.warning(resp.text)
raise ValueError(f'DB: {resp.text}')
raise ValueError(f"DB: {resp.text}")
async def db_delete(endpoint: str, object_id: int, api_ver: int = 2, timeout=3):
req_url = get_req_url(endpoint, api_ver=api_ver, object_id=object_id)
log_string = f'delete:\n{endpoint} {object_id}'
log_string = f"delete:\n{endpoint} {object_id}"
logging.info(log_string) if master_debug else logging.debug(log_string)
retries = 0
@ -178,21 +210,29 @@ async def db_delete(endpoint: str, object_id: int, api_ver: int = 2, timeout=3):
resp = requests.delete(req_url, headers=AUTH_HEADER, timeout=timeout)
break
except requests.ReadTimeout as e:
logging.error(f'Delete Timeout: {req_url} / retries: {retries} / timeout: {timeout}')
logging.error(
f"Delete Timeout: {req_url} / retries: {retries} / timeout: {timeout}"
)
if retries > 1:
raise ConnectionError(f'DB: The internet was a bit too slow for me to grab the data I needed. Please '
f'hang on a few extra seconds and try again.')
raise ConnectionError(
f"DB: The internet was a bit too slow for me to grab the data I needed. Please "
f"hang on a few extra seconds and try again."
)
timeout += [min(3, timeout), min(5, timeout)][retries]
retries += 1
if resp.status_code == 200:
data = resp.json()
log_string = f'{data}'
log_string = f"{data}"
if master_debug:
logging.info(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}')
logging.info(
f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}'
)
else:
logging.debug(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}')
logging.debug(
f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}'
)
return True
else:
logging.warning(resp.text)
raise ValueError(f'DB: {resp.text}')
raise ValueError(f"DB: {resp.text}")

View File

@ -1,20 +1,61 @@
import logging
import os
from datetime import datetime
from fastapi import FastAPI, Request
from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.openapi.utils import get_openapi
_log_date = f"{datetime.now().year}-{datetime.now().month}-{datetime.now().day}"
logging.basicConfig(
filename=f"logs/database/{_log_date}.log",
format="%(asctime)s - database - %(levelname)s - %(message)s",
level=logging.INFO if os.environ.get("LOG_LEVEL") == "INFO" else logging.WARNING,
)
# from fastapi.staticfiles import StaticFiles
# from fastapi.templating import Jinja2Templates
from .db_engine import db
from .routers_v2 import (
current, awards, teams, rarity, cardsets, players, packtypes, packs, cards, events, results, rewards, decisions,
batstats, pitstats, notifications, paperdex, gamerewards, gauntletrewards, gauntletruns, battingcards,
battingcardratings, pitchingcards, pitchingcardratings, cardpositions, scouting, mlbplayers, stratgame, stratplays)
current,
awards,
teams,
rarity,
cardsets,
players,
packtypes,
packs,
cards,
events,
results,
rewards,
decisions,
batstats,
pitstats,
notifications,
paperdex,
gamerewards,
gauntletrewards,
gauntletruns,
battingcards,
battingcardratings,
pitchingcards,
pitchingcardratings,
cardpositions,
scouting,
mlbplayers,
stratgame,
stratplays,
scout_opportunities,
scout_claims,
)
app = FastAPI(
# root_path='/api',
responses={404: {'description': 'Not found'}},
docs_url='/api/docs',
redoc_url='/api/redoc'
responses={404: {"description": "Not found"}},
docs_url="/api/docs",
redoc_url="/api/redoc",
)
# app.mount("/static", StaticFiles(directory="storage/static"), name="static")
@ -49,14 +90,28 @@ app.include_router(mlbplayers.router)
app.include_router(stratgame.router)
app.include_router(stratplays.router)
app.include_router(decisions.router)
app.include_router(scout_opportunities.router)
app.include_router(scout_claims.router)
@app.middleware("http")
async def db_session_middleware(request: Request, call_next):
try:
db.connect(reuse_if_open=True)
response = await call_next(request)
return response
finally:
if not db.is_closed():
db.close()
@app.get("/api/docs", include_in_schema=False)
async def get_docs(req: Request):
print(req.scope)
return get_swagger_ui_html(openapi_url=req.scope.get('root_path')+'/openapi.json', title='Swagger')
return get_swagger_ui_html(
openapi_url=req.scope.get("root_path") + "/openapi.json", title="Swagger"
)
@app.get("/api/openapi.json", include_in_schema=False)
async def openapi():
return get_openapi(title='Paper Dynasty API', version=f'0.1.1', routes=app.routes)
return get_openapi(title="Paper Dynasty API", version=f"0.1.1", routes=app.routes)

View File

@ -1,14 +1,9 @@
from fastapi import APIRouter, Depends, HTTPException
import logging
from ..db_engine import db, Player
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA, PRIVATE_IN_SCHEMA
from ..db_engine import Player
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/admin',
@ -19,8 +14,7 @@ router = APIRouter(
@router.post('/stl-fix', include_in_schema=PRIVATE_IN_SCHEMA)
async def stl_cardinals_fix(token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to post. This event has been logged.'
@ -29,7 +23,6 @@ async def stl_cardinals_fix(token: str = Depends(oauth2_scheme)):
p_query = Player.update(mlbclub='St Louis Cardinals', franchise='St Louis Cardinals').where(
Player.mlbclub == 'St. Louis Cardinals'
).execute()
db.close()
return {'detail': f'Removed the period from St Louis'}

View File

@ -4,14 +4,9 @@ import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Award, model_to_dict
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA, PRIVATE_IN_SCHEMA
from ..db_engine import Award, model_to_dict, DoesNotExist
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/awards',
@ -41,7 +36,6 @@ async def get_awards(
all_awards = Award.select().order_by(Award.id)
if all_awards.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'There are no awards to filter')
if name is not None:
@ -65,7 +59,6 @@ async def get_awards(
])
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
@ -73,7 +66,6 @@ async def get_awards(
for x in all_awards:
return_val['awards'].append(model_to_dict(x))
db.close()
return return_val
@ -81,8 +73,7 @@ async def get_awards(
async def get_one_award(award_id, csv: Optional[bool] = None):
try:
this_award = Award.get_by_id(award_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No award found with id {award_id}')
if csv:
@ -93,20 +84,17 @@ async def get_one_award(award_id, csv: Optional[bool] = None):
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_award)
db.close()
return return_val
@router.post('', include_in_schema=PRIVATE_IN_SCHEMA)
async def post_awards(award: AwardModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to post awards. This event has been logged.'
@ -124,10 +112,8 @@ async def post_awards(award: AwardModel, token: str = Depends(oauth2_scheme)):
saved = this_award.save()
if saved == 1:
return_val = model_to_dict(this_award)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that roster'
@ -137,20 +123,17 @@ async def post_awards(award: AwardModel, token: str = Depends(oauth2_scheme)):
@router.delete('/{award_id}', include_in_schema=PRIVATE_IN_SCHEMA)
async def delete_award(award_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to delete awards. This event has been logged.'
)
try:
this_award = Award.get_by_id(award_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No award found with id {award_id}')
count = this_award.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Award {award_id} has been deleted')

View File

@ -6,14 +6,9 @@ import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, BattingStat, model_to_dict, fn, Card, Player, Current
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA, PRIVATE_IN_SCHEMA
from ..db_engine import db, BattingStat, model_to_dict, fn, Card, Player, Current, DoesNotExist
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/batstats',
@ -119,7 +114,6 @@ async def get_batstats(
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
@ -127,7 +121,6 @@ async def get_batstats(
for x in all_stats:
return_val['stats'].append(model_to_dict(x, recurse=False))
db.close()
return return_val
@ -166,7 +159,6 @@ async def get_player_stats(
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
@ -175,15 +167,13 @@ async def get_player_stats(
for x in all_stats:
logging.debug(f'this_line: {model_to_dict(x)}')
return_val = model_to_dict(all_stats[0])
db.close()
return return_val
@router.post('', include_in_schema=PRIVATE_IN_SCHEMA)
async def post_batstats(stats: BattingStatModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to post stats. This event has been logged.'
@ -232,7 +222,6 @@ async def post_batstats(stats: BattingStatModel, token: str = Depends(oauth2_sch
with db.atomic():
BattingStat.bulk_create(new_stats, batch_size=15)
db.close()
raise HTTPException(status_code=200, detail=f'{len(new_stats)} batting lines have been added')
@ -240,20 +229,17 @@ async def post_batstats(stats: BattingStatModel, token: str = Depends(oauth2_sch
@router.delete('/{stat_id}', include_in_schema=PRIVATE_IN_SCHEMA)
async def delete_batstat(stat_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to delete stats. This event has been logged.'
)
try:
this_stat = BattingStat.get_by_id(stat_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No stat found with id {stat_id}')
count = this_stat.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Stat {stat_id} has been deleted')

View File

@ -2,8 +2,7 @@ import os
from fastapi import APIRouter, Depends, HTTPException, Query, Response
from fastapi.responses import FileResponse
from scipy import stats
from typing import Literal, Optional, List
from typing import Literal, List
import logging
import pandas as pd
import pydantic
@ -13,21 +12,14 @@ from ..db_engine import (
db,
BattingCardRatings,
model_to_dict,
chunked,
BattingCard,
Player,
query_to_csv,
Team,
CardPosition,
)
from ..db_helpers import upsert_batting_card_ratings
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA, PRIVATE_IN_SCHEMA
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA
logging.basicConfig(
filename=LOG_DATA["filename"],
format=LOG_DATA["format"],
level=LOG_DATA["log_level"],
)
router = APIRouter(prefix="/api/v2/battingcardratings", tags=["battingcardratings"])
RATINGS_FILE = "storage/batting-ratings.csv"
@ -158,12 +150,11 @@ async def get_card_ratings(
logging.debug(f"Team: {this_team} / has_guide: {this_team.has_guide}")
if this_team is None or ts != this_team.team_hash() or this_team.has_guide != 1:
logging.warning(f"Team_id {team_id} attempted to pull ratings")
db.close()
raise HTTPException(
status_code=401, detail="You are not authorized to pull card ratings."
)
# elif not valid_token(token):
# logging.warning(f'Bad Token: {token}')
# logging.warning('Bad Token: [REDACTED]')
# db.close()
# raise HTTPException(
# status_code=401,
@ -195,7 +186,6 @@ async def get_card_ratings(
x["player_id"] = x["battingcard"]["player"]["player_id"]
del x["battingcard"], x["player"]
db.close()
return Response(
content=pd.DataFrame(return_vals).to_csv(index=False), media_type="text/csv"
)
@ -207,7 +197,6 @@ async def get_card_ratings(
model_to_dict(x, recurse=not short_output) for x in all_ratings
],
}
db.close()
return return_val
@ -328,7 +317,6 @@ def get_scouting_dfs(cardset_id: list = None):
name=f"Throw C",
)
)
db.close()
logging.debug(f"series_list: {series_list}")
return bat_df.join(series_list)
@ -340,7 +328,6 @@ async def get_card_scouting(team_id: int, ts: str):
logging.debug(f"Team: {this_team} / has_guide: {this_team.has_guide}")
if this_team is None or ts != this_team.team_hash() or this_team.has_guide != 1:
logging.warning(f"Team_id {team_id} attempted to pull ratings")
db.close()
return (
"Your team does not have the ratings guide enabled. If you have purchased a copy ping Cal to "
"make sure it is enabled on your team. If you are interested you can pick it up here (thank you!): "
@ -362,8 +349,7 @@ async def get_card_scouting(team_id: int, ts: str):
@router.post("/calculate/scouting", include_in_schema=PRIVATE_IN_SCHEMA)
async def post_calc_scouting(token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401, detail="You are not authorized to calculate card ratings."
)
@ -399,8 +385,7 @@ async def get_basic_scouting(cardset_id: list = Query(default=None)):
@router.post("/calculate/basic", include_in_schema=PRIVATE_IN_SCHEMA)
async def post_calc_basic(token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401, detail="You are not authorized to calculate basic ratings."
)
@ -646,21 +631,18 @@ async def post_calc_basic(token: str = Depends(oauth2_scheme)):
@router.get("/{ratings_id}")
async def get_one_rating(ratings_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401, detail="You are not authorized to pull card ratings."
)
this_rating = BattingCardRatings.get_or_none(BattingCardRatings.id == ratings_id)
if this_rating is None:
db.close()
raise HTTPException(
status_code=404, detail=f"BattingCardRating id {ratings_id} not found"
)
r_data = model_to_dict(this_rating)
db.close()
return r_data
@ -672,8 +654,7 @@ async def get_player_ratings(
token: str = Depends(oauth2_scheme),
):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401, detail="You are not authorized to pull card ratings."
)
@ -694,15 +675,13 @@ async def get_player_ratings(
"count": all_ratings.count(),
"ratings": [model_to_dict(x, recurse=not short_output) for x in all_ratings],
}
db.close()
return return_val
@router.put("", include_in_schema=PRIVATE_IN_SCHEMA)
async def put_ratings(ratings: RatingsList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401, detail="You are not authorized to post card ratings."
)
@ -730,28 +709,24 @@ async def put_ratings(ratings: RatingsList, token: str = Depends(oauth2_scheme))
# Use PostgreSQL-compatible upsert helper
upsert_batting_card_ratings(new_ratings, batch_size=30)
db.close()
return f"Updated ratings: {updates}; new ratings: {len(new_ratings)}"
@router.delete("/{ratings_id}", include_in_schema=PRIVATE_IN_SCHEMA)
async def delete_rating(ratings_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401, detail="You are not authorized to post card ratings."
)
this_rating = BattingCardRatings.get_or_none(BattingCardRatings.id == ratings_id)
if this_rating is None:
db.close()
raise HTTPException(
status_code=404, detail=f"BattingCardRating id {ratings_id} not found"
)
count = this_rating.delete_instance()
db.close()
if count == 1:
return f"Rating {this_rating} has been deleted"

View File

@ -5,15 +5,10 @@ from typing import Literal, Optional, List
import logging
import pydantic
from ..db_engine import db, BattingCard, model_to_dict, fn, chunked, Player, MlbPlayer
from ..db_engine import db, BattingCard, model_to_dict, fn, Player, MlbPlayer
from ..db_helpers import upsert_batting_cards
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA["filename"],
format=LOG_DATA["format"],
level=LOG_DATA["log_level"],
)
router = APIRouter(prefix="/api/v2/battingcards", tags=["battingcards"])
@ -65,7 +60,6 @@ async def get_batting_cards(
"count": all_cards.count(),
"cards": [model_to_dict(x, recurse=not short_output) for x in all_cards],
}
db.close()
return return_val
@ -73,13 +67,11 @@ async def get_batting_cards(
async def get_one_card(card_id: int):
this_card = BattingCard.get_or_none(BattingCard.id == card_id)
if this_card is None:
db.close()
raise HTTPException(
status_code=404, detail=f"BattingCard id {card_id} not found"
)
r_card = model_to_dict(this_card)
db.close()
return r_card
@ -99,15 +91,13 @@ async def get_player_cards(
"count": all_cards.count(),
"cards": [model_to_dict(x, recurse=not short_output) for x in all_cards],
}
db.close()
return return_val
@router.put("")
async def put_cards(cards: BattingCardList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to post batting cards. This event has been logged.",
@ -157,7 +147,6 @@ async def put_cards(cards: BattingCardList, token: str = Depends(oauth2_scheme))
# Use PostgreSQL-compatible upsert helper
upsert_batting_cards(new_cards, batch_size=30)
db.close()
return f"Updated cards: {updates}; new cards: {len(new_cards)}"
@ -176,8 +165,7 @@ async def patch_card(
token: str = Depends(oauth2_scheme),
):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to patch batting cards. This event has been logged.",
@ -185,7 +173,6 @@ async def patch_card(
this_card = BattingCard.get_or_none(BattingCard.id == card_id)
if this_card is None:
db.close()
raise HTTPException(
status_code=404, detail=f"BattingCard id {card_id} not found"
)
@ -211,10 +198,8 @@ async def patch_card(
if this_card.save() == 1:
return_val = model_to_dict(this_card)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail="Well slap my ass and call me a teapot; I could not save that card",
@ -224,8 +209,7 @@ async def patch_card(
@router.delete("/{card_id}")
async def delete_card(card_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to delete batting cards. This event has been logged.",
@ -233,13 +217,11 @@ async def delete_card(card_id: int, token: str = Depends(oauth2_scheme)):
this_card = BattingCard.get_or_none(BattingCard.id == card_id)
if this_card is None:
db.close()
raise HTTPException(
status_code=404, detail=f"BattingCard id {card_id} not found"
)
count = this_card.delete_instance()
db.close()
if count == 1:
return f"Card {this_card} has been deleted"
@ -252,8 +234,7 @@ async def delete_card(card_id: int, token: str = Depends(oauth2_scheme)):
@router.delete("")
async def delete_all_cards(token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to delete batting cards. This event has been logged.",

View File

@ -4,15 +4,10 @@ import logging
import pydantic
from pydantic import root_validator
from ..db_engine import db, CardPosition, model_to_dict, chunked, Player, fn
from ..db_engine import db, CardPosition, model_to_dict, Player, fn
from ..db_helpers import upsert_card_positions
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA["filename"],
format=LOG_DATA["format"],
level=LOG_DATA["log_level"],
)
router = APIRouter(prefix="/api/v2/cardpositions", tags=["cardpositions"])
@ -95,7 +90,6 @@ async def get_card_positions(
"count": all_pos.count(),
"positions": [model_to_dict(x, recurse=not short_output) for x in all_pos],
}
db.close()
return return_val
@ -103,21 +97,18 @@ async def get_card_positions(
async def get_one_position(position_id: int):
this_pos = CardPosition.get_or_none(CardPosition.id == position_id)
if this_pos is None:
db.close()
raise HTTPException(
status_code=404, detail=f"CardPosition id {position_id} not found"
)
r_data = model_to_dict(this_pos)
db.close()
return r_data
@router.put("")
async def put_positions(positions: PositionList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to post card positions. This event has been logged.",
@ -149,15 +140,13 @@ async def put_positions(positions: PositionList, token: str = Depends(oauth2_sch
# Use PostgreSQL-compatible upsert helper
upsert_card_positions(new_cards, batch_size=30)
db.close()
return f"Updated cards: {updates}; new cards: {len(new_cards)}"
@router.delete("/{position_id}")
async def delete_position(position_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to delete card positions. This event has been logged.",
@ -165,13 +154,11 @@ async def delete_position(position_id: int, token: str = Depends(oauth2_scheme))
this_pos = CardPosition.get_or_none(CardPosition.id == position_id)
if this_pos is None:
db.close()
raise HTTPException(
status_code=404, detail=f"CardPosition id {position_id} not found"
)
count = this_pos.delete_instance()
db.close()
if count == 1:
return f"Card Position {this_pos} has been deleted"

View File

@ -4,14 +4,9 @@ import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Card, model_to_dict, Team, Player, Pack, Paperdex, CARDSETS
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..db_engine import db, Card, model_to_dict, Team, Player, Pack, Paperdex, CARDSETS, DoesNotExist
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/cards',
@ -46,22 +41,19 @@ async def get_cards(
if team_id is not None:
try:
this_team = Team.get_by_id(team_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No team found with id {team_id}')
all_cards = all_cards.where(Card.team == this_team)
if player_id is not None:
try:
this_player = Player.get_by_id(player_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No player found with id {player_id}')
all_cards = all_cards.where(Card.player == this_player)
if pack_id is not None:
try:
this_pack = Pack.get_by_id(pack_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No pack found with id {pack_id}')
all_cards = all_cards.where(Card.pack == this_pack)
if value is not None:
@ -108,7 +100,6 @@ async def get_cards(
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
@ -127,7 +118,6 @@ async def get_cards(
# return_val['cards'].append(model_to_dict(x))
db.close()
return return_val
@ -135,32 +125,27 @@ async def get_cards(
async def v1_cards_get_one(card_id, csv: Optional[bool] = False):
try:
this_card = Card.get_by_id(card_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No card found with id {card_id}')
if csv:
data_list = [
['id', 'player', 'team', 'pack', 'value', 'roster1', 'roster2', 'roster3'],
[this_card.id, this_card.player, this_card.team.abbrev, this_card.pack, this_card.value,
this_card.roster1.name, this_card.roster2.name, this_card.roster3.name]
['id', 'player', 'team', 'pack', 'value'],
[this_card.id, this_card.player, this_card.team.abbrev, this_card.pack, this_card.value]
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_card)
db.close()
return return_val
@router.post('')
async def v1_cards_post(cards: CardModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to post cards. This event has been logged.'
@ -195,7 +180,6 @@ async def v1_cards_post(cards: CardModel, token: str = Depends(oauth2_scheme)):
cost_query = Player.update(cost=Player.cost + 1).where(Player.player_id << player_ids)
cost_query.execute()
# sheets.post_new_cards(SHEETS_AUTH, lc_id)
db.close()
raise HTTPException(status_code=200, detail=f'{len(new_cards)} cards have been added')
@ -203,7 +187,7 @@ async def v1_cards_post(cards: CardModel, token: str = Depends(oauth2_scheme)):
# @router.post('/ai-update')
# async def v1_cards_ai_update(token: str = Depends(oauth2_scheme)):
# if not valid_token(token):
# logging.warning(f'Bad Token: {token}')
# logging.warning('Bad Token: [REDACTED]')
# db.close()
# raise HTTPException(
# status_code=401,
@ -218,8 +202,7 @@ async def v1_cards_post(cards: CardModel, token: str = Depends(oauth2_scheme)):
async def v1_cards_legal_check(
rarity_name: str, card_id: list = Query(default=None), token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='Unauthorized'
@ -251,23 +234,20 @@ async def v1_cards_legal_check(
@router.post('/post-update/{starting_id}')
async def v1_cards_post_update(starting_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to update card lists. This event has been logged.'
)
# sheets.post_new_cards(SHEETS_AUTH, starting_id)
db.close()
raise HTTPException(status_code=200, detail=f'Just sent cards to sheets starting at ID {starting_id}')
@router.post('/post-delete')
async def v1_cards_post_delete(del_ids: str, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to delete card lists. This event has been logged.'
@ -280,8 +260,7 @@ async def v1_cards_post_delete(del_ids: str, token: str = Depends(oauth2_scheme)
@router.post('/wipe-team/{team_id}')
async def v1_cards_wipe_team(team_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to wipe teams. This event has been logged.'
@ -289,12 +268,11 @@ async def v1_cards_wipe_team(team_id: int, token: str = Depends(oauth2_scheme)):
try:
this_team = Team.get_by_id(team_id)
except Exception as e:
except DoesNotExist as e:
logging.error(f'/cards/wipe-team/{team_id} - could not find team')
raise HTTPException(status_code=404, detail=f'Team {team_id} not found')
t_query = Card.update(team=None).where(Card.team == this_team).execute()
db.close()
return f'Wiped {t_query} cards'
@ -304,16 +282,14 @@ async def v1_cards_patch(
value: Optional[int] = None, variant: Optional[int] = None, roster1_id: Optional[int] = None, roster2_id: Optional[int] = None,
roster3_id: Optional[int] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to patch cards. This event has been logged.'
)
try:
this_card = Card.get_by_id(card_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No card found with id {card_id}')
if player_id is not None:
@ -338,10 +314,8 @@ async def v1_cards_patch(
if this_card.save() == 1:
return_val = model_to_dict(this_card)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that rarity'
@ -351,20 +325,17 @@ async def v1_cards_patch(
@router.delete('/{card_id}')
async def v1_cards_delete(card_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to delete packs. This event has been logged.'
)
try:
this_card = Card.get_by_id(card_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No cards found with id {card_id}')
count = this_card.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Card {card_id} has been deleted')

View File

@ -4,14 +4,9 @@ import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Cardset, model_to_dict, fn, Event
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..db_engine import Cardset, model_to_dict, fn, Event, DoesNotExist
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/cardsets',
@ -36,7 +31,6 @@ async def get_cardsets(
all_cardsets = Cardset.select().order_by(Cardset.id)
if all_cardsets.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'There are no cardsets to filter')
if name is not None:
@ -47,7 +41,7 @@ async def get_cardsets(
try:
this_event = Event.get_by_id(event_id)
all_cardsets = all_cardsets.where(Cardset.event == this_event)
except Exception as e:
except DoesNotExist as e:
logging.error(f'Failed to find event {event_id}: {e}')
raise HTTPException(status_code=404, detail=f'Event id {event_id} not found')
if in_packs is not None:
@ -56,7 +50,6 @@ async def get_cardsets(
all_cardsets = all_cardsets.where(Cardset.ranked_legal == ranked_legal)
if all_cardsets.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'No cardsets found')
if csv:
@ -72,7 +65,6 @@ async def get_cardsets(
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
@ -80,7 +72,6 @@ async def get_cardsets(
for x in all_cardsets:
return_val['cardsets'].append(model_to_dict(x))
db.close()
return return_val
@ -113,9 +104,8 @@ async def search_cardsets(
try:
this_event = Event.get_by_id(event_id)
all_cardsets = all_cardsets.where(Cardset.event == this_event)
except Exception as e:
except DoesNotExist as e:
logging.error(f'Failed to find event {event_id}: {e}')
db.close()
raise HTTPException(status_code=404, detail=f'Event id {event_id} not found')
# Convert to list for sorting
@ -153,7 +143,6 @@ async def search_cardsets(
'cardsets': [model_to_dict(x) for x in limited_results]
}
db.close()
return return_val
@ -161,8 +150,7 @@ async def search_cardsets(
async def get_one_cardset(cardset_id, csv: Optional[bool] = False):
try:
this_cardset = Cardset.get_by_id(cardset_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No cardset found with id {cardset_id}')
if csv:
@ -172,19 +160,16 @@ async def get_one_cardset(cardset_id, csv: Optional[bool] = False):
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_cardset)
db.close()
return return_val
@router.post('')
async def post_cardsets(cardset: CardsetModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to post cardsets. This event has been logged.'
@ -192,7 +177,6 @@ async def post_cardsets(cardset: CardsetModel, token: str = Depends(oauth2_schem
dupe_set = Cardset.get_or_none(Cardset.name == cardset.name)
if dupe_set:
db.close()
raise HTTPException(status_code=400, detail=f'There is already a cardset using {cardset.name}')
this_cardset = Cardset(**cardset.__dict__)
@ -200,7 +184,6 @@ async def post_cardsets(cardset: CardsetModel, token: str = Depends(oauth2_schem
saved = this_cardset.save()
if saved == 1:
return_val = model_to_dict(this_cardset)
db.close()
return return_val
else:
raise HTTPException(
@ -215,16 +198,14 @@ async def patch_cardsets(
for_purchase: Optional[bool] = None, total_cards: Optional[int] = None, ranked_legal: Optional[bool] = None,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to patch cardsets. This event has been logged.'
)
try:
this_cardset = Cardset.get_by_id(cardset_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No cardset found with id {cardset_id}')
if name is not None:
@ -242,7 +223,6 @@ async def patch_cardsets(
if this_cardset.save() == 1:
return_val = model_to_dict(this_cardset)
db.close()
return return_val
else:
raise HTTPException(
@ -254,20 +234,17 @@ async def patch_cardsets(
@router.delete('/{cardset_id}')
async def delete_cardsets(cardset_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to delete cardsets. This event has been logged.'
)
try:
this_cardset = Cardset.get_by_id(cardset_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No cardset found with id {cardset_id}')
count = this_cardset.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Cardset {cardset_id} has been deleted')

View File

@ -4,14 +4,9 @@ from typing import Optional
import logging
import pydantic
from ..db_engine import db, Current, model_to_dict
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA, PRIVATE_IN_SCHEMA
from ..db_engine import Current, model_to_dict, DoesNotExist
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/current',
@ -40,11 +35,9 @@ async def get_current(season: Optional[int] = None, csv: Optional[bool] = False)
]
return_val = DataFrame(current_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(current)
db.close()
return return_val
@ -52,8 +45,7 @@ async def get_current(season: Optional[int] = None, csv: Optional[bool] = False)
async def get_one_current(current_id, csv: Optional[bool] = False):
try:
current = Current.get_by_id(current_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No current found with id {current_id}')
if csv:
@ -63,19 +55,16 @@ async def get_one_current(current_id, csv: Optional[bool] = False):
]
return_val = DataFrame(current_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(current)
db.close()
return return_val
@router.post('', include_in_schema=PRIVATE_IN_SCHEMA)
async def post_current(current: CurrentModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to post current. This event has been logged.'
@ -83,7 +72,6 @@ async def post_current(current: CurrentModel, token: str = Depends(oauth2_scheme
dupe_curr = Current.get_or_none(Current.season == current.season)
if dupe_curr:
db.close()
raise HTTPException(status_code=400, detail=f'There is already a current for season {current.season}')
this_curr = Current(
@ -96,7 +84,6 @@ async def post_current(current: CurrentModel, token: str = Depends(oauth2_scheme
saved = this_curr.save()
if saved == 1:
return_val = model_to_dict(this_curr)
db.close()
return return_val
else:
raise HTTPException(status_code=418, detail='Well slap my ass and call me a teapot; I could not save that team')
@ -108,16 +95,14 @@ async def patch_current(
gsheet_template: Optional[str] = None, gsheet_version: Optional[str] = None,
live_scoreboard: Optional[int] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to patch current. This event has been logged.'
)
try:
current = Current.get_by_id(current_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No current found with id {current_id}')
if season is not None:
@ -133,7 +118,6 @@ async def patch_current(
if current.save() == 1:
return_val = model_to_dict(current)
db.close()
return return_val
else:
raise HTTPException(
@ -145,20 +129,17 @@ async def patch_current(
@router.delete('/{current_id}', include_in_schema=PRIVATE_IN_SCHEMA)
async def delete_current(current_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to delete current. This event has been logged.'
)
try:
this_curr = Current.get_by_id(current_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No current found with id {current_id}')
count = this_curr.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Current {current_id} has been deleted')

View File

@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException, Query, Response
from typing import List, Optional, Literal
import copy
from typing import List, Optional
import logging
import pandas as pd
import pydantic
@ -11,20 +10,14 @@ from ..db_engine import (
StratGame,
Player,
model_to_dict,
chunked,
fn,
Team,
Card,
StratPlay,
)
from ..db_helpers import upsert_decisions
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA["filename"],
format=LOG_DATA["format"],
level=LOG_DATA["log_level"],
)
router = APIRouter(prefix="/api/v2/decisions", tags=["decisions"])
@ -112,7 +105,6 @@ async def get_decisions(
"count": all_dec.count(),
"decisions": [model_to_dict(x, recurse=not short_output) for x in all_dec],
}
db.close()
if csv:
return_vals = return_dec["decisions"]
@ -136,7 +128,6 @@ async def get_decisions(
exclude = first + ["lob_all", "lob_all_rate", "lob_2outs", "rbi%"]
output = output[first + [col for col in output.columns if col not in exclude]]
db.close()
return Response(
content=pd.DataFrame(output).to_csv(index=False), media_type="text/csv"
)
@ -189,7 +180,6 @@ async def get_decisions_for_rest(
return_dec.append(this_val)
db.close()
return Response(
content=pd.DataFrame(return_dec).to_csv(index=False, header=False),
media_type="text/csv",
@ -211,12 +201,11 @@ async def patch_decision(
token: str = Depends(oauth2_scheme),
):
if not valid_token(token):
logging.warning(f"patch_decision - Bad Token: {token}")
logging.warning("patch_decision - Bad Token: [REDACTED]")
raise HTTPException(status_code=401, detail="Unauthorized")
this_dec = Decision.get_or_none(Decision.id == decision_id)
if this_dec is None:
db.close()
raise HTTPException(
status_code=404, detail=f"Decision ID {decision_id} not found"
)
@ -242,10 +231,8 @@ async def patch_decision(
if this_dec.save() == 1:
d_result = model_to_dict(this_dec)
db.close()
return d_result
else:
db.close()
raise HTTPException(
status_code=500, detail=f"Unable to patch decision {decision_id}"
)
@ -254,7 +241,7 @@ async def patch_decision(
@router.post("")
async def post_decisions(dec_list: DecisionList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"post_decisions - Bad Token: {token}")
logging.warning("post_decisions - Bad Token: [REDACTED]")
raise HTTPException(status_code=401, detail="Unauthorized")
new_dec = []
@ -277,7 +264,6 @@ async def post_decisions(dec_list: DecisionList, token: str = Depends(oauth2_sch
with db.atomic():
# Use PostgreSQL-compatible upsert helper
upsert_decisions(new_dec, batch_size=10)
db.close()
return f"Inserted {len(new_dec)} decisions"
@ -285,18 +271,16 @@ async def post_decisions(dec_list: DecisionList, token: str = Depends(oauth2_sch
@router.delete("/{decision_id}")
async def delete_decision(decision_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"delete_decision - Bad Token: {token}")
logging.warning("delete_decision - Bad Token: [REDACTED]")
raise HTTPException(status_code=401, detail="Unauthorized")
this_dec = Decision.get_or_none(Decision.id == decision_id)
if this_dec is None:
db.close()
raise HTTPException(
status_code=404, detail=f"Decision ID {decision_id} not found"
)
count = this_dec.delete_instance()
db.close()
if count == 1:
return f"Decision {decision_id} has been deleted"
@ -309,16 +293,14 @@ async def delete_decision(decision_id: int, token: str = Depends(oauth2_scheme))
@router.delete("/game/{game_id}")
async def delete_decisions_game(game_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"delete_decisions_game - Bad Token: {token}")
logging.warning("delete_decisions_game - Bad Token: [REDACTED]")
raise HTTPException(status_code=401, detail="Unauthorized")
this_game = StratGame.get_or_none(StratGame.id == game_id)
if not this_game:
db.close()
raise HTTPException(status_code=404, detail=f"Game ID {game_id} not found")
count = Decision.delete().where(Decision.game == this_game).execute()
db.close()
if count > 0:
return f"Deleted {count} decisions matching Game ID {game_id}"

View File

@ -4,14 +4,9 @@ import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Event, model_to_dict, fn
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..db_engine import Event, model_to_dict, fn, DoesNotExist
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/events',
@ -54,7 +49,6 @@ async def v1_events_get(
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
@ -62,7 +56,6 @@ async def v1_events_get(
for x in all_events:
return_val['events'].append(model_to_dict(x))
db.close()
return return_val
@ -70,8 +63,7 @@ async def v1_events_get(
async def v1_events_get_one(event_id, csv: Optional[bool] = False):
try:
this_event = Event.get_by_id(event_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No event found with id {event_id}')
if csv:
@ -82,20 +74,17 @@ async def v1_events_get_one(event_id, csv: Optional[bool] = False):
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_event)
db.close()
return return_val
@router.post('')
async def v1_events_post(event: EventModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to post events. This event has been logged.'
@ -103,7 +92,6 @@ async def v1_events_post(event: EventModel, token: str = Depends(oauth2_scheme))
dupe_event = Event.get_or_none(Event.name == event.name)
if dupe_event:
db.close()
raise HTTPException(status_code=400, detail=f'There is already an event using {event.name}')
this_event = Event(
@ -118,10 +106,8 @@ async def v1_events_post(event: EventModel, token: str = Depends(oauth2_scheme))
saved = this_event.save()
if saved == 1:
return_val = model_to_dict(this_event)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that cardset'
@ -134,16 +120,14 @@ async def v1_events_patch(
url: Optional[str] = None, thumbnail: Optional[str] = None, active: Optional[bool] = None,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to patch events. This event has been logged.'
)
try:
this_event = Event.get_by_id(event_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No event found with id {event_id}')
if name is not None:
@ -161,10 +145,8 @@ async def v1_events_patch(
if this_event.save() == 1:
return_val = model_to_dict(this_event)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that event'
@ -174,20 +156,17 @@ async def v1_events_patch(
@router.delete('/{event_id}')
async def v1_events_delete(event_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to delete events. This event has been logged.'
)
try:
this_event = Event.get_by_id(event_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No event found with id {event_id}')
count = this_event.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Event {event_id} has been deleted')

View File

@ -4,14 +4,9 @@ import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, GameRewards, model_to_dict
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..db_engine import GameRewards, model_to_dict, DoesNotExist
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/gamerewards',
@ -54,7 +49,6 @@ async def v1_gamerewards_get(
])
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
@ -62,7 +56,6 @@ async def v1_gamerewards_get(
for x in all_rewards:
return_val['gamerewards'].append(model_to_dict(x))
db.close()
return return_val
@ -70,8 +63,7 @@ async def v1_gamerewards_get(
async def v1_gamerewards_get_one(gamereward_id, csv: Optional[bool] = None):
try:
this_game_reward = GameRewards.get_by_id(gamereward_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No game reward found with id {gamereward_id}')
if csv:
@ -82,20 +74,17 @@ async def v1_gamerewards_get_one(gamereward_id, csv: Optional[bool] = None):
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_game_reward)
db.close()
return return_val
@router.post('')
async def v1_gamerewards_post(game_reward: GameRewardModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to post game rewards. This event has been logged.'
@ -111,10 +100,8 @@ async def v1_gamerewards_post(game_reward: GameRewardModel, token: str = Depends
saved = this_award.save()
if saved == 1:
return_val = model_to_dict(this_award)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that roster'
@ -126,16 +113,14 @@ async def v1_gamerewards_patch(
game_reward_id: int, name: Optional[str] = None, pack_type_id: Optional[int] = None,
player_id: Optional[int] = None, money: Optional[int] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to patch gamerewards. This event has been logged.'
)
try:
this_game_reward = GameRewards.get_by_id(game_reward_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No game reward found with id {game_reward_id}')
if name is not None:
@ -158,7 +143,6 @@ async def v1_gamerewards_patch(
if this_game_reward.save() == 1:
return_val = model_to_dict(this_game_reward)
db.close()
return return_val
else:
raise HTTPException(
@ -170,20 +154,17 @@ async def v1_gamerewards_patch(
@router.delete('/{gamereward_id}')
async def v1_gamerewards_delete(gamereward_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to delete awards. This event has been logged.'
)
try:
this_award = GameRewards.get_by_id(gamereward_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No award found with id {gamereward_id}')
count = this_award.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Game Reward {gamereward_id} has been deleted')

View File

@ -3,15 +3,10 @@ from typing import Optional, List
import logging
import pydantic
from ..db_engine import db, GauntletReward, model_to_dict, chunked, DatabaseError
from ..db_engine import db, GauntletReward, model_to_dict, DatabaseError, DoesNotExist
from ..db_helpers import upsert_gauntlet_rewards
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA["filename"],
format=LOG_DATA["format"],
level=LOG_DATA["log_level"],
)
router = APIRouter(prefix="/api/v2/gauntletrewards", tags=["gauntletrewards"])
@ -55,7 +50,6 @@ async def v1_gauntletreward_get(
for x in all_rewards:
return_val["rewards"].append(model_to_dict(x))
db.close()
return return_val
@ -63,15 +57,13 @@ async def v1_gauntletreward_get(
async def v1_gauntletreward_get_one(gauntletreward_id):
try:
this_reward = GauntletReward.get_by_id(gauntletreward_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(
status_code=404,
detail=f"No gauntlet reward found with id {gauntletreward_id}",
)
return_val = model_to_dict(this_reward)
db.close()
return return_val
@ -86,8 +78,7 @@ async def v1_gauntletreward_patch(
token: str = Depends(oauth2_scheme),
):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to patch gauntlet rewards. This event has been logged.",
@ -95,7 +86,6 @@ async def v1_gauntletreward_patch(
this_reward = GauntletReward.get_or_none(GauntletReward.id == gauntletreward_id)
if this_reward is None:
db.close()
raise KeyError(f"Gauntlet Reward ID {gauntletreward_id} not found")
if gauntlet_id is not None:
@ -111,10 +101,8 @@ async def v1_gauntletreward_patch(
if this_reward.save():
r_curr = model_to_dict(this_reward)
db.close()
return r_curr
else:
db.close()
raise DatabaseError(f"Unable to patch gauntlet reward {gauntletreward_id}")
@ -123,8 +111,7 @@ async def v1_gauntletreward_post(
gauntletreward: GauntletRewardList, token: str = Depends(oauth2_scheme)
):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to post gauntlets. This event has been logged.",
@ -137,7 +124,6 @@ async def v1_gauntletreward_post(
with db.atomic():
# Use PostgreSQL-compatible upsert helper
upsert_gauntlet_rewards(all_rewards, batch_size=15)
db.close()
return f"Inserted {len(all_rewards)} gauntlet rewards"

View File

@ -4,14 +4,9 @@ from typing import Optional
import logging
import pydantic
from ..db_engine import db, GauntletRun, model_to_dict, DatabaseError
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..db_engine import GauntletRun, model_to_dict, DatabaseError, DoesNotExist
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/gauntletruns',
@ -82,7 +77,6 @@ async def get_gauntletruns(
for x in all_gauntlets:
return_val['runs'].append(model_to_dict(x))
db.close()
return return_val
@ -90,12 +84,10 @@ async def get_gauntletruns(
async def get_one_gauntletrun(gauntletrun_id):
try:
this_gauntlet = GauntletRun.get_by_id(gauntletrun_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No gauntlet found with id {gauntletrun_id}')
return_val = model_to_dict(this_gauntlet)
db.close()
return return_val
@ -105,8 +97,7 @@ async def patch_gauntletrun(
gsheet: Optional[str] = None, created: Optional[bool] = None, ended: Optional[bool] = None,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to patch gauntlet runs. This event has been logged.'
@ -114,7 +105,6 @@ async def patch_gauntletrun(
this_run = GauntletRun.get_or_none(GauntletRun.id == gauntletrun_id)
if this_run is None:
db.close()
raise KeyError(f'Gauntlet Run ID {gauntletrun_id} not found')
if team_id is not None:
@ -138,18 +128,15 @@ async def patch_gauntletrun(
if this_run.save():
r_curr = model_to_dict(this_run)
db.close()
return r_curr
else:
db.close()
raise DatabaseError(f'Unable to patch gauntlet run {gauntletrun_id}')
@router.post('')
async def post_gauntletrun(gauntletrun: GauntletRunModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to post gauntlets. This event has been logged.'
@ -169,10 +156,8 @@ async def post_gauntletrun(gauntletrun: GauntletRunModel, token: str = Depends(o
if this_run.save():
r_run = model_to_dict(this_run)
db.close()
return r_run
else:
db.close()
raise DatabaseError(f'Unable to post gauntlet run')

View File

@ -5,27 +5,19 @@ from fastapi import APIRouter, Depends, HTTPException, Response, Query
from typing import Optional, List
import logging
import pydantic
from pandas import DataFrame
from ..db_engine import (
db,
MlbPlayer,
Player,
BattingCard,
PitchingCard,
model_to_dict,
fn,
chunked,
query_to_csv,
)
from ..db_helpers import upsert_mlb_players
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA["filename"],
format=LOG_DATA["format"],
level=LOG_DATA["log_level"],
)
router = APIRouter(prefix="/api/v2/mlbplayers", tags=["mlbplayers"])
@ -37,7 +29,7 @@ class PlayerModel(pydantic.BaseModel):
key_fangraphs: int = None
key_bbref: str = None
key_retro: str = None
offense_col: int = random.randint(1, 3)
offense_col: int = pydantic.Field(default_factory=lambda: random.randint(1, 3))
class PlayerList(pydantic.BaseModel):
@ -111,14 +103,12 @@ async def get_players(
if csv:
return_val = query_to_csv(all_players)
db.close()
return Response(content=return_val, media_type="text/csv")
return_val = {
"count": all_players.count(),
"players": [model_to_dict(x) for x in all_players],
}
db.close()
return return_val
@ -126,13 +116,11 @@ async def get_players(
async def get_one_player(player_id: int):
this_player = MlbPlayer.get_or_none(MlbPlayer.id == player_id)
if this_player is None:
db.close()
raise HTTPException(
status_code=404, detail=f"MlbPlayer id {player_id} not found"
)
r_data = model_to_dict(this_player)
db.close()
return r_data
@ -149,8 +137,7 @@ async def patch_player(
token: str = Depends(oauth2_scheme),
):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to patch mlb players. This event has been logged.",
@ -158,7 +145,6 @@ async def patch_player(
this_player = MlbPlayer.get_or_none(MlbPlayer.id == player_id)
if this_player is None:
db.close()
raise HTTPException(
status_code=404, detail=f"MlbPlayer id {player_id} not found"
)
@ -180,10 +166,8 @@ async def patch_player(
if this_player.save() == 1:
return_val = model_to_dict(this_player)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail="Well slap my ass and call me a teapot; I could not save that player",
@ -193,8 +177,7 @@ async def patch_player(
@router.post("")
async def post_players(players: PlayerList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to post mlb players. This event has been logged.",
@ -209,7 +192,6 @@ async def post_players(players: PlayerList, token: str = Depends(oauth2_scheme))
| (MlbPlayer.key_bbref == x.key_bbref)
)
if dupes.count() > 0:
db.close()
raise HTTPException(
status_code=400,
detail=f"{x.first_name} {x.last_name} has a key already in the database",
@ -221,7 +203,6 @@ async def post_players(players: PlayerList, token: str = Depends(oauth2_scheme))
# Use PostgreSQL-compatible upsert helper
# Note: Duplicate check is already done above, so this is effectively just insert
upsert_mlb_players(new_players, batch_size=15)
db.close()
return f"Inserted {len(new_players)} new MLB players"
@ -229,8 +210,7 @@ async def post_players(players: PlayerList, token: str = Depends(oauth2_scheme))
@router.post("/one")
async def post_one_player(player: PlayerModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to post mlb players. This event has been logged.",
@ -245,7 +225,6 @@ async def post_one_player(player: PlayerModel, token: str = Depends(oauth2_schem
logging.info(f"POST /mlbplayers/one - dupes found:")
for x in dupes:
logging.info(f"{x}")
db.close()
raise HTTPException(
status_code=400,
detail=f"{player.first_name} {player.last_name} has a key already in the database",
@ -255,7 +234,6 @@ async def post_one_player(player: PlayerModel, token: str = Depends(oauth2_schem
saved = new_player.save()
if saved == 1:
return_val = model_to_dict(new_player)
db.close()
return return_val
else:
raise HTTPException(
@ -267,8 +245,7 @@ async def post_one_player(player: PlayerModel, token: str = Depends(oauth2_schem
@router.delete("/{player_id}")
async def delete_player(player_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to delete mlb players. This event has been logged.",
@ -276,13 +253,11 @@ async def delete_player(player_id: int, token: str = Depends(oauth2_scheme)):
this_player = MlbPlayer.get_or_none(MlbPlayer.id == player_id)
if this_player is None:
db.close()
raise HTTPException(
status_code=404, detail=f"MlbPlayer id {player_id} not found"
)
count = this_player.delete_instance()
db.close()
if count == 1:
raise HTTPException(
@ -300,8 +275,7 @@ async def update_columns(
mlbplayer_id: Optional[int] = None, token: str = Depends(oauth2_scheme)
):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to update mlb players. This event has been logged.",
@ -327,7 +301,6 @@ async def update_columns(
logging.info(f"Updated {count} batting cards for {x.first_name} {x.last_name}")
update_card_urls(x)
db.close()
return f"Updated {total_count} batting cards"
@ -337,8 +310,7 @@ async def update_names(
mlbplayer_id: Optional[int] = None, token: str = Depends(oauth2_scheme)
):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to update mlb players. This event has been logged.",
@ -360,14 +332,13 @@ async def update_names(
logging.info(f"Update {count} player records for {x.first_name} {x.last_name}")
update_card_urls(x)
db.close()
return f"Updated {total_count} names"
# @router.post('/link-players')
# async def post_players(token: str = Depends(oauth2_scheme)):
# if not valid_token(token):
# logging.warning(f'Bad Token: {token}')
# logging.warning('Bad Token: [REDACTED]')
# db.close()
# raise HTTPException(
# status_code=401,

View File

@ -5,14 +5,9 @@ import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Notification, model_to_dict, fn
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..db_engine import Notification, model_to_dict, fn, DoesNotExist
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/notifs',
@ -38,7 +33,6 @@ async def get_notifs(
all_notif = Notification.select().order_by(Notification.id)
if all_notif.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'There are no notifications to filter')
if created_after is not None:
@ -66,7 +60,6 @@ async def get_notifs(
])
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
@ -74,7 +67,6 @@ async def get_notifs(
for x in all_notif:
return_val['notifs'].append(model_to_dict(x))
db.close()
return return_val
@ -82,8 +74,7 @@ async def get_notifs(
async def get_one_notif(notif_id, csv: Optional[bool] = None):
try:
this_notif = Notification.get_by_id(notif_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No notification found with id {notif_id}')
if csv:
@ -94,20 +85,17 @@ async def get_one_notif(notif_id, csv: Optional[bool] = None):
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_notif)
db.close()
return return_val
@router.post('')
async def post_notif(notif: NotifModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to post notifications. This event has been logged.'
@ -126,10 +114,8 @@ async def post_notif(notif: NotifModel, token: str = Depends(oauth2_scheme)):
saved = this_notif.save()
if saved == 1:
return_val = model_to_dict(this_notif)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that notification'
@ -142,16 +128,14 @@ async def patch_notif(
field_name: Optional[str] = None, message: Optional[str] = None, about: Optional[str] = None,
ack: Optional[bool] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to patch notifications. This event has been logged.'
)
try:
this_notif = Notification.get_by_id(notif_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No notification found with id {notif_id}')
if title is not None:
@ -171,7 +155,6 @@ async def patch_notif(
if this_notif.save() == 1:
return_val = model_to_dict(this_notif)
db.close()
return return_val
else:
raise HTTPException(
@ -183,20 +166,17 @@ async def patch_notif(
@router.delete('/{notif_id}')
async def delete_notif(notif_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to delete notifications. This event has been logged.'
)
try:
this_notif = Notification.get_by_id(notif_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No notification found with id {notif_id}')
count = this_notif.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Notification {notif_id} has been deleted')

View File

@ -6,14 +6,9 @@ import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Cardset, model_to_dict, Pack, Team, PackType
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..db_engine import db, Cardset, model_to_dict, Pack, Team, PackType, DoesNotExist
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/packs',
@ -41,29 +36,25 @@ async def get_packs(
all_packs = Pack.select()
if all_packs.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'There are no packs to filter')
if team_id is not None:
try:
this_team = Team.get_by_id(team_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No team found with id {team_id}')
all_packs = all_packs.where(Pack.team == this_team)
if pack_type_id is not None:
try:
this_pack_type = PackType.get_by_id(pack_type_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No pack type found with id {pack_type_id}')
all_packs = all_packs.where(Pack.pack_type == this_pack_type)
if pack_team_id is not None:
try:
this_pack_team = Team.get_by_id(pack_team_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No team found with id {pack_team_id}')
all_packs = all_packs.where(Pack.pack_team == this_pack_team)
elif exact_match:
@ -72,8 +63,7 @@ async def get_packs(
if pack_cardset_id is not None:
try:
this_pack_cardset = Cardset.get_by_id(pack_cardset_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No cardset found with id {pack_cardset_id}')
all_packs = all_packs.where(Pack.pack_cardset == this_pack_cardset)
elif exact_match:
@ -103,7 +93,6 @@ async def get_packs(
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
@ -111,16 +100,14 @@ async def get_packs(
for x in all_packs:
return_val['packs'].append(model_to_dict(x))
db.close()
return return_val
@router.get('/{pack_id}')
async def get_one_pack(pack_id, csv: Optional[bool] = False):
async def get_one_pack(pack_id: int, csv: Optional[bool] = False):
try:
this_pack = Pack.get_by_id(pack_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No pack found with id {pack_id}')
if csv:
@ -131,20 +118,17 @@ async def get_one_pack(pack_id, csv: Optional[bool] = False):
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_pack)
db.close()
return return_val
@router.post('')
async def post_pack(packs: PackModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to post packs. This event has been logged.'
@ -163,7 +147,6 @@ async def post_pack(packs: PackModel, token: str = Depends(oauth2_scheme)):
with db.atomic():
Pack.bulk_create(new_packs, batch_size=15)
db.close()
raise HTTPException(status_code=200, detail=f'{len(new_packs)} packs have been added')
@ -171,8 +154,7 @@ async def post_pack(packs: PackModel, token: str = Depends(oauth2_scheme)):
@router.post('/one')
async def post_one_pack(pack: PackPydantic, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to post packs. This event has been logged.'
@ -189,7 +171,6 @@ async def post_one_pack(pack: PackPydantic, token: str = Depends(oauth2_scheme))
saved = this_pack.save()
if saved == 1:
return_val = model_to_dict(this_pack)
db.close()
return return_val
else:
raise HTTPException(
@ -203,16 +184,14 @@ async def patch_pack(
pack_id, team_id: Optional[int] = None, pack_type_id: Optional[int] = None, open_time: Optional[int] = None,
pack_team_id: Optional[int] = None, pack_cardset_id: Optional[int] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to patch packs. This event has been logged.'
)
try:
this_pack = Pack.get_by_id(pack_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No pack found with id {pack_id}')
if team_id is not None:
@ -237,7 +216,6 @@ async def patch_pack(
if this_pack.save() == 1:
return_val = model_to_dict(this_pack)
db.close()
return return_val
else:
raise HTTPException(
@ -249,20 +227,17 @@ async def patch_pack(
@router.delete('/{pack_id}')
async def delete_pack(pack_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to delete packs. This event has been logged.'
)
try:
this_pack = Pack.get_by_id(pack_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No packs found with id {pack_id}')
count = this_pack.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Pack {pack_id} has been deleted')

View File

@ -4,14 +4,9 @@ import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, PackType, model_to_dict, fn
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..db_engine import PackType, model_to_dict, fn, DoesNotExist
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/packtypes',
@ -34,7 +29,6 @@ async def get_packtypes(
all_packtypes = PackType.select().order_by(PackType.id)
if all_packtypes.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'There are no packtypes to filter')
if name is not None:
@ -60,7 +54,6 @@ async def get_packtypes(
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
@ -68,7 +61,6 @@ async def get_packtypes(
for x in all_packtypes:
return_val['packtypes'].append(model_to_dict(x))
db.close()
return return_val
@ -76,8 +68,7 @@ async def get_packtypes(
async def get_one_packtype(packtype_id, csv: Optional[bool] = False):
try:
this_packtype = PackType.get_by_id(packtype_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No packtype found with id {packtype_id}')
if csv:
@ -87,20 +78,17 @@ async def get_one_packtype(packtype_id, csv: Optional[bool] = False):
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_packtype)
db.close()
return return_val
@router.post('')
async def post_packtypes(packtype: PacktypeModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to post packtypes. This event has been logged.'
@ -108,7 +96,6 @@ async def post_packtypes(packtype: PacktypeModel, token: str = Depends(oauth2_sc
dupe_packtype = PackType.get_or_none(PackType.name == packtype.name)
if dupe_packtype:
db.close()
raise HTTPException(status_code=400, detail=f'There is already a packtype using {packtype.name}')
this_packtype = PackType(
@ -122,7 +109,6 @@ async def post_packtypes(packtype: PacktypeModel, token: str = Depends(oauth2_sc
saved = this_packtype.save()
if saved == 1:
return_val = model_to_dict(this_packtype)
db.close()
return return_val
else:
raise HTTPException(
@ -136,16 +122,14 @@ async def patch_packtype(
packtype_id, name: Optional[str] = None, card_count: Optional[int] = None, description: Optional[str] = None,
cost: Optional[int] = None, available: Optional[bool] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to patch packtypes. This event has been logged.'
)
try:
this_packtype = PackType.get_by_id(packtype_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No packtype found with id {packtype_id}')
if name is not None:
@ -161,7 +145,6 @@ async def patch_packtype(
if this_packtype.save() == 1:
return_val = model_to_dict(this_packtype)
db.close()
return return_val
else:
raise HTTPException(
@ -173,20 +156,17 @@ async def patch_packtype(
@router.delete('/{packtype_id}')
async def delete_packtype(packtype_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to delete packtypes. This event has been logged.'
)
try:
this_packtype = PackType.get_by_id(packtype_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No packtype found with id {packtype_id}')
count = this_packtype.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Packtype {packtype_id} has been deleted')

View File

@ -5,14 +5,9 @@ import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Paperdex, model_to_dict, Player, Cardset, Team
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..db_engine import Paperdex, model_to_dict, Player, Cardset, Team, DoesNotExist
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/paperdex',
@ -34,7 +29,6 @@ async def get_paperdex(
all_dex = Paperdex.select().join(Player).join(Cardset).order_by(Paperdex.id)
if all_dex.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'There are no paperdex to filter')
if team_id is not None:
@ -67,7 +61,6 @@ async def get_paperdex(
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
@ -75,7 +68,6 @@ async def get_paperdex(
for x in all_dex:
return_val['paperdex'].append(model_to_dict(x, recurse=not flat))
db.close()
return return_val
@ -83,8 +75,7 @@ async def get_paperdex(
async def get_one_paperdex(paperdex_id, csv: Optional[bool] = False):
try:
this_dex = Paperdex.get_by_id(paperdex_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No paperdex found with id {paperdex_id}')
if csv:
@ -94,20 +85,17 @@ async def get_one_paperdex(paperdex_id, csv: Optional[bool] = False):
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_dex)
db.close()
return return_val
@router.post('')
async def post_paperdex(paperdex: PaperdexModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to post paperdex. This event has been logged.'
@ -116,7 +104,6 @@ async def post_paperdex(paperdex: PaperdexModel, token: str = Depends(oauth2_sch
dupe_dex = Paperdex.get_or_none(Paperdex.team_id == paperdex.team_id, Paperdex.player_id == paperdex.player_id)
if dupe_dex:
return_val = model_to_dict(dupe_dex)
db.close()
return return_val
this_dex = Paperdex(
@ -128,7 +115,6 @@ async def post_paperdex(paperdex: PaperdexModel, token: str = Depends(oauth2_sch
saved = this_dex.save()
if saved == 1:
return_val = model_to_dict(this_dex)
db.close()
return return_val
else:
raise HTTPException(
@ -142,16 +128,14 @@ async def patch_paperdex(
paperdex_id, team_id: Optional[int] = None, player_id: Optional[int] = None, created: Optional[int] = None,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to patch paperdex. This event has been logged.'
)
try:
this_dex = Paperdex.get_by_id(paperdex_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No paperdex found with id {paperdex_id}')
if team_id is not None:
@ -163,7 +147,6 @@ async def patch_paperdex(
if this_dex.save() == 1:
return_val = model_to_dict(this_dex)
db.close()
return return_val
else:
raise HTTPException(
@ -175,20 +158,17 @@ async def patch_paperdex(
@router.delete('/{paperdex_id}')
async def delete_paperdex(paperdex_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to delete rewards. This event has been logged.'
)
try:
this_dex = Paperdex.get_by_id(paperdex_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No paperdex found with id {paperdex_id}')
count = this_dex.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Paperdex {this_dex} has been deleted')
@ -199,8 +179,7 @@ async def delete_paperdex(paperdex_id, token: str = Depends(oauth2_scheme)):
@router.post('/wipe-ai')
async def wipe_ai_paperdex(token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='Unauthorized'

View File

@ -2,7 +2,7 @@ import os
from fastapi import APIRouter, Depends, HTTPException, Query, Response
from fastapi.responses import FileResponse
from typing import Literal, Optional, List
from typing import Literal, List
import logging
import pandas as pd
import pydantic
@ -12,7 +12,6 @@ from ..db_engine import (
db,
PitchingCardRatings,
model_to_dict,
chunked,
PitchingCard,
Player,
query_to_csv,
@ -20,13 +19,8 @@ from ..db_engine import (
CardPosition,
)
from ..db_helpers import upsert_pitching_card_ratings
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA["filename"],
format=LOG_DATA["format"],
level=LOG_DATA["log_level"],
)
router = APIRouter(prefix="/api/v2/pitchingcardratings", tags=["pitchingcardratings"])
RATINGS_FILE = "storage/pitching-ratings.csv"
@ -152,8 +146,7 @@ async def get_card_ratings(
token: str = Depends(oauth2_scheme),
):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401, detail="You are not authorized to pull card ratings."
)
@ -177,7 +170,6 @@ async def get_card_ratings(
if csv:
return_val = query_to_csv(all_ratings)
db.close()
return Response(content=return_val, media_type="text/csv")
else:
@ -187,7 +179,6 @@ async def get_card_ratings(
model_to_dict(x, recurse=not short_output) for x in all_ratings
],
}
db.close()
return return_val
@ -246,7 +237,6 @@ def get_scouting_dfs(cardset_id: list = None):
dict([(x.player.player_id, x.error) for x in positions]), name=f"Error P"
),
]
db.close()
logging.debug(f"series_list: {series_list}")
return pit_df.join(series_list)
@ -258,7 +248,6 @@ async def get_card_scouting(team_id: int, ts: str):
logging.debug(f"Team: {this_team} / has_guide: {this_team.has_guide}")
if this_team is None or ts != this_team.team_hash() or this_team.has_guide != 1:
logging.warning(f"Team_id {team_id} attempted to pull ratings")
db.close()
return (
"Your team does not have the ratings guide enabled. If you have purchased a copy ping Cal to "
"make sure it is enabled on your team. If you are interested you can pick it up here (thank you!): "
@ -280,8 +269,7 @@ async def get_card_scouting(team_id: int, ts: str):
@router.post("/calculate/scouting")
async def post_calc_scouting(token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401, detail="You are not authorized to calculate card ratings."
)
@ -317,8 +305,7 @@ async def get_basic_scouting():
@router.post("/calculate/basic")
async def post_calc_basic(token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401, detail="You are not authorized to calculate basic ratings."
)
@ -497,21 +484,18 @@ async def post_calc_basic(token: str = Depends(oauth2_scheme)):
@router.get("/{ratings_id}")
async def get_one_rating(ratings_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401, detail="You are not authorized to pull card ratings."
)
this_rating = PitchingCardRatings.get_or_none(PitchingCardRatings.id == ratings_id)
if this_rating is None:
db.close()
raise HTTPException(
status_code=404, detail=f"PitchingCardRating id {ratings_id} not found"
)
r_data = model_to_dict(this_rating)
db.close()
return r_data
@ -535,15 +519,13 @@ async def get_player_ratings(
"count": all_ratings.count(),
"ratings": [model_to_dict(x, recurse=not short_output) for x in all_ratings],
}
db.close()
return return_val
@router.put("")
async def put_ratings(ratings: RatingsList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401, detail="You are not authorized to post card ratings."
)
@ -571,28 +553,24 @@ async def put_ratings(ratings: RatingsList, token: str = Depends(oauth2_scheme))
# Use PostgreSQL-compatible upsert helper
upsert_pitching_card_ratings(new_ratings, batch_size=30)
db.close()
return f"Updated ratings: {updates}; new ratings: {len(new_ratings)}"
@router.delete("/{ratings_id}")
async def delete_rating(ratings_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401, detail="You are not authorized to post card ratings."
)
this_rating = PitchingCardRatings.get_or_none(PitchingCardRatings.id == ratings_id)
if this_rating is None:
db.close()
raise HTTPException(
status_code=404, detail=f"PitchingCardRating id {ratings_id} not found"
)
count = this_rating.delete_instance()
db.close()
if count == 1:
return f"Rating {this_rating} has been deleted"

View File

@ -5,15 +5,10 @@ from typing import Literal, Optional, List
import logging
import pydantic
from ..db_engine import db, PitchingCard, model_to_dict, chunked, Player, fn, MlbPlayer
from ..db_engine import db, PitchingCard, model_to_dict, Player, fn, MlbPlayer
from ..db_helpers import upsert_pitching_cards
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA["filename"],
format=LOG_DATA["format"],
level=LOG_DATA["log_level"],
)
router = APIRouter(prefix="/api/v2/pitchingcards", tags=["pitchingcards"])
@ -62,7 +57,6 @@ async def get_pitching_cards(
"count": all_cards.count(),
"cards": [model_to_dict(x, recurse=not short_output) for x in all_cards],
}
db.close()
return return_val
@ -70,13 +64,11 @@ async def get_pitching_cards(
async def get_one_card(card_id: int):
this_card = PitchingCard.get_or_none(PitchingCard.id == card_id)
if this_card is None:
db.close()
raise HTTPException(
status_code=404, detail=f"PitchingCard id {card_id} not found"
)
r_card = model_to_dict(this_card)
db.close()
return r_card
@ -96,15 +88,13 @@ async def get_player_cards(
"count": all_cards.count(),
"cards": [model_to_dict(x, recurse=not short_output) for x in all_cards],
}
db.close()
return return_val
@router.put("")
async def put_cards(cards: PitchingCardList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to post pitching cards. This event has been logged.",
@ -153,7 +143,6 @@ async def put_cards(cards: PitchingCardList, token: str = Depends(oauth2_scheme)
# Use PostgreSQL-compatible upsert helper
upsert_pitching_cards(new_cards, batch_size=30)
db.close()
return f"Updated cards: {updates}; new cards: {len(new_cards)}"
@ -170,8 +159,7 @@ async def patch_card(
token: str = Depends(oauth2_scheme),
):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to patch pitching cards. This event has been logged.",
@ -179,7 +167,6 @@ async def patch_card(
this_card = PitchingCard.get_or_none(PitchingCard.id == card_id)
if this_card is None:
db.close()
raise HTTPException(
status_code=404, detail=f"PitchingCard id {card_id} not found"
)
@ -201,10 +188,8 @@ async def patch_card(
if this_card.save() == 1:
return_val = model_to_dict(this_card)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail="Well slap my ass and call me a teapot; I could not save that card",
@ -214,8 +199,7 @@ async def patch_card(
@router.delete("/{card_id}")
async def delete_card(card_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to delete pitching cards. This event has been logged.",
@ -223,11 +207,9 @@ async def delete_card(card_id: int, token: str = Depends(oauth2_scheme)):
this_card = PitchingCard.get_or_none(PitchingCard.id == card_id)
if this_card is None:
db.close()
raise HTTPException(status_code=404, detail=f"Pitching id {card_id} not found")
count = this_card.delete_instance()
db.close()
if count == 1:
return f"Card {this_card} has been deleted"
@ -240,8 +222,7 @@ async def delete_card(card_id: int, token: str = Depends(oauth2_scheme)):
@router.delete("")
async def delete_all_cards(token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to delete pitching cards. This event has been logged.",

View File

@ -5,14 +5,9 @@ import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, PitchingStat, model_to_dict, Card, Player, Current
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..db_engine import db, PitchingStat, model_to_dict, Card, Player, Current, DoesNotExist
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/pitstats',
@ -108,7 +103,6 @@ async def get_pit_stats(
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
@ -116,15 +110,13 @@ async def get_pit_stats(
for x in all_stats:
return_val['stats'].append(model_to_dict(x, recurse=False))
db.close()
return return_val
@router.post('')
async def post_pitstat(stats: PitchingStatModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to post stats. This event has been logged.'
@ -164,7 +156,6 @@ async def post_pitstat(stats: PitchingStatModel, token: str = Depends(oauth2_sch
with db.atomic():
PitchingStat.bulk_create(new_stats, batch_size=15)
db.close()
raise HTTPException(status_code=200, detail=f'{len(new_stats)} pitching lines have been added')
@ -172,20 +163,17 @@ async def post_pitstat(stats: PitchingStatModel, token: str = Depends(oauth2_sch
@router.delete('/{stat_id}')
async def delete_pitstat(stat_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to delete stats. This event has been logged.'
)
try:
this_stat = PitchingStat.get_by_id(stat_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No stat found with id {stat_id}')
count = this_stat.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Stat {stat_id} has been deleted')

View File

@ -1,12 +1,10 @@
import datetime
import os.path
import base64
import pandas as pd
from fastapi import APIRouter, Depends, HTTPException, Request, Response, Query
from fastapi.responses import FileResponse
from fastapi.templating import Jinja2Templates
from html2image import Html2Image
from typing import Optional, List, Literal
import logging
import pydantic
@ -14,12 +12,11 @@ from pandas import DataFrame
from playwright.async_api import async_playwright
from ..card_creation import get_batter_card_data, get_pitcher_card_data
from ..db_engine import (
from ..db_engine import (, DoesNotExist
db,
Player,
model_to_dict,
fn,
chunked,
Paperdex,
Cardset,
Rarity,
@ -29,9 +26,10 @@ from ..db_engine import (
PitchingCardRatings,
CardPosition,
MlbPlayer,
DoesNotExist,
)
from ..db_helpers import upsert_players
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..dependencies import oauth2_scheme, valid_token
# Franchise normalization: Convert city+team names to city-agnostic team names
# This enables cross-era player matching (e.g., 'Oakland Athletics' -> 'Athletics')
@ -76,11 +74,6 @@ def normalize_franchise(franchise: str) -> str:
return FRANCHISE_NORMALIZE.get(titled, titled)
logging.basicConfig(
filename=LOG_DATA["filename"],
format=LOG_DATA["format"],
level=LOG_DATA["log_level"],
)
router = APIRouter(prefix="/api/v2/players", tags=["players"])
@ -152,7 +145,6 @@ async def get_players(
):
all_players = Player.select()
if all_players.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f"There are no players to filter")
if name is not None:
@ -239,7 +231,6 @@ async def get_players(
if csv:
card_vals = [model_to_dict(x) for x in all_players]
db.close()
for x in card_vals:
x["player_name"] = x["p_name"]
@ -328,7 +319,6 @@ async def get_players(
# return_val['players'].append(model_to_dict(x, recurse=not flat))
db.close()
return return_val
@ -479,7 +469,6 @@ async def get_random_player(
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type="text/csv")
else:
@ -497,7 +486,6 @@ async def get_random_player(
return_val["players"].append(this_record)
# return_val['players'].append(model_to_dict(x))
db.close()
return return_val
@ -591,16 +579,14 @@ async def search_players(
return_val["players"].append(this_record)
db.close()
return return_val
@router.get("/{player_id}")
async def get_one_player(player_id, csv: Optional[bool] = False):
async def get_one_player(player_id: int, csv: Optional[bool] = False):
try:
this_player = Player.get_by_id(player_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(
status_code=404, detail=f"No player found with id {player_id}"
)
@ -632,7 +618,6 @@ async def get_one_player(player_id, csv: Optional[bool] = False):
"description",
]
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
data_list.append(
[
this_player.id,
@ -659,8 +644,8 @@ async def get_one_player(player_id, csv: Optional[bool] = False):
this_player.description,
]
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type="text/csv")
else:
return_val = model_to_dict(this_player)
@ -668,7 +653,6 @@ async def get_one_player(player_id, csv: Optional[bool] = False):
return_val["paperdex"] = {"count": this_dex.count(), "paperdex": []}
for x in this_dex:
return_val["paperdex"]["paperdex"].append(model_to_dict(x, recurse=False))
db.close()
return return_val
@ -685,8 +669,7 @@ async def get_batter_card(
):
try:
this_player = Player.get_by_id(player_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(
status_code=404, detail=f"No player found with id {player_id}"
)
@ -701,7 +684,6 @@ async def get_batter_card(
)
and html is False
):
db.close()
return FileResponse(
path=f"storage/cards/cardset-{this_player.cardset.id}/{card_type}/{player_id}-{d}-v{variant}.png",
media_type="image/png",
@ -788,7 +770,6 @@ async def get_batter_card(
html_response = templates.TemplateResponse("player_card.html", card_data)
if html:
db.close()
return html_response
updates = 0
@ -843,7 +824,6 @@ async def get_batter_card(
# save_as=f'{player_id}-{d}-v{variant}.png'
# )
db.close()
return FileResponse(path=file_path, media_type="image/png", headers=headers)
@ -881,8 +861,7 @@ async def v1_players_patch(
token: str = Depends(oauth2_scheme),
):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to patch players. This event has been logged.",
@ -890,8 +869,7 @@ async def v1_players_patch(
try:
this_player = Player.get_by_id(player_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(
status_code=404, detail=f"No player found with id {player_id}"
)
@ -914,8 +892,7 @@ async def v1_players_patch(
if cardset_id is not None:
try:
this_cardset = Cardset.get_by_id(cardset_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(
status_code=404, detail=f"No cardset found with id {cardset_id}"
)
@ -923,8 +900,7 @@ async def v1_players_patch(
if rarity_id is not None:
try:
this_rarity = Rarity.get_by_id(rarity_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(
status_code=404, detail=f"No rarity found with id {rarity_id}"
)
@ -986,7 +962,6 @@ async def v1_players_patch(
if this_player.save() == 1:
return_val = model_to_dict(this_player)
db.close()
return return_val
else:
raise HTTPException(
@ -998,8 +973,7 @@ async def v1_players_patch(
@router.put("")
async def put_players(players: PlayerModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to post players. This event has been logged.",
@ -1068,7 +1042,6 @@ async def put_players(players: PlayerModel, token: str = Depends(oauth2_scheme))
with db.atomic():
# Use PostgreSQL-compatible upsert helper (preserves SQLite compatibility)
upsert_players(new_players, batch_size=15)
db.close()
# sheets.update_all_players(SHEETS_AUTH)
raise HTTPException(
@ -1079,8 +1052,7 @@ async def put_players(players: PlayerModel, token: str = Depends(oauth2_scheme))
@router.post("")
async def post_players(new_player: PlayerPydantic, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to post players. This event has been logged.",
@ -1091,7 +1063,6 @@ async def post_players(new_player: PlayerPydantic, token: str = Depends(oauth2_s
& (Player.cardset_id == new_player.cardset_id)
)
if dupe_query.count() != 0:
db.close()
raise HTTPException(
status_code=400,
detail=f"This appears to be a duplicate with player {dupe_query[0].player_id}",
@ -1104,7 +1075,6 @@ async def post_players(new_player: PlayerPydantic, token: str = Depends(oauth2_s
p_id = Player.insert(new_player.dict()).execute()
return_val = model_to_dict(Player.get_by_id(p_id))
db.close()
return return_val
@ -1113,8 +1083,7 @@ async def post_image_reset(
player_id: int, dev: bool = False, token: str = Depends(oauth2_scheme)
):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to modify players. This event has been logged.",
@ -1122,7 +1091,6 @@ async def post_image_reset(
this_player = Player.get_or_none(Player.player_id == player_id)
if this_player is None:
db.close()
raise HTTPException(status_code=404, detail=f"Player ID {player_id} not found")
now = datetime.datetime.now()
@ -1143,15 +1111,13 @@ async def post_image_reset(
this_player.save()
r_player = model_to_dict(this_player)
db.close()
return r_player
@router.delete("/{player_id}")
async def delete_player(player_id, token: str = Depends(oauth2_scheme)):
async def delete_player(player_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to delete players. This event has been logged.",
@ -1159,14 +1125,12 @@ async def delete_player(player_id, token: str = Depends(oauth2_scheme)):
try:
this_player = Player.get_by_id(player_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(
status_code=404, detail=f"No player found with id {player_id}"
)
count = this_player.delete_instance()
db.close()
if count == 1:
raise HTTPException(

View File

@ -4,14 +4,9 @@ import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Rarity, model_to_dict, fn
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..db_engine import Rarity, model_to_dict, fn, DoesNotExist
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/rarities',
@ -31,7 +26,6 @@ async def get_rarities(value: Optional[int] = None, name: Optional[str] = None,
all_rarities = Rarity.select().order_by(Rarity.id)
if all_rarities.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'There are no rarities to filter')
if value is not None:
@ -44,7 +38,6 @@ async def get_rarities(value: Optional[int] = None, name: Optional[str] = None,
all_rarities = all_rarities.where(Rarity.value <= max_value)
if all_rarities.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'No rarities found')
if csv:
@ -57,7 +50,6 @@ async def get_rarities(value: Optional[int] = None, name: Optional[str] = None,
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
@ -65,7 +57,6 @@ async def get_rarities(value: Optional[int] = None, name: Optional[str] = None,
for x in all_rarities:
return_val['rarities'].append(model_to_dict(x))
db.close()
return return_val
@ -73,8 +64,7 @@ async def get_rarities(value: Optional[int] = None, name: Optional[str] = None,
async def get_one_rarity(rarity_id, csv: Optional[bool] = False):
try:
this_rarity = Rarity.get_by_id(rarity_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No rarity found with id {rarity_id}')
if csv:
@ -87,19 +77,16 @@ async def get_one_rarity(rarity_id, csv: Optional[bool] = False):
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_rarity)
db.close()
return return_val
@router.post('')
async def post_rarity(rarity: RarityModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to post rarities. This event has been logged.'
@ -107,7 +94,6 @@ async def post_rarity(rarity: RarityModel, token: str = Depends(oauth2_scheme)):
dupe_team = Rarity.get_or_none(Rarity.name)
if dupe_team:
db.close()
raise HTTPException(status_code=400, detail=f'There is already a rarity using {rarity.name}')
this_rarity = Rarity(
@ -119,7 +105,6 @@ async def post_rarity(rarity: RarityModel, token: str = Depends(oauth2_scheme)):
saved = this_rarity.save()
if saved == 1:
return_val = model_to_dict(this_rarity)
db.close()
return return_val
else:
raise HTTPException(
@ -133,16 +118,14 @@ async def patch_rarity(
rarity_id, value: Optional[int] = None, name: Optional[str] = None, color: Optional[str] = None,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to patch rarities. This event has been logged.'
)
try:
this_rarity = Rarity.get_by_id(rarity_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No rarity found with id {rarity_id}')
if value is not None:
@ -154,7 +137,6 @@ async def patch_rarity(
if this_rarity.save() == 1:
return_val = model_to_dict(this_rarity)
db.close()
return return_val
else:
raise HTTPException(
@ -166,20 +148,17 @@ async def patch_rarity(
@router.delete('/{rarity_id}')
async def v1_rarities_delete(rarity_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to delete rarities. This event has been logged.'
)
try:
this_rarity = Rarity.get_by_id(rarity_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No rarity found with id {rarity_id}')
count = this_rarity.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Rarity {rarity_id} has been deleted')

View File

@ -4,14 +4,9 @@ import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Result, model_to_dict, Team, DataError
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..db_engine import Result, model_to_dict, Team, DataError, DoesNotExist
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/results',
@ -55,32 +50,28 @@ async def get_results(
try:
this_team = Team.get_by_id(away_team_id)
all_results = all_results.where(Result.away_team == this_team)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No team found with id {away_team_id}')
if home_team_id is not None:
try:
this_team = Team.get_by_id(home_team_id)
all_results = all_results.where(Result.home_team == this_team)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No team found with id {home_team_id}')
if team_one_id is not None:
try:
this_team = Team.get_by_id(team_one_id)
all_results = all_results.where((Result.home_team == this_team) | (Result.away_team == this_team))
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No team found with id {team_one_id}')
if team_two_id is not None:
try:
this_team = Team.get_by_id(team_two_id)
all_results = all_results.where((Result.home_team == this_team) | (Result.away_team == this_team))
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No team found with id {team_two_id}')
if away_score_min is not None:
@ -153,7 +144,6 @@ async def get_results(
])
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
@ -161,7 +151,6 @@ async def get_results(
for x in all_results:
return_val['results'].append(model_to_dict(x))
db.close()
return return_val
@ -169,8 +158,7 @@ async def get_results(
async def get_one_results(result_id, csv: Optional[bool] = None):
try:
this_result = Result.get_by_id(result_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No result found with id {result_id}')
if csv:
@ -184,12 +172,10 @@ async def get_one_results(result_id, csv: Optional[bool] = None):
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_result)
db.close()
return return_val
@ -199,7 +185,7 @@ async def get_team_results(
all_results = Result.select().where((Result.away_team_id == team_id) | (Result.home_team_id == team_id)).order_by(Result.id)
try:
this_team = Team.get_by_id(team_id)
except Exception as e:
except DoesNotExist as e:
logging.error(f'Unknown team id {team_id} trying to pull team results')
raise HTTPException(404, f'Team id {team_id} not found')
@ -243,7 +229,6 @@ async def get_team_results(
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
@ -254,15 +239,13 @@ async def get_team_results(
'casual_wins': c_wins,
'casual_losses': c_loss,
}
db.close()
return return_val
@router.post('')
async def post_result(result: ResultModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to post results. This event has been logged.'
@ -273,12 +256,10 @@ async def post_result(result: ResultModel, token: str = Depends(oauth2_scheme)):
if result.ranked:
if not result.away_team_ranking:
db.close()
error = f'Ranked game did not include away team ({result.away_team_id}) ranking.'
logging.error(error)
raise DataError(error)
if not result.home_team_ranking:
db.close()
error = f'Ranked game did not include home team ({result.home_team_id}) ranking.'
logging.error(error)
raise DataError(error)
@ -328,10 +309,8 @@ async def post_result(result: ResultModel, token: str = Depends(oauth2_scheme)):
if saved == 1:
return_val = model_to_dict(this_result)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that roster'
@ -346,16 +325,14 @@ async def patch_result(
season: Optional[int] = None, short_game: Optional[bool] = None, game_type: Optional[str] = None,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to patch results. This event has been logged.'
)
try:
this_result = Result.get_by_id(result_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No result found with id {result_id}')
if away_team_id is not None:
@ -396,10 +373,8 @@ async def patch_result(
if this_result.save() == 1:
return_val = model_to_dict(this_result)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that event'
@ -409,20 +384,17 @@ async def patch_result(
@router.delete('/{result_id}')
async def delete_result(result_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to post results. This event has been logged.'
)
try:
this_result = Result.get_by_id(result_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No result found with id {result_id}')
count = this_result.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Result {result_id} has been deleted')

View File

@ -5,14 +5,9 @@ import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Reward, model_to_dict, fn
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..db_engine import Reward, model_to_dict, fn, DoesNotExist
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/rewards',
@ -36,7 +31,6 @@ async def get_rewards(
all_rewards = Reward.select().order_by(Reward.id)
if all_rewards.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'There are no rewards to filter')
if name is not None:
@ -55,7 +49,6 @@ async def get_rewards(
all_rewards = all_rewards.where(Reward.week == week)
if all_rewards.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'No rewards found')
if csv:
@ -68,7 +61,6 @@ async def get_rewards(
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
@ -76,7 +68,6 @@ async def get_rewards(
for x in all_rewards:
return_val['rewards'].append(model_to_dict(x, recurse=not flat))
db.close()
return return_val
@ -84,8 +75,7 @@ async def get_rewards(
async def get_one_reward(reward_id, csv: Optional[bool] = False):
try:
this_reward = Reward.get_by_id(reward_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No reward found with id {reward_id}')
if csv:
@ -95,20 +85,17 @@ async def get_one_reward(reward_id, csv: Optional[bool] = False):
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_reward)
db.close()
return return_val
@router.post('')
async def post_rewards(reward: RewardModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to post rewards. This event has been logged.'
@ -123,7 +110,6 @@ async def post_rewards(reward: RewardModel, token: str = Depends(oauth2_scheme))
saved = this_reward.save()
if saved == 1:
return_val = model_to_dict(this_reward)
db.close()
return return_val
else:
raise HTTPException(
@ -137,16 +123,14 @@ async def patch_reward(
reward_id, name: Optional[str] = None, team_id: Optional[int] = None, created: Optional[int] = None,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to patch rewards. This event has been logged.'
)
try:
this_reward = Reward.get_by_id(reward_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No reward found with id {reward_id}')
if name is not None:
@ -159,7 +143,6 @@ async def patch_reward(
if this_reward.save() == 1:
return_val = model_to_dict(this_reward)
db.close()
return return_val
else:
raise HTTPException(
@ -171,20 +154,17 @@ async def patch_reward(
@router.delete('/{reward_id}')
async def delete_reward(reward_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning('Bad Token: [REDACTED]')
raise HTTPException(
status_code=401,
detail='You are not authorized to delete rewards. This event has been logged.'
)
try:
this_reward = Reward.get_by_id(reward_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f'No reward found with id {reward_id}')
count = this_reward.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Reward {reward_id} has been deleted')

View File

@ -0,0 +1,91 @@
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException
from typing import Optional
import logging
import pydantic
from ..db_engine import ScoutClaim, ScoutOpportunity, model_to_dict
from ..dependencies import oauth2_scheme, valid_token
router = APIRouter(prefix="/api/v2/scout_claims", tags=["scout_claims"])
class ScoutClaimModel(pydantic.BaseModel):
scout_opportunity_id: int
card_id: int
claimed_by_team_id: int
@router.get("")
async def get_scout_claims(
scout_opportunity_id: Optional[int] = None, claimed_by_team_id: Optional[int] = None
):
query = ScoutClaim.select().order_by(ScoutClaim.id)
if scout_opportunity_id is not None:
query = query.where(ScoutClaim.scout_opportunity_id == scout_opportunity_id)
if claimed_by_team_id is not None:
query = query.where(ScoutClaim.claimed_by_team_id == claimed_by_team_id)
results = [model_to_dict(x, recurse=False) for x in query]
return {"count": len(results), "results": results}
@router.get("/{claim_id}")
async def get_one_scout_claim(claim_id: int):
try:
claim = ScoutClaim.get_by_id(claim_id)
except Exception:
raise HTTPException(
status_code=404, detail=f"No scout claim found with id {claim_id}"
)
return model_to_dict(claim)
@router.post("")
async def post_scout_claim(claim: ScoutClaimModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
raise HTTPException(
status_code=401,
detail="You are not authorized to post scout claims. This event has been logged.",
)
claim_data = claim.dict()
claim_data["created"] = int(datetime.timestamp(datetime.now()) * 1000)
this_claim = ScoutClaim(**claim_data)
saved = this_claim.save()
if saved == 1:
return model_to_dict(this_claim)
else:
raise HTTPException(status_code=418, detail="Could not save scout claim")
@router.delete("/{claim_id}")
async def delete_scout_claim(claim_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
raise HTTPException(
status_code=401,
detail="You are not authorized to delete scout claims. This event has been logged.",
)
try:
claim = ScoutClaim.get_by_id(claim_id)
except Exception:
raise HTTPException(
status_code=404, detail=f"No scout claim found with id {claim_id}"
)
count = claim.delete_instance()
if count == 1:
raise HTTPException(
status_code=200, detail=f"Scout claim {claim_id} has been deleted"
)
else:
raise HTTPException(
status_code=500, detail=f"Scout claim {claim_id} was not deleted"
)

View File

@ -0,0 +1,123 @@
import json
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException
from typing import Optional, List
import logging
import pydantic
from ..db_engine import ScoutOpportunity, ScoutClaim, model_to_dict, fn
from ..dependencies import oauth2_scheme, valid_token
router = APIRouter(prefix="/api/v2/scout_opportunities", tags=["scout_opportunities"])
class ScoutOpportunityModel(pydantic.BaseModel):
pack_id: Optional[int] = None
opener_team_id: int
card_ids: List[int]
expires_at: int
created: Optional[int] = None
def opportunity_to_dict(opp, recurse=True):
"""Convert a ScoutOpportunity to dict with card_ids deserialized."""
result = model_to_dict(opp, recurse=recurse)
if isinstance(result.get("card_ids"), str):
result["card_ids"] = json.loads(result["card_ids"])
return result
@router.get("")
async def get_scout_opportunities(
claimed: Optional[bool] = None,
expired_before: Optional[int] = None,
opener_team_id: Optional[int] = None,
):
query = ScoutOpportunity.select().order_by(ScoutOpportunity.id)
if opener_team_id is not None:
query = query.where(ScoutOpportunity.opener_team_id == opener_team_id)
if expired_before is not None:
query = query.where(ScoutOpportunity.expires_at < expired_before)
if claimed is not None:
# Check whether any scout_claims exist for each opportunity
claim_subquery = ScoutClaim.select(ScoutClaim.scout_opportunity)
if claimed:
query = query.where(ScoutOpportunity.id.in_(claim_subquery))
else:
query = query.where(ScoutOpportunity.id.not_in(claim_subquery))
results = [opportunity_to_dict(x, recurse=False) for x in query]
return {"count": len(results), "results": results}
@router.get("/{opportunity_id}")
async def get_one_scout_opportunity(opportunity_id: int):
try:
opp = ScoutOpportunity.get_by_id(opportunity_id)
except Exception:
raise HTTPException(
status_code=404,
detail=f"No scout opportunity found with id {opportunity_id}",
)
return opportunity_to_dict(opp)
@router.post("")
async def post_scout_opportunity(
opportunity: ScoutOpportunityModel, token: str = Depends(oauth2_scheme)
):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
raise HTTPException(
status_code=401,
detail="You are not authorized to post scout opportunities. This event has been logged.",
)
opp_data = opportunity.dict()
opp_data["card_ids"] = json.dumps(opp_data["card_ids"])
if opp_data["created"] is None:
opp_data["created"] = int(datetime.timestamp(datetime.now()) * 1000)
this_opp = ScoutOpportunity(**opp_data)
saved = this_opp.save()
if saved == 1:
return opportunity_to_dict(this_opp)
else:
raise HTTPException(status_code=418, detail="Could not save scout opportunity")
@router.delete("/{opportunity_id}")
async def delete_scout_opportunity(
opportunity_id: int, token: str = Depends(oauth2_scheme)
):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
raise HTTPException(
status_code=401,
detail="You are not authorized to delete scout opportunities. This event has been logged.",
)
try:
opp = ScoutOpportunity.get_by_id(opportunity_id)
except Exception:
raise HTTPException(
status_code=404,
detail=f"No scout opportunity found with id {opportunity_id}",
)
count = opp.delete_instance()
if count == 1:
raise HTTPException(
status_code=200,
detail=f"Scout opportunity {opportunity_id} has been deleted",
)
else:
raise HTTPException(
status_code=500,
detail=f"Scout opportunity {opportunity_id} was not deleted",
)

View File

@ -1,33 +1,20 @@
import csv
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Response, Query
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query
import logging
import pydantic
import pandas as pd
from ..db_engine import db, model_to_dict, fn, query_to_csv, complex_data_to_csv, Player, BattingCardRatings
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA, int_timestamp
from ..db_engine import Player
from ..dependencies import oauth2_scheme, valid_token
from ..player_scouting import get_player_ids
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/scouting',
tags=['scouting']
)
router = APIRouter(prefix="/api/v2/scouting", tags=["scouting"])
class BattingFiles(pydantic.BaseModel):
vl_basic: str = 'vl-basic.csv'
vl_rate: str = 'vl-rate.csv'
vr_basic: str = 'vr-basic.csv'
vr_rate: str = 'vr-rate.csv'
running: str = 'running.csv'
vl_basic: str = "vl-basic.csv"
vl_rate: str = "vl-rate.csv"
vr_basic: str = "vr-basic.csv"
vr_rate: str = "vr-rate.csv"
running: str = "running.csv"
# def csv_file_to_dataframe(filename: str) -> pd.DataFrame | None:
@ -37,66 +24,26 @@ class BattingFiles(pydantic.BaseModel):
# for row in reader:
@router.get('/playerkeys')
@router.get("/playerkeys")
async def get_player_keys(player_id: list = Query(default=None)):
all_keys = []
for x in player_id:
this_player = Player.get_or_none(Player.player_id == x)
if this_player is not None:
this_keys = get_player_ids(this_player.bbref_id, id_type='bbref')
this_keys = get_player_ids(this_player.bbref_id, id_type="bbref")
if this_keys is not None:
all_keys.append(this_keys)
return_val = {'count': len(all_keys), 'keys': [
dict(x) for x in all_keys
]}
db.close()
return_val = {"count": len(all_keys), "keys": [dict(x) for x in all_keys]}
return return_val
@router.post('/live-update/batting')
def live_update_batting(files: BattingFiles, cardset_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to initiate live updates.'
)
data = {} # <fg id>: { 'vL': [combined vl stat data], 'vR': [combined vr stat data] }
for row in files.vl_basic:
if row['pa'] >= 20:
data[row['fgid']]['vL'] = row
for row in files.vl_rate:
if row['fgid'] in data.keys():
data[row['fgid']]['vL'].extend(row)
for row in files.vr_basic:
if row['pa'] >= 40 and row['fgid'] in data.keys():
data[row['fgid']]['vR'] = row
for row in files.vr_rate:
if row['fgid'] in data.keys():
data[row['fgid']]['vR'].extend(row)
for x in data.items():
pass
# Create BattingCardRating object for vL
# Create BattingCardRating object for vR
# Read running stats and create/update BattingCard object
return files.dict()
@router.post('/live-update/pitching')
@router.post("/live-update/pitching")
def live_update_pitching(files: BattingFiles, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail='You are not authorized to initiate live updates.'
status_code=401, detail="You are not authorized to initiate live updates."
)
return files.dict()

View File

@ -1,18 +1,12 @@
from fastapi import APIRouter, Depends, HTTPException, Query, Response
from typing import Literal, Optional, List
from typing import Optional, List
import logging
import pandas as pd
import pydantic
from pydantic import validator
from ..db_engine import db, StratGame, model_to_dict, chunked, PitchingCard, Player, query_to_csv, Team, fn
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..db_engine import StratGame, model_to_dict, fn
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/games',
@ -83,7 +77,6 @@ async def get_games(
x['home_abbrev'] = x['home_team']['abbrev']
del x['away_team'], x['home_team']
db.close()
output = pd.DataFrame(return_vals)[[
'id', 'away_abbrev', 'home_abbrev', 'away_score', 'home_score', 'away_team_value', 'home_team_value',
'game_type', 'season', 'week', 'short_game', 'ranked'
@ -94,7 +87,6 @@ async def get_games(
return_val = {'count': all_games.count(), 'games': [
model_to_dict(x, recurse=not short_output) for x in all_games
]}
db.close()
return return_val
@ -102,11 +94,9 @@ async def get_games(
async def get_one_game(game_id: int):
this_game = StratGame.get_or_none(StratGame.id == game_id)
if not this_game:
db.close()
raise HTTPException(status_code=404, detail=f'StratGame ID {game_id} not found')
g_result = model_to_dict(this_game)
db.close()
return g_result
@ -115,12 +105,11 @@ async def patch_game(
game_id: int, game_type: Optional[str] = None, away_score: Optional[int] = None,
home_score: Optional[int] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_game - Bad Token: {token}')
logging.warning('patch_game - Bad Token: [REDACTED]')
raise HTTPException(status_code=401, detail='Unauthorized')
this_game = StratGame.get_or_none(StratGame.id == game_id)
if not this_game:
db.close()
raise HTTPException(status_code=404, detail=f'StratGame ID {game_id} not found')
if away_score is not None:
@ -132,17 +121,15 @@ async def patch_game(
if this_game.save() == 1:
g_result = model_to_dict(this_game)
db.close()
return g_result
else:
db.close()
raise HTTPException(status_code=500, detail=f'Unable to patch game {game_id}')
@router.post('')
async def post_game(this_game: GameModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'post_games - Bad Token: {token}')
logging.warning('post_games - Bad Token: [REDACTED]')
raise HTTPException(status_code=401, detail='Unauthorized')
this_game = StratGame(**this_game.dict())
@ -150,7 +137,6 @@ async def post_game(this_game: GameModel, token: str = Depends(oauth2_scheme)):
saved = this_game.save()
if saved == 1:
return_val = model_to_dict(this_game)
db.close()
return return_val
else:
raise HTTPException(
@ -162,16 +148,14 @@ async def post_game(this_game: GameModel, token: str = Depends(oauth2_scheme)):
@router.delete('/{game_id}')
async def delete_game(game_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'delete_game - Bad Token: {token}')
logging.warning('delete_game - Bad Token: [REDACTED]')
raise HTTPException(status_code=401, detail='Unauthorized')
this_game = StratGame.get_or_none(StratGame.id == game_id)
if not this_game:
db.close()
raise HTTPException(status_code=404, detail=f'StratGame ID {game_id} not found')
count = this_game.delete_instance()
db.close()
if count == 1:
return f'StratGame {game_id} has been deleted'

View File

@ -13,21 +13,14 @@ from ..db_engine import (
Team,
Player,
model_to_dict,
chunked,
fn,
SQL,
Case,
complex_data_to_csv,
Decision,
)
from ..db_helpers import upsert_strat_plays
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
from ..dependencies import oauth2_scheme, valid_token
logging.basicConfig(
filename=LOG_DATA["filename"],
format=LOG_DATA["format"],
level=LOG_DATA["log_level"],
)
router = APIRouter(prefix="/api/v2/plays", tags=["plays"])
@ -367,7 +360,6 @@ async def get_plays(
x["runner_team"],
)
db.close()
return Response(
content=pd.DataFrame(return_vals).to_csv(index=False), media_type="text/csv"
)
@ -376,7 +368,6 @@ async def get_plays(
"count": all_plays.count(),
"plays": [model_to_dict(x, recurse=not short_output) for x in all_plays],
}
db.close()
return return_plays
@ -808,12 +799,10 @@ async def get_batting_totals(
exclude = first + ["lob_all", "lob_all_rate", "lob_2outs", "rbi%"]
output = output[first + [col for col in output.columns if col not in exclude]]
db.close()
return Response(
content=pd.DataFrame(output).to_csv(index=False), media_type="text/csv"
)
db.close()
return return_stats
@ -1175,7 +1164,6 @@ async def get_pitching_totals(
"rbi%": rbi_rate,
}
)
db.close()
if csv:
return_vals = return_stats["stats"]
@ -1209,7 +1197,6 @@ async def get_pitching_totals(
exclude = first + ["lob_2outs", "rbi%"]
output = output[first + [col for col in output.columns if col not in exclude]]
db.close()
return Response(
content=pd.DataFrame(output).to_csv(index=False), media_type="text/csv"
)
@ -1227,7 +1214,6 @@ async def get_game_summary(
):
this_game = StratGame.get_or_none(StratGame.id == game_id)
if this_game is None:
db.close()
raise HTTPException(status_code=404, detail=f"Game {game_id} not found")
game_plays = StratPlay.select().where(StratPlay.game_id == game_id)
@ -1404,12 +1390,10 @@ async def get_game_summary(
@router.get("/{play_id}")
async def get_one_play(play_id: int):
if StratPlay.get_or_none(StratPlay.id == play_id) is None:
db.close()
play = StratPlay.get_or_none(StratPlay.id == play_id)
if play is None:
raise HTTPException(status_code=404, detail=f"Play ID {play_id} not found")
r_play = model_to_dict(StratPlay.get_by_id(play_id))
db.close()
return r_play
return model_to_dict(play)
@router.patch("/{play_id}")
@ -1417,23 +1401,21 @@ async def patch_play(
play_id: int, new_play: PlayModel, token: str = Depends(oauth2_scheme)
):
if not valid_token(token):
logging.warning(f"patch_play - Bad Token: {token}")
logging.warning("patch_play - Bad Token: [REDACTED]")
raise HTTPException(status_code=401, detail="Unauthorized")
if StratPlay.get_or_none(StratPlay.id == play_id) is None:
db.close()
raise HTTPException(status_code=404, detail=f"Play ID {play_id} not found")
StratPlay.update(**new_play.dict()).where(StratPlay.id == play_id).execute()
r_play = model_to_dict(StratPlay.get_by_id(play_id))
db.close()
return r_play
@router.post("")
async def post_plays(p_list: PlayList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"post_plays - Bad Token: {token}")
logging.warning("post_plays - Bad Token: [REDACTED]")
raise HTTPException(status_code=401, detail="Unauthorized")
new_plays = []
@ -1476,7 +1458,6 @@ async def post_plays(p_list: PlayList, token: str = Depends(oauth2_scheme)):
with db.atomic():
# Use PostgreSQL-compatible upsert helper
upsert_strat_plays(new_plays, batch_size=20)
db.close()
return f"Inserted {len(new_plays)} plays"
@ -1484,16 +1465,14 @@ async def post_plays(p_list: PlayList, token: str = Depends(oauth2_scheme)):
@router.delete("/{play_id}")
async def delete_play(play_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"delete_play - Bad Token: {token}")
logging.warning("delete_play - Bad Token: [REDACTED]")
raise HTTPException(status_code=401, detail="Unauthorized")
this_play = StratPlay.get_or_none(StratPlay.id == play_id)
if not this_play:
db.close()
raise HTTPException(status_code=404, detail=f"Play ID {play_id} not found")
count = this_play.delete_instance()
db.close()
if count == 1:
return f"Play {play_id} has been deleted"
@ -1506,16 +1485,14 @@ async def delete_play(play_id: int, token: str = Depends(oauth2_scheme)):
@router.delete("/game/{game_id}")
async def delete_plays_game(game_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"delete_plays_game - Bad Token: {token}")
logging.warning("delete_plays_game - Bad Token: [REDACTED]")
raise HTTPException(status_code=401, detail="Unauthorized")
this_game = StratGame.get_or_none(StratGame.id == game_id)
if not this_game:
db.close()
raise HTTPException(status_code=404, detail=f"Game ID {game_id} not found")
count = StratPlay.delete().where(StratPlay.game == this_game).execute()
db.close()
if count > 0:
return f"Deleted {count} plays matching Game ID {game_id}"

View File

@ -8,7 +8,7 @@ import logging
import pydantic
from pandas import DataFrame
from ..db_engine import (
from ..db_engine import (, DoesNotExist
db,
Team,
model_to_dict,
@ -31,20 +31,14 @@ from ..db_engine import (
PitchingCardRatings,
StratGame,
LIVE_PROMO_CARDSET_ID,
DoesNotExist,
)
from ..dependencies import (
oauth2_scheme,
valid_token,
LOG_DATA,
int_timestamp,
PRIVATE_IN_SCHEMA,
)
logging.basicConfig(
filename=LOG_DATA["filename"],
format=LOG_DATA["format"],
level=LOG_DATA["log_level"],
)
router = APIRouter(prefix="/api/v2/teams", tags=["teams"])
@ -139,9 +133,6 @@ async def get_teams(
if ranking_max is not None:
all_teams = all_teams.where(Team.ranking <= ranking_max)
if ranking_max is not None:
all_teams = all_teams.where(Team.ranking <= ranking_max)
if has_guide is not None:
# Use boolean comparison (PostgreSQL-compatible)
if not has_guide:
@ -150,7 +141,10 @@ async def get_teams(
all_teams = all_teams.where(Team.has_guide == True)
if is_ai is not None:
all_teams = all_teams.where(Team.is_ai)
if not is_ai:
all_teams = all_teams.where(Team.is_ai == False)
else:
all_teams = all_teams.where(Team.is_ai == True)
if event_id is not None:
all_teams = all_teams.where(Team.event_id == event_id)
@ -163,7 +157,6 @@ async def get_teams(
if csv:
return_val = query_to_csv(all_teams, exclude=[Team.career])
db.close()
return Response(content=return_val, media_type="text/csv")
else:
@ -171,16 +164,14 @@ async def get_teams(
for x in all_teams:
return_teams["teams"].append(model_to_dict(x))
db.close()
return return_teams
@router.get("/{team_id}")
async def get_one_team(team_id, inc_packs: bool = True, csv: Optional[bool] = False):
async def get_one_team(team_id: int, inc_packs: bool = True, csv: Optional[bool] = False):
try:
this_team = Team.get_by_id(team_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f"No team found with id {team_id}")
p_query = Pack.select().where(
@ -195,7 +186,6 @@ async def get_one_team(team_id, inc_packs: bool = True, csv: Optional[bool] = Fa
if inc_packs:
return_val["sealed_packs"] = [model_to_dict(x) for x in p_query]
db.close()
return return_val
@ -284,8 +274,6 @@ def get_scouting_dfs(allowed_players, position: str):
)
)
db.close()
def get_total_ops(df_data):
ops_vl = df_data["obp_vl"] + df_data["slg_vl"]
ops_vr = df_data["obp_vr"] + df_data["slg_vr"]
@ -311,11 +299,9 @@ async def get_team_lineup(
"""
this_team = Team.get_or_none(Team.id == team_id)
if this_team is None:
db.close()
raise HTTPException(status_code=404, detail=f"Team id {team_id} not found")
if difficulty_name not in CARDSETS.keys() and difficulty_name != "exhibition":
db.close()
raise HTTPException(
status_code=400,
detail=f"Difficulty name {difficulty_name} not a valid check",
@ -329,7 +315,6 @@ async def get_team_lineup(
if difficulty_name == "exhibition":
logging.info(f"pulling an exhibition lineup")
if cardset_id is None:
db.close()
raise HTTPException(
status_code=400,
detail=f"Must provide at least one cardset_id for exhibition lineups",
@ -370,17 +355,35 @@ async def get_team_lineup(
"DH": {"player": None, "vl": None, "vr": None, "ops": 0},
}
# Batch-fetch BattingCards and ratings for all candidate players to avoid
# per-player DB round trips inside the lineup construction loop below.
if backup_players is not None:
_batch_bcards = BattingCard.select().where(
(BattingCard.player << legal_players)
| (BattingCard.player << backup_players)
)
else:
_batch_bcards = BattingCard.select().where(BattingCard.player << legal_players)
_batting_cards_by_player = {bc.player_id: bc for bc in _batch_bcards}
_all_bratings = (
BattingCardRatings.select().where(
BattingCardRatings.battingcard << list(_batting_cards_by_player.values())
)
if _batting_cards_by_player
else []
)
_ratings_by_card_hand = {}
for _r in _all_bratings:
_ratings_by_card_hand.setdefault(_r.battingcard_id, {})[_r.vs_hand] = _r
def get_bratings(player_id):
this_bcard = BattingCard.get_or_none(BattingCard.player_id == player_id)
vl_ratings = BattingCardRatings.get_or_none(
BattingCardRatings.battingcard == this_bcard,
BattingCardRatings.vs_hand == "L",
this_bcard = _batting_cards_by_player.get(player_id)
card_ratings = (
_ratings_by_card_hand.get(this_bcard.id, {}) if this_bcard else {}
)
vl_ratings = card_ratings.get("L")
vr_ratings = card_ratings.get("R")
vl_ops = vl_ratings.obp + vl_ratings.slg
vr_ratings = BattingCardRatings.get_or_none(
BattingCardRatings.battingcard == this_bcard,
BattingCardRatings.vs_hand == "R",
)
vr_ops = vr_ratings.obp + vr_ratings.slg
return (
model_to_dict(vl_ratings),
@ -596,16 +599,21 @@ def sort_pitchers(pitching_card_query) -> DataFrame | None:
pitcher_df = pd.DataFrame(all_s).set_index("player", drop=False)
logging.debug(f"pitcher_df: {pitcher_df}")
def get_total_ops(df_data):
vlval = PitchingCardRatings.get_or_none(
PitchingCardRatings.pitchingcard_id == df_data["id"],
PitchingCardRatings.vs_hand == "L",
)
vrval = PitchingCardRatings.get_or_none(
PitchingCardRatings.pitchingcard_id == df_data["id"],
PitchingCardRatings.vs_hand == "R",
card_ids = pitcher_df["id"].tolist()
ratings_map = {
(r.pitchingcard_id, r.vs_hand): r
for r in PitchingCardRatings.select().where(
(PitchingCardRatings.pitchingcard_id << card_ids)
& (PitchingCardRatings.vs_hand << ["L", "R"])
)
}
def get_total_ops(df_data):
vlval = ratings_map.get((df_data["id"], "L"))
vrval = ratings_map.get((df_data["id"], "R"))
if vlval is None or vrval is None:
return float("inf")
ops_vl = vlval.obp + vlval.slg
ops_vr = vrval.obp + vrval.slg
# TODO: should this be max??
@ -628,11 +636,9 @@ async def get_team_sp(
)
this_team = Team.get_or_none(Team.id == team_id)
if this_team is None:
db.close()
raise HTTPException(status_code=404, detail=f"Team id {team_id} not found")
if difficulty_name not in CARDSETS.keys() and difficulty_name != "exhibition":
db.close()
raise HTTPException(
status_code=400,
detail=f"Difficulty name {difficulty_name} not a valid check",
@ -643,7 +649,6 @@ async def get_team_sp(
if difficulty_name == "exhibition":
logging.info(f"pulling an exhibition lineup")
if cardset_id is None:
db.close()
raise HTTPException(
status_code=400,
detail=f"Must provide at least one cardset_id for exhibition lineups",
@ -676,16 +681,21 @@ async def get_team_sp(
starter_df = pd.DataFrame(all_s).set_index("player", drop=False)
logging.debug(f"starter_df: {starter_df}")
def get_total_ops(df_data):
vlval = PitchingCardRatings.get_or_none(
PitchingCardRatings.pitchingcard_id == df_data["id"],
PitchingCardRatings.vs_hand == "L",
)
vrval = PitchingCardRatings.get_or_none(
PitchingCardRatings.pitchingcard_id == df_data["id"],
PitchingCardRatings.vs_hand == "R",
card_ids = starter_df["id"].tolist()
ratings_map = {
(r.pitchingcard_id, r.vs_hand): r
for r in PitchingCardRatings.select().where(
(PitchingCardRatings.pitchingcard_id << card_ids)
& (PitchingCardRatings.vs_hand << ["L", "R"])
)
}
def get_total_ops(df_data):
vlval = ratings_map.get((df_data["id"], "L"))
vrval = ratings_map.get((df_data["id"], "R"))
if vlval is None or vrval is None:
return float("inf")
ops_vl = vlval.obp + vlval.slg
ops_vr = vrval.obp + vrval.slg
return (ops_vr + ops_vl + min(ops_vl, ops_vr)) / 3
@ -707,13 +717,11 @@ async def get_team_sp(
if all_starters is not None and len(all_starters.index) >= sp_rank:
this_player_id = all_starters.iloc[sp_rank - 1].player
this_player = model_to_dict(Player.get_by_id(this_player_id), recurse=False)
db.close()
return this_player
if all_starters is not None and len(all_starters.index) > 0:
this_player_id = all_starters.iloc[len(all_starters.index) - 1].player
this_player = model_to_dict(Player.get_by_id(this_player_id), recurse=False)
db.close()
return this_player
# Include backup cardsets
@ -726,13 +734,11 @@ async def get_team_sp(
if all_starters is not None and len(all_starters.index) >= sp_rank:
this_player_id = all_starters.iloc[sp_rank - 1].player
this_player = model_to_dict(Player.get_by_id(this_player_id), recurse=False)
db.close()
return this_player
if all_starters is not None and len(all_starters.index) > 0:
this_player_id = all_starters.iloc[len(all_starters.index) - 1].player
this_player = model_to_dict(Player.get_by_id(this_player_id), recurse=False)
db.close()
return this_player
raise HTTPException(
@ -755,11 +761,9 @@ async def get_team_rp(
)
this_team = Team.get_or_none(Team.id == team_id)
if this_team is None:
db.close()
raise HTTPException(status_code=404, detail=f"Team id {team_id} not found")
if difficulty_name not in CARDSETS.keys() and difficulty_name != "exhibition":
db.close()
raise HTTPException(
status_code=400,
detail=f"Difficulty name {difficulty_name} not a valid check",
@ -773,7 +777,6 @@ async def get_team_rp(
if difficulty_name == "exhibition":
logging.info(f"pulling an exhibition RP")
if cardset_id is None:
db.close()
raise HTTPException(
status_code=400,
detail=f"Must provide at least one cardset_id for exhibition lineups",
@ -845,7 +848,6 @@ async def get_team_rp(
this_player = model_to_dict(
Player.get_by_id(this_player_id), recurse=False
)
db.close()
return this_player
elif need == "setup":
@ -870,7 +872,6 @@ async def get_team_rp(
this_player = model_to_dict(
Player.get_by_id(this_player_id), recurse=False
)
db.close()
return this_player
elif need == "length" or len(used_pitcher_ids) > 4:
@ -904,7 +905,6 @@ async def get_team_rp(
this_player = model_to_dict(
Player.get_by_id(this_player_id), recurse=False
)
db.close()
return this_player
elif need == "middle":
@ -929,7 +929,6 @@ async def get_team_rp(
this_player = model_to_dict(
Player.get_by_id(this_player_id), recurse=False
)
db.close()
return this_player
logging.info(f"Falling to last chance pitcher")
@ -945,7 +944,6 @@ async def get_team_rp(
if all_relievers is not None:
this_player_id = all_relievers.iloc[len(all_relievers.index) - 1].player
this_player = model_to_dict(Player.get_by_id(this_player_id), recurse=False)
db.close()
return this_player
raise HTTPException(status_code=400, detail=f"No RP found for Team {team_id}")
@ -1031,7 +1029,6 @@ async def get_team_record(team_id: int, season: int):
# team_games = lg_query.where((StratGame.away_team_id == x) | (StratGame.home_team_id == x))
# for game in team_games:
db.close()
return standings
@ -1039,13 +1036,11 @@ async def get_team_record(team_id: int, season: int):
async def team_buy_players(team_id: int, ids: str, ts: str):
try:
this_team = Team.get_by_id(team_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f"No team found with id {team_id}")
if ts != this_team.team_hash():
logging.warning(f"Bad Team Secret: {ts} ({this_team.team_hash()})")
db.close()
raise HTTPException(
status_code=401,
detail=f"You are not authorized to buy {this_team.abbrev} cards. This event has been logged.",
@ -1061,8 +1056,7 @@ async def team_buy_players(team_id: int, ids: str, ts: str):
if player_id != "":
try:
this_player = Player.get_by_id(player_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(
status_code=404,
detail=f"No player found with id {player_id} /// "
@ -1075,7 +1069,6 @@ async def team_buy_players(team_id: int, ids: str, ts: str):
f"{this_player} was not purchased. {this_team.lname} only has {this_team.wallet}₼, but "
f"{this_player} costs {this_player.cost}₼."
)
db.close()
raise HTTPException(
200,
detail=f"{this_player} was not purchased. {this_team.lname} only has {this_team.wallet}₼, but "
@ -1131,21 +1124,18 @@ async def team_buy_packs(
):
try:
this_packtype = PackType.get_by_id(packtype_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(
status_code=404, detail=f"No pack type found with id {packtype_id}"
)
try:
this_team = Team.get_by_id(team_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f"No team found with id {team_id}")
if ts != this_team.team_hash():
logging.warning(f"Bad Team Secret: {ts} ({this_team.team_hash()})")
db.close()
logging.warning(
f"team: {this_team} / pack_type: {this_packtype} / secret: {ts} / "
f"actual: {this_team.team_hash()}"
@ -1158,7 +1148,6 @@ async def team_buy_packs(
# check wallet balance
total_cost = this_packtype.cost * quantity
if this_team.wallet < total_cost:
db.close()
raise HTTPException(
200,
detail=f"{this_packtype} was not purchased. {this_team.lname} only has {this_team.wallet} bucks, but "
@ -1186,7 +1175,6 @@ async def team_buy_packs(
with db.atomic():
Pack.bulk_create(all_packs, batch_size=15)
db.close()
raise HTTPException(
status_code=200,
@ -1199,13 +1187,11 @@ async def team_buy_packs(
async def team_sell_cards(team_id: int, ids: str, ts: str):
try:
this_team = Team.get_by_id(team_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f"No team found with id {team_id}")
if ts != this_team.team_hash():
logging.warning(f"Bad Team Secret: {ts} ({this_team.team_hash()})")
db.close()
raise HTTPException(
status_code=401,
detail=f"You are not authorized to sell {this_team.abbrev} cards. This event has been logged.",
@ -1219,8 +1205,7 @@ async def team_sell_cards(team_id: int, ids: str, ts: str):
if card_id != "":
try:
this_card = Card.get_by_id(card_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(
status_code=404, detail=f"No card found with id {card_id}"
)
@ -1288,12 +1273,10 @@ async def get_team_cards(team_id, csv: Optional[bool] = True):
"""
try:
this_team = Team.get_by_id(team_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f"No team found with id {team_id}")
if not csv:
db.close()
raise HTTPException(
status_code=400,
detail="The /teams/{team_id}/cards endpoint only supports csv output.",
@ -1307,11 +1290,9 @@ async def get_team_cards(team_id, csv: Optional[bool] = True):
.order_by(-Card.player.rarity.value, Card.player.p_name)
)
if all_cards.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f"No cards found")
card_vals = [model_to_dict(x) for x in all_cards]
db.close()
for x in card_vals:
x.update(x["player"])
@ -1355,8 +1336,7 @@ async def get_team_cards(team_id, csv: Optional[bool] = True):
@router.post("", include_in_schema=PRIVATE_IN_SCHEMA)
async def post_team(team: TeamModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to post teams. This event has been logged.",
@ -1364,7 +1344,6 @@ async def post_team(team: TeamModel, token: str = Depends(oauth2_scheme)):
dupe_team = Team.get_or_none(Team.season == team.season, Team.abbrev == team.abbrev)
if dupe_team:
db.close()
raise HTTPException(
status_code=400,
detail=f"There is already a season {team.season} team using {team.abbrev}",
@ -1392,7 +1371,6 @@ async def post_team(team: TeamModel, token: str = Depends(oauth2_scheme)):
saved = this_team.save()
if saved == 1:
return_team = model_to_dict(this_team)
db.close()
return return_team
else:
raise HTTPException(
@ -1404,8 +1382,7 @@ async def post_team(team: TeamModel, token: str = Depends(oauth2_scheme)):
@router.post("/new-season/{new_season}", include_in_schema=PRIVATE_IN_SCHEMA)
async def team_season_update(new_season: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to post teams. This event has been logged.",
@ -1417,7 +1394,6 @@ async def team_season_update(new_season: int, token: str = Depends(oauth2_scheme
current = Current.latest()
current.season = new_season
current.save()
db.close()
return {
"detail": f"Team rankings, season, guides, and wallets updated for season {new_season}"
@ -1429,8 +1405,7 @@ async def team_update_money(
team_id: int, delta: int, token: str = Depends(oauth2_scheme)
):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to adjust wallets. This event has been logged.",
@ -1438,15 +1413,13 @@ async def team_update_money(
try:
this_team = Team.get_by_id(team_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f"No team found with id {team_id}")
this_team.wallet += delta
if this_team.save() == 1:
return_team = model_to_dict(this_team)
db.close()
return return_team
else:
raise HTTPException(
@ -1477,16 +1450,14 @@ async def patch_team(
abbrev: Optional[str] = None,
):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to delete teams. This event has been logged.",
)
try:
this_team = Team.get_by_id(team_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f"No team found with id {team_id}")
if abbrev is not None:
@ -1530,7 +1501,6 @@ async def patch_team(
if this_team.save() == 1:
return_team = model_to_dict(this_team)
db.close()
return return_team
else:
raise HTTPException(
@ -1542,20 +1512,17 @@ async def patch_team(
@router.delete("/{team_id}", include_in_schema=PRIVATE_IN_SCHEMA)
async def delete_team(team_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f"Bad Token: {token}")
db.close()
logging.warning("Bad Token: [REDACTED]")
raise HTTPException(
status_code=401,
detail="You are not authorized to delete teams. This event has been logged.",
)
try:
this_team = Team.get_by_id(team_id)
except Exception:
db.close()
except DoesNotExist:
raise HTTPException(status_code=404, detail=f"No team found with id {team_id}")
count = this_team.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f"Team {team_id} has been deleted")

View File

@ -0,0 +1,57 @@
-- Migration: Add scout_opportunity and scout_claim tables
-- Date: 2026-03-04
-- Issue: #44
-- Purpose: Support the scouting feature where players can scout cards
-- from other teams' opened packs within a 30-minute window.
--
-- Run on dev first, verify with:
-- SELECT count(*) FROM scout_opportunity;
-- SELECT count(*) FROM scout_claim;
--
-- Rollback: See DROP statements at bottom of file
-- ============================================
-- FORWARD MIGRATION
-- ============================================
BEGIN;
CREATE TABLE IF NOT EXISTS scout_opportunity (
id SERIAL PRIMARY KEY,
pack_id INTEGER REFERENCES pack(id) ON DELETE SET NULL,
opener_team_id INTEGER NOT NULL REFERENCES team(id) ON DELETE CASCADE,
card_ids VARCHAR(255) NOT NULL, -- JSON array of card IDs, e.g. "[10, 11, 12]"
expires_at BIGINT NOT NULL, -- Unix ms timestamp, 30 min after creation
created BIGINT NOT NULL -- Unix ms timestamp
);
CREATE TABLE IF NOT EXISTS scout_claim (
id SERIAL PRIMARY KEY,
scout_opportunity_id INTEGER NOT NULL REFERENCES scout_opportunity(id) ON DELETE CASCADE,
card_id INTEGER NOT NULL REFERENCES card(id) ON DELETE CASCADE,
claimed_by_team_id INTEGER NOT NULL REFERENCES team(id) ON DELETE CASCADE,
created BIGINT NOT NULL -- Unix ms timestamp, auto-set on creation
);
-- Unique constraint: one claim per team per opportunity
CREATE UNIQUE INDEX IF NOT EXISTS scout_claim_opportunity_team_uniq
ON scout_claim (scout_opportunity_id, claimed_by_team_id);
-- Index for the common query: find unclaimed, expired opportunities
CREATE INDEX IF NOT EXISTS scout_opportunity_expires_at_idx
ON scout_opportunity (expires_at);
COMMIT;
-- ============================================
-- VERIFICATION QUERIES
-- ============================================
-- \d scout_opportunity
-- \d scout_claim
-- SELECT indexname FROM pg_indexes WHERE tablename IN ('scout_opportunity', 'scout_claim');
-- ============================================
-- ROLLBACK (if needed)
-- ============================================
-- DROP TABLE IF EXISTS scout_claim CASCADE;
-- DROP TABLE IF EXISTS scout_opportunity CASCADE;

View File

@ -0,0 +1,65 @@
"""
Migration: Replace 26 FK columns on Roster with RosterSlot junction table.
Creates the `rosterslot` table and migrates existing lineup data from the
card_1..card_26 columns. Safe to re-run (skips rosters already migrated).
Usage:
python migrations/migrate_roster_junction_table.py
"""
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
from app.db_engine import db, Roster, RosterSlot
SLOTS = 26
def migrate():
db.connect(reuse_if_open=True)
# Create the table if it doesn't exist yet
db.create_tables([RosterSlot], safe=True)
# Read raw rows from the old schema via plain SQL so we don't depend on
# the ORM model knowing about the legacy card_N columns.
cursor = db.execute_sql("SELECT * FROM roster")
columns = [desc[0] for desc in cursor.description]
migrated = 0
skipped = 0
with db.atomic():
for row in cursor.fetchall():
row_dict = dict(zip(columns, row))
roster_id = row_dict["id"]
already_migrated = (
RosterSlot.select().where(RosterSlot.roster == roster_id).exists()
)
if already_migrated:
skipped += 1
continue
slots_to_insert = []
for slot_num in range(1, SLOTS + 1):
col = f"card_{slot_num}_id"
card_id = row_dict.get(col)
if card_id is not None:
slots_to_insert.append(
{"roster": roster_id, "slot": slot_num, "card": card_id}
)
if slots_to_insert:
RosterSlot.insert_many(slots_to_insert).execute()
migrated += 1
print(f"Migration complete: {migrated} rosters migrated, {skipped} already done.")
db.close()
if __name__ == "__main__":
migrate()