- Add db_helpers.py with cross-database upsert functions for SQLite/PostgreSQL - Replace 12 on_conflict_replace() calls with PostgreSQL-compatible upserts - Add unique indexes: StratPlay(game, play_num), Decision(game, pitcher) - Add max_length to Team model fields (abbrev, sname, lname) - Fix boolean comparison in teams.py (== 0/1 to == False/True) - Create migrate_to_postgres.py with ID-preserving migration logic - Create audit_sqlite.py for pre-migration data integrity checks - Add PROJECT_PLAN.json for migration tracking - Add .secrets/ to .gitignore for credentials Audit results: 658,963 records across 29 tables, 2,390 orphaned stats (expected) Based on Major Domo migration lessons learned (33 issues resolved there)
151 lines
4.6 KiB
Python
151 lines
4.6 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from typing import Optional, List
|
|
import logging
|
|
import pydantic
|
|
|
|
from ..db_engine import db, GauntletReward, model_to_dict, chunked, DatabaseError
|
|
from ..db_helpers import upsert_gauntlet_rewards
|
|
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
|
|
|
|
logging.basicConfig(
|
|
filename=LOG_DATA["filename"],
|
|
format=LOG_DATA["format"],
|
|
level=LOG_DATA["log_level"],
|
|
)
|
|
|
|
router = APIRouter(prefix="/api/v2/gauntletrewards", tags=["gauntletrewards"])
|
|
|
|
|
|
class GauntletRewardModel(pydantic.BaseModel):
|
|
name: str
|
|
gauntlet_id: Optional[int] = 0
|
|
reward_id: Optional[int] = 0
|
|
win_num: Optional[int] = 0
|
|
loss_max: Optional[int] = 1
|
|
|
|
|
|
class GauntletRewardList(pydantic.BaseModel):
|
|
rewards: List[GauntletRewardModel]
|
|
|
|
|
|
@router.get("")
|
|
async def v1_gauntletreward_get(
|
|
name: Optional[str] = None,
|
|
gauntlet_id: Optional[int] = None,
|
|
reward_id: list = Query(default=None),
|
|
win_num: Optional[int] = None,
|
|
loss_max: Optional[int] = None,
|
|
):
|
|
all_rewards = GauntletReward.select()
|
|
|
|
if name is not None:
|
|
all_rewards = all_rewards.where(GauntletReward.name == name)
|
|
if gauntlet_id is not None:
|
|
all_rewards = all_rewards.where(GauntletReward.gauntlet_id == gauntlet_id)
|
|
if reward_id is not None:
|
|
all_rewards = all_rewards.where(GauntletReward.reward_id << reward_id)
|
|
if win_num is not None:
|
|
all_rewards = all_rewards.where(GauntletReward.win_num == win_num)
|
|
if loss_max is not None:
|
|
all_rewards = all_rewards.where(GauntletReward.loss_max >= loss_max)
|
|
|
|
all_rewards = all_rewards.order_by(-GauntletReward.loss_max, GauntletReward.win_num)
|
|
|
|
return_val = {"count": all_rewards.count(), "rewards": []}
|
|
for x in all_rewards:
|
|
return_val["rewards"].append(model_to_dict(x))
|
|
|
|
db.close()
|
|
return return_val
|
|
|
|
|
|
@router.get("/{gauntletreward_id}")
|
|
async def v1_gauntletreward_get_one(gauntletreward_id):
|
|
try:
|
|
this_reward = GauntletReward.get_by_id(gauntletreward_id)
|
|
except Exception:
|
|
db.close()
|
|
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
|
|
|
|
|
|
@router.patch("/{gauntletreward_id}")
|
|
async def v1_gauntletreward_patch(
|
|
gauntletreward_id,
|
|
name: Optional[str] = None,
|
|
gauntlet_id: Optional[int] = None,
|
|
reward_id: Optional[int] = None,
|
|
win_num: Optional[int] = None,
|
|
loss_max: Optional[int] = None,
|
|
token: str = Depends(oauth2_scheme),
|
|
):
|
|
if not valid_token(token):
|
|
logging.warning(f"Bad Token: {token}")
|
|
db.close()
|
|
raise HTTPException(
|
|
status_code=401,
|
|
detail="You are not authorized to patch gauntlet rewards. This event has been logged.",
|
|
)
|
|
|
|
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:
|
|
this_reward.gauntlet_id = gauntlet_id
|
|
if reward_id is not None:
|
|
this_reward.reward_id = reward_id
|
|
if win_num is not None:
|
|
this_reward.win_num = win_num
|
|
if loss_max is not None:
|
|
this_reward.loss_max = loss_max
|
|
if name is not None:
|
|
this_reward.name = name
|
|
|
|
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}")
|
|
|
|
|
|
@router.post("")
|
|
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()
|
|
raise HTTPException(
|
|
status_code=401,
|
|
detail="You are not authorized to post gauntlets. This event has been logged.",
|
|
)
|
|
|
|
all_rewards = []
|
|
for x in gauntletreward.rewards:
|
|
all_rewards.append(x.dict())
|
|
|
|
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"
|
|
|
|
|
|
@router.delete("/{gauntletreward_id}")
|
|
async def v1_gauntletreward_delete(gauntletreward_id):
|
|
if GauntletReward.delete_by_id(gauntletreward_id) == 1:
|
|
return f"Deleted gauntlet reward ID {gauntletreward_id}"
|
|
|
|
raise DatabaseError(f"Unable to delete gauntlet run {gauntletreward_id}")
|