From 3d4e5280e0f22e5a0506755310da88f2105175a9 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Thu, 5 Feb 2026 13:34:09 -0600 Subject: [PATCH] Remove deprecated legacy files and reorganize documentation - Delete deprecated root main.py (superseded by app/main.py) - Delete deprecated root db_engine.py (superseded by app/db_engine.py) - Delete legacy sheets.py (Google Sheets integration, unused) - Archive db_migrations.py and SQL migrations to .claude/archive/ - Archive POSTGRES_MIGRATION_PLAN.md to .claude/archive/ - Move active documentation to docs/ directory This cleanup removes ~10,000 lines of deprecated code and organizes remaining documentation for better maintainability. Co-Authored-By: Claude Sonnet 4.5 --- POSTGRES_MIGRATION_PLAN.md | 1050 --- db_engine.py | 898 --- db_migrations.py | 50 - .../DOCKER_QUICKSTART.md | 0 .../MIGRATION_PROGRESS.md | 0 PROJECT_PLAN.json => docs/PROJECT_PLAN.json | 0 QUICK_START.md => docs/QUICK_START.md | 0 main.py | 6363 ----------------- migrations/2026-01-07_normalize_franchise.sql | 104 - sheets.py | 156 - 10 files changed, 8621 deletions(-) delete mode 100644 POSTGRES_MIGRATION_PLAN.md delete mode 100644 db_engine.py delete mode 100644 db_migrations.py rename DOCKER_QUICKSTART.md => docs/DOCKER_QUICKSTART.md (100%) rename MIGRATION_PROGRESS.md => docs/MIGRATION_PROGRESS.md (100%) rename PROJECT_PLAN.json => docs/PROJECT_PLAN.json (100%) rename QUICK_START.md => docs/QUICK_START.md (100%) delete mode 100644 main.py delete mode 100644 migrations/2026-01-07_normalize_franchise.sql delete mode 100644 sheets.py diff --git a/POSTGRES_MIGRATION_PLAN.md b/POSTGRES_MIGRATION_PLAN.md deleted file mode 100644 index a82652b..0000000 --- a/POSTGRES_MIGRATION_PLAN.md +++ /dev/null @@ -1,1050 +0,0 @@ -# PostgreSQL Migration Plan for Paper Dynasty Database - -**Created:** 2025-11-07 -**Status:** Planning Phase -**Estimated Effort:** 3-5 days -**Risk Level:** Medium - -## Table of Contents -1. [Executive Summary](#executive-summary) -2. [Migration Context](#migration-context) -3. [Critical Issues Identified](#critical-issues-identified) -4. [Detailed Code Changes Required](#detailed-code-changes-required) -5. [Migration Strategy](#migration-strategy) -6. [Testing Plan](#testing-plan) -7. [Rollback Plan](#rollback-plan) -8. [Timeline](#timeline) - ---- - -## Executive Summary - -### Current State -- **Database:** SQLite 3.x with WAL mode -- **Database File:** `storage/pd_master.db` (~104 MB) -- **ORM:** Peewee 3.x -- **Models:** 40 database models -- **API Endpoints:** 30+ routers in `app/routers_v2/` - -### Target State -- **Database:** PostgreSQL 14+ -- **Connection Pooling:** PooledPostgresqlDatabase -- **Models:** Same 40 models with explicit table names -- **API Endpoints:** Updated for PostgreSQL compatibility - -### Why Migrate? -Based on sister project (Major Domo) experience: -- Better concurrency and performance under load -- Production-ready transaction handling -- Better data integrity constraints -- Scalability for future growth - -### Key Metrics -- **Files Requiring Changes:** 7 must-modify + 15 test-thoroughly -- **Models Missing `table_name`:** 40/40 (100%) -- **GROUP BY Issues:** ~30 instances in 2 files -- **SQLite-Specific Code:** 3 critical instances - ---- - -## Migration Context - -### Sister Project Lessons Learned - -The Major Domo project (SBA database) successfully migrated from SQLite to PostgreSQL in August 2025. Key findings: - -#### What Went Well -✅ **74.6% immediate success rate** on endpoint testing -✅ **Zero data loss** during migration -✅ **All failures were environment/state differences, not migration bugs** -✅ **Peewee ORM handled most SQL differences automatically** - -#### Critical Issues Encountered -⚠️ **PostgreSQL GROUP BY strictness** - All non-aggregated SELECT fields must be in GROUP BY -⚠️ **Table naming** - Models must explicitly define `Meta.table_name` -⚠️ **Connection pooling** - Required configuration changes - -#### Code Changes Required (Major Domo) -- Fixed ~20 GROUP BY clauses in stratplay.py -- Added `table_name` to all models -- Changed from `SqliteDatabase` to `PooledPostgresqlDatabase` -- Updated migrator from `SqliteMigrator` to `PostgresqlMigrator` - ---- - -## Critical Issues Identified - -### 1. GROUP BY Queries (BLOCKING - Must Fix) - -**PostgreSQL Requirement:** All non-aggregated SELECT fields MUST appear in GROUP BY clause - -**Location:** `app/routers_v2/stratplays.py` - -#### Issue 1: Batting Totals Query (Lines 342-456) - -**Current Code:** -```python -bat_plays = ( - StratPlay - .select( - StratPlay.batter, # Field 1 - not aggregated - StratPlay.game, # Field 2 - not aggregated - fn.SUM(StratPlay.pa).alias('sum_pa'), # Aggregate - # ... many more aggregates ... - StratPlay.batter_team, # Field 3 - not aggregated - ) - .where(...) -) - -# Later, conditionally group by: -if group_by == 'player': - bat_plays = bat_plays.group_by(StratPlay.batter) # ONLY batter! -``` - -**Problem:** When `group_by='player'`, PostgreSQL will reject this because: -- SELECT includes: `StratPlay.batter`, `StratPlay.game`, `StratPlay.batter_team` -- GROUP BY only includes: `StratPlay.batter` -- `StratPlay.game` and `StratPlay.batter_team` are neither aggregated nor grouped - -**Sister Project Solution:** Conditionally build SELECT fields based on `group_by` parameter - -**Example Fix Pattern (from Major Domo):** -```python -# Build SELECT fields conditionally based on group_by -base_select_fields = [ - fn.SUM(StratPlay.pa).alias('sum_pa'), - fn.SUM(StratPlay.ab).alias('sum_ab'), - # ... all aggregates ... -] - -# Add non-aggregated fields based on grouping type -if group_by in ['player', 'playerteam', 'playergame']: - base_select_fields.insert(0, StratPlay.batter) -if group_by in ['team', 'playerteam', 'teamgame']: - base_select_fields.append(StratPlay.batter_team) -if group_by in ['playergame', 'teamgame']: - base_select_fields.append(StratPlay.game) - -bat_plays = StratPlay.select(*base_select_fields).where(...) -``` - -**Affected Functions:** -- `get_batting_totals()` - Lines 342-456 (bat_plays, run_plays) -- `get_pitching_totals()` - Lines 645-733 (pit_plays) -- `get_game_summary()` - Lines 911-929 (all_batters, all_pitchers) - -**Estimated Effort:** 4-6 hours (complex logic, must test all group_by modes) - ---- - -### 2. Database Models Missing table_name (BLOCKING - Must Fix) - -**PostgreSQL Best Practice:** Explicitly define table names to avoid Peewee naming inconsistencies - -**Location:** `app/db_engine.py` - -**All 40 Models Affected:** -``` -Current, Rarity, Event, Cardset, MlbPlayer, Player, Team, PackType, Pack, -Card, Roster, Result, BattingStat, PitchingStat, Award, Paperdex, -Reward, GameRewards, Notification, GauntletReward, GauntletRun, -BattingCard, BattingCardRatings, PitchingCard, PitchingCardRatings, -CardPosition, StratGame, StratPlay, Decision, Scouting, ... -``` - -**Required Change (for EACH model):** -```python -class Current(BaseModel): - season = IntegerField() - week = IntegerField(default=0) - # ... other fields ... - - class Meta: - database = db - table_name = 'current' # ADD THIS LINE -``` - -**Estimated Effort:** 1-2 hours (repetitive, low risk) - ---- - -### 3. Database Connection Configuration (BLOCKING - Must Fix) - -**Location:** `app/db_engine.py` (Lines 11-18) - -**Current Code:** -```python -db = SqliteDatabase( - 'storage/pd_master.db', - pragmas={ - 'journal_mode': 'wal', - 'cache_size': -1 * 64000, - 'synchronous': 0 - } -) -``` - -**Required Changes:** - -#### Option A: Environment-Based Configuration (Recommended) -```python -import os -from playhouse.pool import PooledPostgresqlDatabase - -DATABASE_TYPE = os.environ.get('DATABASE_TYPE', 'sqlite') - -if DATABASE_TYPE.lower() == 'postgresql': - db = PooledPostgresqlDatabase( - os.environ.get('POSTGRES_DB', 'pd_master'), - user=os.environ.get('POSTGRES_USER', 'pd_admin'), - password=os.environ.get('POSTGRES_PASSWORD'), - host=os.environ.get('POSTGRES_HOST', 'localhost'), - port=int(os.environ.get('POSTGRES_PORT', '5432')), - max_connections=20, - stale_timeout=300, # 5 minutes - timeout=0, - autoconnect=True, - autorollback=True # Automatically rollback failed transactions - ) -else: - # Keep SQLite for local development/testing - db = SqliteDatabase( - 'storage/pd_master.db', - pragmas={ - 'journal_mode': 'wal', - 'cache_size': -1 * 64000, - 'synchronous': 0 - } - ) -``` - -**Environment Variables Required:** -```bash -DATABASE_TYPE=postgresql -POSTGRES_HOST=localhost # or production host -POSTGRES_DB=pd_master -POSTGRES_USER=pd_admin -POSTGRES_PASSWORD=secure_password_here -POSTGRES_PORT=5432 -``` - -**Estimated Effort:** 1 hour - ---- - -### 4. Migration Script Updates (BLOCKING - Must Fix) - -**Location:** `db_migrations.py` (Line 5) - -**Current Code:** -```python -from playhouse.migrate import * -migrator = SqliteMigrator(db_engine.db) -``` - -**Required Changes:** -```python -from playhouse.migrate import * - -# Determine which migrator to use based on database type -if isinstance(db_engine.db, PostgresqlDatabase): - migrator = PostgresqlMigrator(db_engine.db) -else: - migrator = SqliteMigrator(db_engine.db) -``` - -**Estimated Effort:** 30 minutes - ---- - -## Detailed Code Changes Required - -### Priority 1: CRITICAL (Must Fix Before Migration) - -#### File: `app/db_engine.py` - -**Change 1: Database Connection** -- Lines 11-18: Replace SQLite configuration with environment-based PostgreSQL/SQLite -- Add import: `from playhouse.pool import PooledPostgresqlDatabase` -- See detailed code in [Section 3](#3-database-connection-configuration-blocking---must-fix) - -**Change 2: Add table_name to All Models** -- Add `table_name = 'model_name_lowercase'` to Meta class for all 40 models -- Use snake_case for multi-word models (e.g., `BattingCard` → `'batting_card'`) - -**Model Name Mapping:** -```python -Current → 'current' -Rarity → 'rarity' -Event → 'event' -Cardset → 'cardset' -MlbPlayer → 'mlb_player' -Player → 'player' -Team → 'team' -PackType → 'pack_type' -Pack → 'pack' -Card → 'card' -Roster → 'roster' -Result → 'result' -BattingStat → 'batting_stat' -PitchingStat → 'pitching_stat' -Award → 'award' -Paperdex → 'paperdex' -Reward → 'reward' -GameRewards → 'game_rewards' -Notification → 'notification' -GauntletReward → 'gauntlet_reward' -GauntletRun → 'gauntlet_run' -BattingCard → 'batting_card' -BattingCardRatings → 'batting_card_ratings' -PitchingCard → 'pitching_card' -PitchingCardRatings → 'pitching_card_ratings' -CardPosition → 'card_position' -StratGame → 'strat_game' -StratPlay → 'strat_play' -Decision → 'decision' -Scouting → 'scouting' -# ... (add remaining models) -``` - -#### File: `app/routers_v2/stratplays.py` - -**Change 1: Fix get_batting_totals() GROUP BY (Lines 342-456)** - -Replace lines 342-456 with conditional SELECT field building: - -```python -# Determine which fields to include in SELECT based on group_by parameter -base_select_fields = [ - fn.SUM(StratPlay.pa).alias('sum_pa'), - fn.SUM(StratPlay.ab).alias('sum_ab'), - fn.SUM(StratPlay.run).alias('sum_run'), - fn.SUM(StratPlay.hit).alias('sum_hit'), - fn.SUM(StratPlay.rbi).alias('sum_rbi'), - fn.SUM(StratPlay.double).alias('sum_double'), - fn.SUM(StratPlay.triple).alias('sum_triple'), - fn.SUM(StratPlay.homerun).alias('sum_hr'), - fn.SUM(StratPlay.bb).alias('sum_bb'), - fn.SUM(StratPlay.so).alias('sum_so'), - fn.SUM(StratPlay.hbp).alias('sum_hbp'), - fn.SUM(StratPlay.sac).alias('sum_sac'), - fn.SUM(StratPlay.ibb).alias('sum_ibb'), - fn.SUM(StratPlay.gidp).alias('sum_gidp'), - fn.SUM(StratPlay.sb).alias('sum_sb'), - fn.SUM(StratPlay.cs).alias('sum_cs'), - fn.SUM(StratPlay.bphr).alias('sum_bphr'), - fn.SUM(StratPlay.bpfo).alias('sum_bpfo'), - fn.SUM(StratPlay.bp1b).alias('sum_bp1b'), - fn.SUM(StratPlay.bplo).alias('sum_bplo'), - fn.SUM(StratPlay.wpa).alias('sum_wpa'), - fn.SUM(StratPlay.re24).alias('sum_re24'), - # ... all other aggregates ... -] - -# Conditionally add non-aggregated fields based on group_by -if group_by in ['player', 'playerteam', 'playergame', 'playergtype', 'playerteamgtype']: - base_select_fields.insert(0, StratPlay.batter) -if group_by in ['team', 'playerteam', 'teamgame', 'playerteamgtype']: - base_select_fields.append(StratPlay.batter_team) -if group_by in ['playergame', 'teamgame']: - base_select_fields.append(StratPlay.game) - -bat_plays = ( - StratPlay - .select(*base_select_fields) - .where((StratPlay.game << season_games) & (StratPlay.batter.is_null(False))) - .having(fn.SUM(StratPlay.pa) >= min_pa) -) - -# Similar pattern for run_plays -run_select_fields = [ - fn.SUM(StratPlay.sb).alias('sum_sb'), - fn.SUM(StratPlay.cs).alias('sum_cs'), - fn.SUM(StratPlay.pick_off).alias('sum_pick'), - fn.SUM(StratPlay.wpa).alias('sum_wpa'), - fn.SUM(StratPlay.re24).alias('sum_re24') -] - -if group_by in ['player', 'playerteam', 'playergame', 'playergtype', 'playerteamgtype']: - run_select_fields.insert(0, StratPlay.runner) -if group_by in ['team', 'playerteam', 'teamgame', 'playerteamgtype']: - run_select_fields.append(StratPlay.runner_team) -if group_by in ['playergame', 'teamgame']: - run_select_fields.append(StratPlay.game) - -run_plays = ( - StratPlay - .select(*run_select_fields) - .where((StratPlay.game << season_games) & (StratPlay.runner.is_null(False))) -) -``` - -**Change 2: Fix get_pitching_totals() GROUP BY (Lines 645-733)** - -Apply same pattern as batting totals - conditionally build SELECT based on group_by - -**Change 3: Fix get_game_summary() GROUP BY (Lines 911-929)** - -Review and ensure GROUP BY includes all non-aggregated SELECT fields - -#### File: `db_migrations.py` - -**Change: Update Migrator Selection (Line 5)** -```python -from playhouse.migrate import * -from app.db_engine import db - -# Automatically select correct migrator based on database type -if hasattr(db, '__class__') and 'Postgres' in db.__class__.__name__: - migrator = PostgresqlMigrator(db) -else: - migrator = SqliteMigrator(db) -``` - ---- - -### Priority 2: HIGH (Test Thoroughly After Migration) - -#### Files with `.on_conflict_replace()` (15 files) - -**Potential Issue:** PostgreSQL uses different syntax than SQLite for conflict resolution - -**SQLite:** `INSERT OR REPLACE INTO ...` -**PostgreSQL:** `INSERT ... ON CONFLICT ... DO UPDATE SET ...` - -**Peewee Should Handle This Automatically**, but test these endpoints carefully: - -1. `app/routers_v2/players.py:765` - `Player.insert_many(batch).on_conflict_replace().execute()` -2. `app/routers_v2/battingcardratings.py:549` - `BattingCardRatings.insert_many(batch).on_conflict_replace().execute()` -3. `app/routers_v2/battingcards.py:134` - `BattingCard.insert_many(batch).on_conflict_replace().execute()` -4. `app/routers_v2/pitchingcardratings.py:551` - Similar pattern -5. `app/routers_v2/pitchingcards.py:135` - Similar pattern -6. `app/routers_v2/stratplays.py:1082` - `StratPlay.insert_many(batch).on_conflict_replace().execute()` -7. Additional UPDATE/INSERT operations in teams.py, cards.py, admin.py - -**Testing Strategy:** -- Create integration tests for each bulk insert operation -- Verify data integrity after conflicts -- Test with duplicate key scenarios - -#### Random Function Usage - -**Location:** `app/routers_v2/players.py:236` - -**Current Code:** -```python -all_players = Player.select().order_by(fn.Random()) -``` - -**Status:** Should work (both SQLite and PostgreSQL use RANDOM()) -**Action:** Test to confirm behavior is identical - ---- - -### Priority 3: MEDIUM (Monitor During Migration) - -#### Date/Time Handling -**Status:** ✅ GOOD - All using Python's `datetime` library -**Action:** Verify timestamp storage/retrieval works identically - -#### All `.execute()` Calls -**Action:** Monitor logs during testing for any SQL errors - ---- - -## Migration Strategy - -### Phase 1: Preparation (Day 1) - -#### 1.1 Set Up PostgreSQL Development Environment -```bash -# Install PostgreSQL -sudo dnf install postgresql-server postgresql-contrib # Fedora/RHEL -# OR -sudo apt install postgresql postgresql-contrib # Debian/Ubuntu - -# Initialize database -sudo postgresql-setup --initdb # Fedora/RHEL -sudo systemctl start postgresql -sudo systemctl enable postgresql - -# Create database and user -sudo -u postgres psql -CREATE DATABASE pd_master; -CREATE USER pd_admin WITH PASSWORD 'secure_password'; -GRANT ALL PRIVILEGES ON DATABASE pd_master TO pd_admin; -\q -``` - -#### 1.2 Install Python PostgreSQL Adapter -```bash -pip install psycopg2-binary # or psycopg2 (requires compilation) -``` - -#### 1.3 Create Development Branch -```bash -git checkout -b postgres-migration -``` - -#### 1.4 Backup Current SQLite Database -```bash -cp storage/pd_master.db storage/pd_master_backup_$(date +%Y%m%d).db -sqlite3 storage/pd_master.db .dump > storage/pd_master_backup_$(date +%Y%m%d).sql -``` - ---- - -### Phase 2: Code Changes (Day 1-2) - -#### 2.1 Update Database Configuration -- Modify `app/db_engine.py` with environment-based PostgreSQL support -- Add all `table_name` values to model Meta classes -- Update `db_migrations.py` with PostgreSQL migrator support - -#### 2.2 Fix GROUP BY Queries -- Update `app/routers_v2/stratplays.py`: - - Fix `get_batting_totals()` - conditional SELECT fields - - Fix `get_pitching_totals()` - conditional SELECT fields - - Fix `get_game_summary()` - verify GROUP BY completeness -- Review `app/routers_v2/batstats.py` - verify GROUP BY (likely OK) -- Review `app/routers_v2/pitstats.py` - verify GROUP BY (likely OK) - -#### 2.3 Verify Other Routers -- Review all routers for any SQLite-specific code -- Check for any hardcoded SQL that needs updating - -**Estimated Time:** 6-8 hours - ---- - -### Phase 3: Data Migration (Day 2) - -#### Option A: SQLite to PostgreSQL Data Transfer (Recommended) - -**Step 1: Export Schema** -```bash -# Let Peewee create PostgreSQL tables -DATABASE_TYPE=postgresql python -c " -from app.db_engine import db, Current, Rarity, Event, Cardset, MlbPlayer, Player, Team, PackType, Pack, Card, Roster, Result, BattingStat, PitchingStat, Award, Paperdex, Reward, GameRewards, Notification, GauntletReward, GauntletRun, BattingCard, BattingCardRatings, PitchingCard, PitchingCardRatings, CardPosition, StratGame, StratPlay, Decision, Scouting -db.create_tables([Current, Rarity, Event, Cardset, MlbPlayer, Player, Team, PackType, Pack, Card, Roster, Result, BattingStat, PitchingStat, Award, Paperdex, Reward, GameRewards, Notification, GauntletReward, GauntletRun, BattingCard, BattingCardRatings, PitchingCard, PitchingCardRatings, CardPosition, StratGame, StratPlay, Decision, Scouting]) -print('PostgreSQL schema created') -" -``` - -**Step 2: Create Migration Script** - -Create `scripts/migrate_sqlite_to_postgres.py`: -```python -""" -SQLite to PostgreSQL Data Migration Script - -Migrates all data from SQLite (storage/pd_master.db) to PostgreSQL. -""" -import os -import logging -from peewee import SqliteDatabase, PostgresqlDatabase -from playhouse.pool import PooledPostgresqlDatabase -from app.db_engine import ( - Current, Rarity, Event, Cardset, MlbPlayer, Player, Team, PackType, Pack, - Card, Roster, Result, BattingStat, PitchingStat, Award, Paperdex, - Reward, GameRewards, Notification, GauntletReward, GauntletRun, - BattingCard, BattingCardRatings, PitchingCard, PitchingCardRatings, - CardPosition, StratGame, StratPlay, Decision, Scouting -) - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -# Source database (SQLite) -sqlite_db = SqliteDatabase('storage/pd_master.db') - -# Target database (PostgreSQL) -postgres_db = PooledPostgresqlDatabase( - os.environ.get('POSTGRES_DB', 'pd_master'), - user=os.environ.get('POSTGRES_USER', 'pd_admin'), - password=os.environ.get('POSTGRES_PASSWORD'), - host=os.environ.get('POSTGRES_HOST', 'localhost'), - port=int(os.environ.get('POSTGRES_PORT', '5432')) -) - -# List of models in dependency order (foreign keys last) -MODELS = [ - Current, Rarity, Event, Cardset, MlbPlayer, Player, Team, PackType, - # Add all models in correct dependency order -] - -def migrate_model(model_class, batch_size=1000): - """Migrate a single model from SQLite to PostgreSQL""" - logger.info(f"Migrating {model_class.__name__}...") - - # Bind to SQLite to read - model_class._meta.database = sqlite_db - - total = model_class.select().count() - logger.info(f" Total records: {total}") - - migrated = 0 - - # Read from SQLite in batches - for i in range(0, total, batch_size): - batch = list(model_class.select().offset(i).limit(batch_size).dicts()) - - if not batch: - break - - # Bind to PostgreSQL to write - model_class._meta.database = postgres_db - - # Insert batch into PostgreSQL - with postgres_db.atomic(): - model_class.insert_many(batch).execute() - - migrated += len(batch) - logger.info(f" Progress: {migrated}/{total}") - - logger.info(f"✓ {model_class.__name__} migration complete") - -def main(): - """Run full migration""" - logger.info("Starting SQLite → PostgreSQL migration") - - # Create PostgreSQL tables - postgres_db.create_tables(MODELS) - logger.info("PostgreSQL tables created") - - # Migrate each model - for model in MODELS: - try: - migrate_model(model) - except Exception as e: - logger.error(f"✗ Failed to migrate {model.__name__}: {e}") - raise - - logger.info("Migration complete!") - -if __name__ == '__main__': - main() -``` - -**Step 3: Run Migration** -```bash -export DATABASE_TYPE=postgresql -export POSTGRES_HOST=localhost -export POSTGRES_DB=pd_master -export POSTGRES_USER=pd_admin -export POSTGRES_PASSWORD=your_secure_password -export POSTGRES_PORT=5432 - -python scripts/migrate_sqlite_to_postgres.py -``` - -#### Option B: pg_loader (Alternative) - -Can use `pgloader` for automatic migration, but requires testing. - -**Estimated Time:** 2-4 hours (including verification) - ---- - -### Phase 4: Testing (Day 3) - -#### 4.1 Unit Testing Strategy - -Create `tests/test_postgres_migration.py`: -```python -""" -PostgreSQL Migration Test Suite - -Tests all critical endpoints against PostgreSQL database. -""" -import pytest -from fastapi.testclient import TestClient -from app.main import app - -client = TestClient(app) - -class TestGroupByQueries: - """Test all GROUP BY query variations""" - - def test_batting_totals_group_by_player(self): - """Test batting totals with group_by=player""" - response = client.get("/api/v2/stratplays/batting?group_by=player&limit=10") - assert response.status_code == 200 - data = response.json() - assert len(data) <= 10 - - def test_batting_totals_group_by_team(self): - """Test batting totals with group_by=team""" - response = client.get("/api/v2/stratplays/batting?group_by=team") - assert response.status_code == 200 - - def test_batting_totals_group_by_playerteam(self): - """Test batting totals with group_by=playerteam""" - response = client.get("/api/v2/stratplays/batting?group_by=playerteam&limit=10") - assert response.status_code == 200 - - def test_batting_totals_group_by_playergame(self): - """Test batting totals with group_by=playergame""" - response = client.get("/api/v2/stratplays/batting?group_by=playergame&limit=10") - assert response.status_code == 200 - - # Add tests for all group_by variations - - def test_pitching_totals_all_variations(self): - """Test pitching totals with all group_by variations""" - variations = ['player', 'team', 'playerteam', 'playergame', 'teamgame', - 'league', 'gtype', 'playergtype', 'playerteamgtype'] - for variation in variations: - response = client.get(f"/api/v2/stratplays/pitching?group_by={variation}&limit=5") - assert response.status_code == 200, f"Failed for group_by={variation}" - -class TestConflictResolution: - """Test .on_conflict_replace() operations""" - - def test_player_bulk_insert(self): - """Test player bulk insert with conflicts""" - # Implementation depends on your API - pass - - def test_card_ratings_insert(self): - """Test card ratings bulk insert""" - pass - -class TestDataIntegrity: - """Verify data was migrated correctly""" - - def test_record_counts(self): - """Verify record counts match SQLite""" - # Query each table and compare counts - pass - - def test_random_sampling(self): - """Sample random records and verify data""" - pass - -# Run with: pytest tests/test_postgres_migration.py -v -``` - -#### 4.2 Integration Testing - -**Test All Endpoints:** -```bash -# Use Major Domo's test pattern - comprehensive API testing -python tests/comprehensive_api_test.py # Create based on Major Domo pattern -``` - -**Expected Results:** -- All GROUP BY queries return data without errors -- All bulk insert operations complete successfully -- Data integrity verified (counts, relationships, values) -- Performance comparable or better than SQLite - -#### 4.3 Load Testing (Optional) - -```bash -# Use Apache Bench or similar -ab -n 1000 -c 10 http://localhost:8000/api/v2/players/ -``` - -**Estimated Time:** 4-6 hours - ---- - -### Phase 5: Production Deployment (Day 4) - -#### 5.1 Production PostgreSQL Setup - -**On Production Server:** -```bash -# Install PostgreSQL -sudo apt install postgresql postgresql-contrib - -# Create production database -sudo -u postgres psql -CREATE DATABASE pd_master_prod; -CREATE USER pd_prod WITH PASSWORD 'strong_production_password'; -GRANT ALL PRIVILEGES ON DATABASE pd_master_prod TO pd_prod; -ALTER DATABASE pd_master_prod OWNER TO pd_prod; -\q - -# Configure PostgreSQL for production -sudo nano /etc/postgresql/14/main/postgresql.conf -# Set: max_connections = 100 -# Set: shared_buffers = 256MB -# Set: effective_cache_size = 1GB -# Set: maintenance_work_mem = 64MB -# Set: checkpoint_completion_target = 0.9 - -sudo systemctl restart postgresql -``` - -#### 5.2 Migrate Production Data - -**Option A: Direct Migration (Downtime Required)** -```bash -# Stop application -sudo systemctl stop paper-dynasty-api - -# Run migration script -export DATABASE_TYPE=postgresql -export POSTGRES_HOST=localhost -export POSTGRES_DB=pd_master_prod -export POSTGRES_USER=pd_prod -export POSTGRES_PASSWORD=strong_production_password - -python scripts/migrate_sqlite_to_postgres.py - -# Verify migration -python scripts/verify_migration.py - -# Update environment variables -sudo nano /etc/systemd/system/paper-dynasty-api.service -# Add PostgreSQL environment variables - -# Start application -sudo systemctl start paper-dynasty-api -``` - -**Option B: Blue-Green Deployment (Zero Downtime)** -- Set up PostgreSQL database -- Migrate data to PostgreSQL while SQLite is still serving traffic -- Switch DNS/load balancer to new PostgreSQL instance -- Monitor for issues, rollback if needed - -#### 5.3 Post-Deployment Monitoring - -**Monitor for 24-48 hours:** -- Database connection pool usage -- Query performance (compare to SQLite baseline) -- Error rates in logs -- API response times -- Database CPU/memory usage - -**Estimated Time:** 2-3 hours + monitoring - ---- - -## Testing Plan - -### Unit Tests - -Create comprehensive unit tests for: -- All GROUP BY query variations -- Model CRUD operations -- Bulk insert with conflicts -- Transaction rollback behavior - -### Integration Tests - -Test all 30+ API endpoints: -- GET requests with various filters -- POST/PUT/DELETE operations -- Bulk operations -- Edge cases (empty results, null values) - -### Data Integrity Tests - -Verify: -- Record counts match between SQLite and PostgreSQL -- Foreign key relationships maintained -- Data values identical (random sampling) -- Aggregation results consistent - -### Performance Tests - -Compare: -- Query execution times (SQLite vs PostgreSQL) -- API response times -- Concurrent request handling -- Database connection overhead - -**Success Criteria:** -- ✅ 100% of unit tests pass -- ✅ 100% of integration tests pass -- ✅ 100% data integrity verification -- ✅ Performance equal or better than SQLite - ---- - -## Rollback Plan - -### If Issues Discovered During Testing - -**Action:** Fix issues in postgres-migration branch, don't merge to main - -### If Issues Discovered After Production Deployment - -**Immediate Rollback Steps:** - -```bash -# Stop application -sudo systemctl stop paper-dynasty-api - -# Restore environment to SQLite -sudo nano /etc/systemd/system/paper-dynasty-api.service -# Remove DATABASE_TYPE=postgresql -# OR set DATABASE_TYPE=sqlite - -# Start application -sudo systemctl start paper-dynasty-api - -# Verify SQLite is working -curl http://localhost:8000/api/v2/current -``` - -**Data Recovery:** -- SQLite database backup exists: `storage/pd_master_backup_YYYYMMDD.db` -- SQL dump exists: `storage/pd_master_backup_YYYYMMDD.sql` -- Recent data added to PostgreSQL can be extracted with pg_dump - -**Estimated Rollback Time:** 5-10 minutes - ---- - -## Timeline - -### Day 1: Preparation & Initial Code Changes (8 hours) -- ✅ **Hour 1-2:** Set up PostgreSQL development environment -- ✅ **Hour 2-3:** Create development branch, backup SQLite database -- ✅ **Hour 3-5:** Update database configuration in db_engine.py -- ✅ **Hour 5-6:** Add table_name to all 40 models -- ✅ **Hour 6-8:** Update db_migrations.py, verify no syntax errors - -### Day 2: Fix GROUP BY & Data Migration (8 hours) -- ✅ **Hour 1-3:** Fix GROUP BY in stratplays.py (get_batting_totals) -- ✅ **Hour 3-4:** Fix GROUP BY in stratplays.py (get_pitching_totals) -- ✅ **Hour 4-5:** Fix GROUP BY in stratplays.py (get_game_summary) -- ✅ **Hour 5-6:** Create data migration script -- ✅ **Hour 6-8:** Run migration, verify data transferred correctly - -### Day 3: Testing (8 hours) -- ✅ **Hour 1-2:** Create unit tests for GROUP BY queries -- ✅ **Hour 2-4:** Run comprehensive integration tests on all endpoints -- ✅ **Hour 4-6:** Test all .on_conflict_replace() operations -- ✅ **Hour 6-7:** Performance testing and comparison -- ✅ **Hour 7-8:** Fix any issues discovered, retest - -### Day 4: Production Deployment (4 hours + monitoring) -- ✅ **Hour 1:** Set up production PostgreSQL database -- ✅ **Hour 2-3:** Migrate production data (with downtime) -- ✅ **Hour 3-4:** Deploy application, verify functionality -- ✅ **24-48 hours:** Monitor production for issues - -**Total Estimated Effort:** 28 hours (3.5 days) + monitoring - ---- - -## Risk Assessment - -### High Risk Items - -**Risk:** GROUP BY queries fail in production -**Mitigation:** Comprehensive testing of all group_by variations, sister project proven pattern -**Likelihood:** Low (sister project solved this successfully) - -**Risk:** Data migration fails or corrupts data -**Mitigation:** Multiple backups, verification scripts, rollback plan ready -**Likelihood:** Low (well-tested migration pattern) - -**Risk:** Performance degrades after migration -**Mitigation:** Performance testing before deployment, connection pooling configured -**Likelihood:** Very Low (PostgreSQL typically faster for concurrent operations) - -### Medium Risk Items - -**Risk:** `.on_conflict_replace()` syntax differences cause issues -**Mitigation:** Peewee should handle this, but test thoroughly -**Likelihood:** Low (Peewee abstracts this) - -**Risk:** Docker/deployment configuration issues -**Mitigation:** Test in staging environment first -**Likelihood:** Medium (new environment variables required) - -### Low Risk Items - -**Risk:** fn.Random() behaves differently -**Mitigation:** Simple test confirms compatibility -**Likelihood:** Very Low (standard SQL function) - ---- - -## Success Metrics - -### Technical Metrics -- ✅ All 40 models successfully created in PostgreSQL -- ✅ All endpoints return 2XX status codes -- ✅ GROUP BY queries execute without errors -- ✅ Data integrity: 100% record count match -- ✅ Performance: Equal or better response times - -### Business Metrics -- ✅ Zero data loss during migration -- ✅ Minimal downtime (< 1 hour if using Option A) -- ✅ No user-facing errors after deployment -- ✅ Application stability maintained - ---- - -## Appendix - -### A. Environment Variables Reference - -**Development:** -```bash -DATABASE_TYPE=postgresql -POSTGRES_HOST=localhost -POSTGRES_DB=pd_master -POSTGRES_USER=pd_admin -POSTGRES_PASSWORD=dev_password_here -POSTGRES_PORT=5432 -LOG_LEVEL=INFO -``` - -**Production:** -```bash -DATABASE_TYPE=postgresql -POSTGRES_HOST=production_host_or_ip -POSTGRES_DB=pd_master_prod -POSTGRES_USER=pd_prod -POSTGRES_PASSWORD=strong_production_password -POSTGRES_PORT=5432 -LOG_LEVEL=WARNING -``` - -### B. Quick Reference: SQLite vs PostgreSQL - -| Feature | SQLite | PostgreSQL | -|---------|--------|------------| -| **Connection** | File-based | Network-based | -| **Concurrency** | Limited (single writer) | Excellent (MVCC) | -| **GROUP BY** | Permissive | Strict (SQL standard) | -| **AUTOINCREMENT** | AUTOINCREMENT keyword | SERIAL/BIGSERIAL | -| **RANDOM** | RANDOM() | RANDOM() (same) | -| **PRAGMA** | Yes | No | -| **INSERT OR REPLACE** | Native | ON CONFLICT DO UPDATE | - -### C. Sister Project Success Metrics - -From Major Domo migration (August 2025): -- **Success Rate:** 74.6% immediate compatibility -- **Data Loss:** 0% -- **Migration Time:** ~3 days -- **Critical Issues:** 0 (all failures were environment differences) -- **Production Ready:** Yes - ---- - -## Next Steps - -1. **Review this plan** with team/stakeholders -2. **Set migration date** (recommend 1-2 weeks out) -3. **Create tickets** for each phase -4. **Begin Day 1** preparation tasks -5. **Schedule testing time** with QA team (if applicable) - ---- - -**Document Version:** 1.0 -**Last Updated:** 2025-11-07 -**Author:** Claude (AI Assistant) -**Based On:** Major Domo PostgreSQL migration (August 2025) diff --git a/db_engine.py b/db_engine.py deleted file mode 100644 index ebd3046..0000000 --- a/db_engine.py +++ /dev/null @@ -1,898 +0,0 @@ -import math - -""" -DEPRECATED: This file is a legacy implementation from before the deployment -of /database/app/. The active codebase is now in /database/app/db_engine.py. -This file is kept for reference only and should not be used. -""" - -from datetime import datetime -from typing import List -import logging -import os - -from pandas import DataFrame -from peewee import * -from peewee import ModelSelect -from playhouse.shortcuts import model_to_dict - -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, -) - - -def model_csv_headers(this_obj, exclude=None) -> List: - data = model_to_dict(this_obj, recurse=False, exclude=exclude) - return [x for x in data.keys()] - - -def model_to_csv(this_obj, exclude=None) -> List: - data = model_to_dict(this_obj, recurse=False, exclude=exclude) - return [x for x in data.values()] - - -def query_to_csv(all_items: ModelSelect, exclude=None): - if all_items.count() == 0: - data_list = [["No data found"]] - else: - data_list = [model_csv_headers(all_items[0], exclude=exclude)] - for x in all_items: - data_list.append(model_to_csv(x, exclude=exclude)) - - return DataFrame(data_list).to_csv(header=False, index=False) - - -def complex_data_to_csv(complex_data: List): - if len(complex_data) == 0: - data_list = [["No data found"]] - else: - data_list = [[x for x in complex_data[0].keys()]] - for line in complex_data: - logging.debug(f"line: {line}") - this_row = [] - for key in line: - logging.debug(f"key: {key}") - if line[key] is None: - this_row.append("") - - elif isinstance(line[key], dict): - if "name" in line[key]: - this_row.append(line[key]["name"]) - elif "abbrev" in line[key]: - this_row.append(line[key]["abbrev"]) - else: - this_row.append(line[key]["id"]) - - elif isinstance(line[key], int) and line[key] > 100000000: - this_row.append(f"'{line[key]}") - - elif isinstance(line[key], str) and "," in line[key]: - this_row.append(line[key].replace(",", "-_-")) - - else: - this_row.append(line[key]) - - data_list.append(this_row) - - return DataFrame(data_list).to_csv(header=False, index=False) - - -class BaseModel(Model): - class Meta: - database = db - - -class Current(BaseModel): - season = IntegerField() - week = IntegerField(default=0) - gsheet_template = CharField() - gsheet_version = CharField() - live_scoreboard = IntegerField() - - @staticmethod - def latest(): - latest_current = Current.select().order_by(-Current.id).get() - return latest_current - - -db.create_tables([Current]) - - -class Rarity(BaseModel): - value = IntegerField() - name = CharField(unique=True) - color = CharField() - - def __str__(self): - return self.name - - -db.create_tables([Rarity]) - - -class Event(BaseModel): - name = CharField() - short_desc = CharField(null=True) - url = CharField(null=True) - long_desc = CharField(null=True) - thumbnail = CharField(null=True) - active = BooleanField(default=False) - - -db.create_tables([Event]) - - -class Cardset(BaseModel): - name = CharField() - description = CharField() - event = ForeignKeyField(Event, null=True) - for_purchase = BooleanField(default=True) # for_purchase - total_cards = IntegerField() - in_packs = BooleanField(default=True) - ranked_legal = BooleanField(default=True) - - def __str__(self): - return self.name - - -db.create_tables([Cardset]) - - -class MlbPlayer(BaseModel): - first_name = CharField() - last_name = CharField() - key_fangraphs = IntegerField(null=True) - key_bbref = CharField(null=True) - key_retro = CharField(null=True) - key_mlbam = IntegerField(null=True) - offense_col = IntegerField(default=1) - - -db.create_tables([MlbPlayer]) - - -class Player(BaseModel): - player_id = IntegerField(primary_key=True) - p_name = CharField() - cost = IntegerField(default=0) - image = CharField() - image2 = CharField(null=True) - mlbclub = CharField() - franchise = CharField() - cardset = ForeignKeyField(Cardset) - set_num = IntegerField() - rarity = ForeignKeyField(Rarity) - pos_1 = CharField() - pos_2 = CharField(null=True) - pos_3 = CharField(null=True) - pos_4 = CharField(null=True) - pos_5 = CharField(null=True) - pos_6 = CharField(null=True) - pos_7 = CharField(null=True) - pos_8 = CharField(null=True) - headshot = CharField(null=True) - vanity_card = CharField(null=True) - strat_code = CharField(null=True) - bbref_id = CharField(null=True) - fangr_id = CharField(null=True) - description = CharField() - quantity = IntegerField(default=999) - mlbplayer = ForeignKeyField(MlbPlayer, null=True) - - def __str__(self): - return f"{self.cardset} {self.p_name} ({self.rarity.name})" - - # def __eq__(self, other): - # if self.cardset.id == other.cardset.id and self.name == other.name: - # return True - # else: - # return False - - def __lt__(self, other): - if self.wara < other.wara: - return True - elif self.wara > other.wara: - return False - elif self.name < other.name: - return True - else: - return False - - def get_all_pos(self): - all_pos = [] - - if self.pos_1 and self.pos_1 != "CP": - all_pos.append(self.pos_1) - if self.pos_2 and self.pos_2 != "CP": - all_pos.append(self.pos_2) - if self.pos_3 and self.pos_3 != "CP": - all_pos.append(self.pos_3) - if self.pos_4 and self.pos_4 != "CP": - all_pos.append(self.pos_4) - if self.pos_5 and self.pos_5 != "CP": - all_pos.append(self.pos_5) - if self.pos_6 and self.pos_6 != "CP": - all_pos.append(self.pos_6) - if self.pos_7 and self.pos_7 != "CP": - all_pos.append(self.pos_7) - if self.pos_8 and self.pos_8 != "CP": - all_pos.append(self.pos_8) - - return all_pos - - def change_on_sell(self): - # caps = { - # 'replacement': 15, - # 'reserve': 50, - # 'starter': 200, - # 'all-star': 750, - # 'mvp': 2500, - # 'hof': 999999999 - # } - logging.info(f"{self.p_name} cost changing from: {self.cost}") - self.cost = max(math.floor(self.cost * 0.95), 1) - # if self.quantity != 999: - # self.quantity += 1 - logging.info(f"{self.p_name} cost now: {self.cost}") - self.save() - - def change_on_buy(self): - logging.info(f"{self.p_name} cost changing from: {self.cost}") - self.cost = math.ceil(self.cost * 1.1) - # if self.quantity != 999: - # self.quantity -= 1 - logging.info(f"{self.p_name} cost now: {self.cost}") - self.save() - - -db.create_tables([Player]) - - -class Team(BaseModel): - abbrev = CharField() - sname = CharField() - lname = CharField() - gmid = IntegerField() - gmname = CharField() - gsheet = CharField() - wallet = IntegerField() - team_value = IntegerField() - collection_value = IntegerField() - logo = CharField(null=True) - color = CharField(null=True) - season = IntegerField() - event = ForeignKeyField(Event, null=True) - career = IntegerField(default=0) - ranking = IntegerField(default=1000) - has_guide = BooleanField(default=False) - is_ai = IntegerField(null=True) - - def __str__(self): - return f"S{self.season} {self.lname}" - - @staticmethod - def get_by_owner(gmid, season=None): - if not season: - season = Current.get().season - team = Team.get_or_none((Team.gmid == gmid) & (Team.season == season)) - - if not team: - return None - - return team - - @staticmethod - def select_season(season=None): - if not season: - season = Current.get().season - return Team.select().where(Team.season == season) - - @staticmethod - def get_season(abbrev, season=None): - if not season: - season = Current.get().season - return Team.get_or_none(Team.season == season, Team.abbrev == abbrev.upper()) - - def team_hash(self): - hash_string = f"{self.sname[-1]}{self.gmid / 6950123:.0f}{self.sname[-2]}{self.gmid / 42069123:.0f}" - logging.info(f"string: {hash_string}") - return hash_string - - -db.create_tables([Team]) - - -class PackType(BaseModel): - name = CharField() - card_count = IntegerField() - description = CharField() - cost = IntegerField() - available = BooleanField(default=True) - - -db.create_tables([PackType]) - - -class Pack(BaseModel): - team = ForeignKeyField(Team) - pack_type = ForeignKeyField(PackType) - pack_team = ForeignKeyField(Team, null=True) - pack_cardset = ForeignKeyField(Cardset, null=True) - open_time = DateTimeField(null=True) - - -db.create_tables([Pack]) - - -class Card(BaseModel): - player = ForeignKeyField(Player, null=True) - team = ForeignKeyField(Team, null=True) - pack = ForeignKeyField(Pack, null=True) - value = IntegerField(default=0) - - def __str__(self): - if self.player: - return f"{self.player} - {self.team.sname}" - else: - return f"Blank - {self.team.sname}" - - @staticmethod - def select_season(season): - return Card.select().join(Team).where(Card.team.season == season) - - -db.create_tables([Card]) - - -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] - - -class Result(BaseModel): - away_team = ForeignKeyField(Team) - home_team = ForeignKeyField(Team) - away_score = IntegerField() - home_score = IntegerField() - away_team_value = IntegerField(null=True) - home_team_value = IntegerField(null=True) - away_team_ranking = IntegerField(null=True) - home_team_ranking = IntegerField(null=True) - scorecard = CharField() - week = IntegerField() - season = IntegerField() - ranked = BooleanField() - short_game = BooleanField() - game_type = CharField(null=True) - - @staticmethod - def select_season(season=None): - if not season: - season = Current.get().season - return Result.select().where(Result.season == season) - - -class BattingStat(BaseModel): - card = ForeignKeyField(Card) - team = ForeignKeyField(Team) - roster_num = IntegerField() - vs_team = ForeignKeyField(Team) - result = ForeignKeyField(Result, null=True) - pos = CharField() - pa = IntegerField() - ab = IntegerField() - run = IntegerField() - hit = IntegerField() - rbi = IntegerField() - double = IntegerField() - triple = IntegerField() - hr = IntegerField() - bb = IntegerField() - so = IntegerField() - hbp = IntegerField() - sac = IntegerField() - ibb = IntegerField() - gidp = IntegerField() - sb = IntegerField() - cs = IntegerField() - bphr = IntegerField() - bpfo = IntegerField() - bp1b = IntegerField() - bplo = IntegerField() - xch = IntegerField() - xhit = IntegerField() - error = IntegerField() - pb = IntegerField() - sbc = IntegerField() - csc = IntegerField() - week = IntegerField() - season = IntegerField() - created = DateTimeField() - game_id = IntegerField() - - -class PitchingStat(BaseModel): - card = ForeignKeyField(Card) - team = ForeignKeyField(Team) - roster_num = IntegerField() - vs_team = ForeignKeyField(Team) - result = ForeignKeyField(Result, null=True) - ip = FloatField() - hit = IntegerField() - run = IntegerField() - erun = IntegerField() - so = IntegerField() - bb = IntegerField() - hbp = IntegerField() - wp = IntegerField() - balk = IntegerField() - hr = IntegerField() - ir = IntegerField() - irs = IntegerField() - gs = IntegerField() - win = IntegerField() - loss = IntegerField() - hold = IntegerField() - sv = IntegerField() - bsv = IntegerField() - week = IntegerField() - season = IntegerField() - created = DateTimeField() - game_id = IntegerField() - - -class Award(BaseModel): - name = CharField() - season = IntegerField() - timing = CharField(default="In-Season") - card = ForeignKeyField(Card, null=True) - team = ForeignKeyField(Team, null=True) - image = CharField(null=True) - - -class Paperdex(BaseModel): - team = ForeignKeyField(Team) - player = ForeignKeyField(Player) - created = DateTimeField(default=datetime.now) - - # def add_to_paperdex(self, team, cards: list): - # for x in players: - # if not isinstance(x, Card): - # raise TypeError(f'The Pokedex can only take a list of Player or Card objects') - # - # Paperdex.get_or_create(team=team, player=player) - - -class Reward(BaseModel): - name = CharField(null=True) - season = IntegerField() - week = IntegerField() - team = ForeignKeyField(Team) - created = DateTimeField() - - -class GameRewards(BaseModel): - name = CharField() - pack_type = ForeignKeyField(PackType, null=True) - player = ForeignKeyField(Player, null=True) - money = IntegerField(null=True) - - -class Notification(BaseModel): - created = DateTimeField() - title = CharField() - desc = CharField(null=True) - field_name = CharField() - message = CharField() - about = CharField() # f'{Topic}-{Object ID}' - ack = BooleanField(default=False) - - -class GauntletReward(BaseModel): - name = CharField() - gauntlet = ForeignKeyField(Event) - reward = ForeignKeyField(GameRewards) - win_num = IntegerField() - loss_max = IntegerField(default=1) - - -class GauntletRun(BaseModel): - team = ForeignKeyField(Team) - gauntlet = ForeignKeyField(Event) - wins = IntegerField(default=0) - losses = IntegerField(default=0) - gsheet = CharField(null=True) - created = DateTimeField(default=datetime.now) - ended = DateTimeField(null=True) - - -db.create_tables( - [ - Roster, - BattingStat, - PitchingStat, - Result, - Award, - Paperdex, - Reward, - GameRewards, - Notification, - GauntletReward, - GauntletRun, - ] -) - - -class BattingCard(BaseModel): - player = ForeignKeyField(Player) - variant = IntegerField() - steal_low = IntegerField() - steal_high = IntegerField() - steal_auto = BooleanField() - steal_jump = FloatField() - bunting = CharField() - hit_and_run = CharField() - running = IntegerField() - offense_col = IntegerField() - hand = CharField(default="R") - - -bc_index = ModelIndex( - BattingCard, (BattingCard.player, BattingCard.variant), unique=True -) -BattingCard.add_index(bc_index) - - -class BattingCardRatings(BaseModel): - battingcard = ForeignKeyField(BattingCard) - vs_hand = CharField(default="R") - pull_rate = FloatField() - center_rate = FloatField() - slap_rate = FloatField() - homerun = FloatField() - bp_homerun = FloatField() - triple = FloatField() - double_three = FloatField() - double_two = FloatField() - double_pull = FloatField() - single_two = FloatField() - single_one = FloatField() - single_center = FloatField() - bp_single = FloatField() - hbp = FloatField() - walk = FloatField() - strikeout = FloatField() - lineout = FloatField() - popout = FloatField() - flyout_a = FloatField() - flyout_bq = FloatField() - flyout_lf_b = FloatField() - flyout_rf_b = FloatField() - groundout_a = FloatField() - groundout_b = FloatField() - groundout_c = FloatField() - avg = FloatField(null=True) - obp = FloatField(null=True) - slg = FloatField(null=True) - - -bcr_index = ModelIndex( - BattingCardRatings, - (BattingCardRatings.battingcard, BattingCardRatings.vs_hand), - unique=True, -) -BattingCardRatings.add_index(bcr_index) - - -class PitchingCard(BaseModel): - player = ForeignKeyField(Player) - variant = IntegerField() - balk = IntegerField() - wild_pitch = IntegerField() - hold = IntegerField() - starter_rating = IntegerField() - relief_rating = IntegerField() - closer_rating = IntegerField(null=True) - batting = CharField(null=True) - offense_col = IntegerField() - hand = CharField(default="R") - - -pc_index = ModelIndex( - PitchingCard, (PitchingCard.player, PitchingCard.variant), unique=True -) -PitchingCard.add_index(pc_index) - - -class PitchingCardRatings(BaseModel): - pitchingcard = ForeignKeyField(PitchingCard) - vs_hand = CharField(default="R") - homerun = FloatField() - bp_homerun = FloatField() - triple = FloatField() - double_three = FloatField() - double_two = FloatField() - double_cf = FloatField() - single_two = FloatField() - single_one = FloatField() - single_center = FloatField() - bp_single = FloatField() - hbp = FloatField() - walk = FloatField() - strikeout = FloatField() - flyout_lf_b = FloatField() - flyout_cf_b = FloatField() - flyout_rf_b = FloatField() - groundout_a = FloatField() - groundout_b = FloatField() - xcheck_p = FloatField() - xcheck_c = FloatField() - xcheck_1b = FloatField() - xcheck_2b = FloatField() - xcheck_3b = FloatField() - xcheck_ss = FloatField() - xcheck_lf = FloatField() - xcheck_cf = FloatField() - xcheck_rf = FloatField() - avg = FloatField(null=True) - obp = FloatField(null=True) - slg = FloatField(null=True) - - -pcr_index = ModelIndex( - PitchingCardRatings, - (PitchingCardRatings.pitchingcard, PitchingCardRatings.vs_hand), - unique=True, -) -PitchingCardRatings.add_index(pcr_index) - - -class CardPosition(BaseModel): - player = ForeignKeyField(Player) - variant = IntegerField() - position = CharField() - innings = IntegerField() - range = IntegerField() - error = IntegerField() - arm = IntegerField(null=True) - pb = IntegerField(null=True) - overthrow = IntegerField(null=True) - - -pos_index = ModelIndex( - CardPosition, - (CardPosition.player, CardPosition.variant, CardPosition.position), - unique=True, -) -CardPosition.add_index(pos_index) - - -db.create_tables( - [BattingCard, BattingCardRatings, PitchingCard, PitchingCardRatings, CardPosition] -) - - -db.close() - -# scout_db = SqliteDatabase( -# 'storage/card_creation.db', -# pragmas={ -# 'journal_mode': 'wal', -# 'cache_size': -1 * 64000, -# 'synchronous': 0 -# } -# ) -# -# -# class BaseModelScout(Model): -# class Meta: -# database = scout_db -# -# -# class ScoutCardset(BaseModelScout): -# set_title = CharField() -# set_subtitle = CharField(null=True) -# -# -# class ScoutPlayer(BaseModelScout): -# sba_id = IntegerField(primary_key=True) -# name = CharField() -# fg_id = IntegerField() -# br_id = CharField() -# offense_col = IntegerField() -# hand = CharField(default='R') -# -# -# scout_db.create_tables([ScoutCardset, ScoutPlayer]) -# -# -# class BatterRatings(BaseModelScout): -# id = CharField(unique=True, primary_key=True) -# player = ForeignKeyField(ScoutPlayer) -# cardset = ForeignKeyField(ScoutCardset) -# vs_hand = FloatField() -# is_prep = BooleanField() -# homerun = FloatField() -# bp_homerun = FloatField() -# triple = FloatField() -# double_three = FloatField() -# double_two = FloatField() -# double_pull = FloatField() -# single_two = FloatField() -# single_one = FloatField() -# single_center = FloatField() -# bp_single = FloatField() -# hbp = FloatField() -# walk = FloatField() -# strikeout = FloatField() -# lineout = FloatField() -# popout = FloatField() -# flyout_a = FloatField() -# flyout_bq = FloatField() -# flyout_lf_b = FloatField() -# flyout_rf_b = FloatField() -# groundout_a = FloatField() -# groundout_b = FloatField() -# groundout_c = FloatField() -# avg = FloatField(null=True) -# obp = FloatField(null=True) -# slg = FloatField(null=True) -# -# -# class PitcherRatings(BaseModelScout): -# id = CharField(unique=True, primary_key=True) -# player = ForeignKeyField(ScoutPlayer) -# cardset = ForeignKeyField(ScoutCardset) -# vs_hand = CharField() -# is_prep = BooleanField() -# homerun = FloatField() -# bp_homerun = FloatField() -# triple = FloatField() -# double_three = FloatField() -# double_two = FloatField() -# double_cf = FloatField() -# single_two = FloatField() -# single_one = FloatField() -# single_center = FloatField() -# bp_single = FloatField() -# hbp = FloatField() -# walk = FloatField() -# strikeout = FloatField() -# fo_slap = FloatField() -# fo_center = FloatField() -# groundout_a = FloatField() -# groundout_b = FloatField() -# xcheck_p = FloatField() -# xcheck_c = FloatField() -# xcheck_1b = FloatField() -# xcheck_2b = FloatField() -# xcheck_3b = FloatField() -# xcheck_ss = FloatField() -# xcheck_lf = FloatField() -# xcheck_cf = FloatField() -# xcheck_rf = FloatField() -# avg = FloatField(null=True) -# obp = FloatField(null=True) -# slg = FloatField(null=True) -# -# -# # scout_db.create_tables([BatterRatings, PitcherRatings]) -# -# -# class CardColumns(BaseModelScout): -# id = CharField(unique=True, primary_key=True) -# player = ForeignKeyField(ScoutPlayer) -# hand = CharField() -# b_ratings = ForeignKeyField(BatterRatings, null=True) -# p_ratings = ForeignKeyField(PitcherRatings, null=True) -# one_dice = CharField() -# one_results = CharField() -# one_splits = CharField() -# two_dice = CharField() -# two_results = CharField() -# two_splits = CharField() -# three_dice = CharField() -# three_results = CharField() -# three_splits = CharField() -# -# -# class Position(BaseModelScout): -# player = ForeignKeyField(ScoutPlayer) -# cardset = ForeignKeyField(ScoutCardset) -# position = CharField() -# innings = IntegerField() -# range = IntegerField() -# error = IntegerField() -# arm = CharField(null=True) -# pb = IntegerField(null=True) -# overthrow = IntegerField(null=True) -# -# -# class BatterData(BaseModelScout): -# player = ForeignKeyField(ScoutPlayer) -# cardset = ForeignKeyField(ScoutCardset) -# stealing = CharField() -# st_low = IntegerField() -# st_high = IntegerField() -# st_auto = BooleanField() -# st_jump = FloatField() -# bunting = CharField(null=True) -# hit_and_run = CharField(null=True) -# running = CharField() -# -# -# class PitcherData(BaseModelScout): -# player = ForeignKeyField(ScoutPlayer) -# cardset = ForeignKeyField(ScoutCardset) -# balk = IntegerField(null=True) -# wild_pitch = IntegerField(null=True) -# hold = CharField() -# starter_rating = IntegerField() -# relief_rating = IntegerField() -# closer_rating = IntegerField(null=True) -# batting = CharField(null=True) -# -# -# scout_db.create_tables([CardColumns, Position, BatterData, PitcherData]) -# -# -# class CardOutput(BaseModelScout): -# name = CharField() -# hand = CharField() -# positions = CharField() -# stealing = CharField() -# bunting = CharField() -# hitandrun = CharField() -# running = CharField() -# -# -# scout_db.close() diff --git a/db_migrations.py b/db_migrations.py deleted file mode 100644 index e9f7976..0000000 --- a/db_migrations.py +++ /dev/null @@ -1,50 +0,0 @@ -from playhouse.migrate import * -import db_engine - -# Automatically select correct migrator based on database type -if hasattr(db_engine.db, '__class__') and 'Postgres' in db_engine.db.__class__.__name__: - migrator = PostgresqlMigrator(db_engine.db) -else: - migrator = SqliteMigrator(db_engine.db) - -# db_engine.db.create_tables([db_engine.PackTheme]) - -# pubdate_field = DateTimeField(null=True) -# comment_field = TextField(default='') - - -# pitcher_injury = IntegerField(null=True) -# offense_col = IntegerField(null=True) -# pos_2 = CharField(null=True) -# last_game = CharField(null=True) -# game_type = CharField(null=True) -mlb_player = ForeignKeyField(db_engine.MlbPlayer, field=db_engine.MlbPlayer.id, null=True) -result = ForeignKeyField(db_engine.Result, field=db_engine.Result.id, null=True) -# active_theme = ForeignKeyField(PackTheme, to_field='id', field_type=int, null=True) -# active_theme = ForeignKeyField(db_engine.PackTheme, field=db_engine.PackTheme.id, null=True) # for careers -# game_type = CharField(null=True) -# pack_team = ForeignKeyField(db_engine.Team, field=db_engine.Team.id, null=True) -# pack_cardset = ForeignKeyField(db_engine.Cardset, field=db_engine.Cardset.id, null=True) -pull_rate = FloatField(default=0.333) - -migrate( - # migrator.add_column('current', 'active_theme_id', active_theme), - # migrator.add_column('pack', 'pack_team_id', pack_team), - # migrator.add_column('player', 'mlbplayer_id', mlb_player), - migrator.add_column('battingstat', 'result_id', result), - migrator.add_column('pitchingstat', 'result_id', result), - # migrator.add_column('battingcardratings', 'pull_rate', pull_rate), - # migrator.rename_column('cardset', 'available', 'for_purchase') - # migrator.add_column('player', 'offense_col', offense_col), - # migrator.add_column('comment_tbl', 'comment', comment_field), - # migrator.rename_column('story', 'pub_date', 'publish_date'), - # migrator.drop_column('story', 'some_old_field'), - # migrator.drop_not_null('team', 'abbrev'), - # migrator.add_not_null('story', 'modified_date'), - # migrator.rename_table('story', 'stories_tbl'), - # migrator.drop_index('team', 'team_abbrev'), - # migrator.drop_index('player', 'player_name') - # migrator.add_column('pack', 'pack_type', pack_type), -) - -db_engine.db.close() diff --git a/DOCKER_QUICKSTART.md b/docs/DOCKER_QUICKSTART.md similarity index 100% rename from DOCKER_QUICKSTART.md rename to docs/DOCKER_QUICKSTART.md diff --git a/MIGRATION_PROGRESS.md b/docs/MIGRATION_PROGRESS.md similarity index 100% rename from MIGRATION_PROGRESS.md rename to docs/MIGRATION_PROGRESS.md diff --git a/PROJECT_PLAN.json b/docs/PROJECT_PLAN.json similarity index 100% rename from PROJECT_PLAN.json rename to docs/PROJECT_PLAN.json diff --git a/QUICK_START.md b/docs/QUICK_START.md similarity index 100% rename from QUICK_START.md rename to docs/QUICK_START.md diff --git a/main.py b/main.py deleted file mode 100644 index 2c0d4bd..0000000 --- a/main.py +++ /dev/null @@ -1,6363 +0,0 @@ -from datetime import datetime -import logging -import os - -from db_engine import * -from app.db_helpers import upsert_players, upsert_gauntlet_rewards -from typing import Optional, List, Union, Literal -from fastapi import FastAPI, HTTPException, Depends, Response, Query -from fastapi.security import OAuth2PasswordBearer -import pydantic -import pygsheets -import sheets - -from playhouse.shortcuts import model_to_dict -from pandas import DataFrame - -raw_log_level = os.getenv("LOG_LEVEL") -if raw_log_level == "INFO": - log_level = logging.INFO -elif raw_log_level == "WARN": - log_level = logging.WARN -else: - log_level = logging.ERROR - -date = f"{datetime.now().year}-{datetime.now().month}-{datetime.now().day}" -logging.basicConfig( - filename=f"logs/database/{date}.log", - format="%(asctime)s - %(levelname)s - %(message)s", - level=log_level, -) - -app = FastAPI() - -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") -DEFAULT_SEASON = 5 - -# Franchise normalization: Convert city+team names to city-agnostic team names -# This enables cross-era player matching (e.g., 'Oakland Athletics' -> 'Athletics') -FRANCHISE_NORMALIZE = { - "Arizona Diamondbacks": "Diamondbacks", - "Atlanta Braves": "Braves", - "Baltimore Orioles": "Orioles", - "Boston Red Sox": "Red Sox", - "Chicago Cubs": "Cubs", - "Chicago White Sox": "White Sox", - "Cincinnati Reds": "Reds", - "Cleveland Guardians": "Guardians", - "Colorado Rockies": "Rockies", - "Detroit Tigers": "Tigers", - "Houston Astros": "Astros", - "Kansas City Royals": "Royals", - "Los Angeles Angels": "Angels", - "Los Angeles Dodgers": "Dodgers", - "Miami Marlins": "Marlins", - "Milwaukee Brewers": "Brewers", - "Minnesota Twins": "Twins", - "New York Mets": "Mets", - "New York Yankees": "Yankees", - "Oakland Athletics": "Athletics", - "Philadelphia Phillies": "Phillies", - "Pittsburgh Pirates": "Pirates", - "San Diego Padres": "Padres", - "San Francisco Giants": "Giants", - "Seattle Mariners": "Mariners", - "St Louis Cardinals": "Cardinals", - "St. Louis Cardinals": "Cardinals", - "Tampa Bay Rays": "Rays", - "Texas Rangers": "Rangers", - "Toronto Blue Jays": "Blue Jays", - "Washington Nationals": "Nationals", -} - - -def normalize_franchise(franchise: str) -> str: - """Convert city+team name to team-only (e.g., 'Oakland Athletics' -> 'Athletics')""" - titled = franchise.title() - return FRANCHISE_NORMALIZE.get(titled, titled) - - -SHEETS_AUTH = pygsheets.authorize( - service_file="storage/paper-dynasty-service-creds.json", retries=1 -) - - -def valid_token(token): - if token == os.environ.get("API_TOKEN"): - return True - else: - return False - - -def int_timestamp(datetime_obj: datetime) -> int: - return int(datetime.timestamp(datetime_obj) * 1000) - - -""" -CURRENT ENDPOINTS -""" - - -class CurrentModel(pydantic.BaseModel): - season: int - week: int - gsheet_template: str - gsheet_version: str - - -@app.get("/api/v1/current") -async def v1_current_get(season: Optional[int] = None, csv: Optional[bool] = False): - if season: - current = Current.get_or_none(season=season) - else: - current = Current.latest() - - if csv: - current_list = [ - ["id", "season", "week"], - [current.id, current.season, current.week], - ] - 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 - - -@app.get("/api/v1/current/{current_id}") -async def v1_current_get_one(current_id, csv: Optional[bool] = False): - try: - current = Current.get_by_id(current_id) - except Exception: - db.close() - raise HTTPException( - status_code=404, detail=f"No current found with id {current_id}" - ) - - if csv: - current_list = [ - ["id", "season", "week"], - [current.id, current.season, current.week], - ] - 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 - - -@app.post("/api/v1/current") -async def v1_current_post(current: CurrentModel, 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 current. This event has been logged.", - ) - - 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( - season=current.season, - week=current.week, - gsheet_template=current.gsheet_template, - gsheet_version=current.gsheet_version, - ) - - 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", - ) - - -@app.patch("/api/v1/current/{current_id}") -async def v1_current_patch( - current_id: int, - season: Optional[int] = None, - week: Optional[int] = None, - 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() - 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() - raise HTTPException( - status_code=404, detail=f"No current found with id {current_id}" - ) - - if season is not None: - current.season = season - if week is not None: - current.week = week - if gsheet_template is not None: - current.gsheet_template = gsheet_template - if gsheet_version is not None: - current.gsheet_version = gsheet_version - if live_scoreboard is not None: - current.live_scoreboard = live_scoreboard - - if current.save() == 1: - return_val = model_to_dict(current) - 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 current", - ) - - -@app.delete("/api/v1/current/{current_id}") -async def v1_current_delete(current_id, 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 delete current. This event has been logged.", - ) - try: - this_curr = Current.get_by_id(current_id) - except Exception: - db.close() - 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" - ) - else: - raise HTTPException( - status_code=500, detail=f"Current {current_id} was not deleted" - ) - - -""" -TEAMS ENDPOINTS -""" - - -class TeamModel(pydantic.BaseModel): - abbrev: str - sname: str - lname: str - gmid: int - gmname: str - wallet: int = 0 - gsheet: str - team_value: int = 0 - collection_value: int = 0 - logo: Optional[str] = None - color: Optional[str] = None - season: int - ps_shiny: Optional[int] = 0 - ranking: Optional[int] = 1000 - has_guide: Optional[bool] = False - is_ai: Optional[bool] = False - - -@app.get("/api/v1/teams") -async def v1_teams_get( - season: Optional[int] = None, - gm_id: Optional[int] = None, - abbrev: Optional[str] = None, - tv_min: Optional[int] = None, - tv_max: Optional[int] = None, - cv_min: Optional[int] = None, - cv_max: Optional[int] = None, - ps_shiny_min: Optional[int] = None, - ps_shiny_max: Optional[int] = None, - ranking_min: Optional[int] = None, - ranking_max: Optional[int] = None, - has_guide: Optional[bool] = None, - sname: Optional[str] = None, - lname: Optional[str] = None, - is_ai: Optional[bool] = None, - event_id: Optional[int] = None, - limit: Optional[int] = None, - csv: Optional[bool] = False, -): - """ - Param: season: int - Param: team_abbrev: string - Param: owner_id: int - """ - if season: - all_teams = Team.select_season(season) - else: - all_teams = Team.select() - - # if all_teams.count() == 0: - # db.close() - # raise HTTPException(status_code=404, detail=f'There are no teams to filter') - - if gm_id is not None: - all_teams = all_teams.where(Team.gmid == gm_id) - - if abbrev is not None: - all_teams = all_teams.where(fn.Lower(Team.abbrev) == abbrev.lower()) - - if sname is not None: - all_teams = all_teams.where(fn.Lower(Team.sname) == sname.lower()) - - if lname is not None: - all_teams = all_teams.where(fn.Lower(Team.lname) == lname.lower()) - - if tv_min is not None: - all_teams = all_teams.where(Team.team_value >= tv_min) - - if tv_max is not None: - all_teams = all_teams.where(Team.team_value <= tv_max) - - if cv_min is not None: - all_teams = all_teams.where(Team.collection_value >= cv_min) - - if cv_max is not None: - all_teams = all_teams.where(Team.collection_value <= cv_max) - - if ps_shiny_min is not None: - all_teams = all_teams.where(Team.career >= ps_shiny_min) - - if ps_shiny_max is not None: - all_teams = all_teams.where(Team.career <= ps_shiny_max) - - if ranking_min is not None: - all_teams = all_teams.where(Team.ranking >= ranking_min) - - 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: - if not has_guide: - all_teams = all_teams.where(Team.has_guide == 0) - else: - all_teams = all_teams.where(Team.has_guide == 1) - - if is_ai is not None: - all_teams = all_teams.where(Team.is_ai) - - if event_id is not None: - all_teams = all_teams.where(Team.event_id == event_id) - - if limit is not None: - all_teams = all_teams.limit(limit) - - # if all_teams.count() == 0: - # db.close() - # raise HTTPException(status_code=404, detail=f'No teams found') - - if csv: - data_list = [ - [ - "id", - "abbrev", - "sname", - "lname", - "gmid", - "gmname", - "wallet", - "gsheet", - "team_value", - "collection_value", - "logo", - "color", - "season", - "ranking", - ] - ] - for line in all_teams: - data_list.append( - [ - line.id, - line.abbrev, - line.sname, - line.lname, - line.gmid, - line.gmname, - line.wallet, - line.gsheet, - line.team_value, - line.collection_value, - line.logo, - f"'{line.color}", - line.season, - line.ranking, - ] - ) - return_val = DataFrame(data_list).to_csv(header=False, index=False) - - db.close() - return Response(content=return_val, media_type="text/csv") - - else: - return_teams = {"count": all_teams.count(), "teams": []} - for x in all_teams: - return_teams["teams"].append(model_to_dict(x)) - - db.close() - return return_teams - - -@app.get("/api/v1/teams/{team_id}") -async def v1_teams_get_one(team_id, csv: Optional[bool] = False): - try: - this_team = Team.get_by_id(team_id) - except Exception: - db.close() - raise HTTPException(status_code=404, detail=f"No team found with id {team_id}") - - if csv: - team_packs = Pack.select().where( - (Pack.team == this_team) & (Pack.open_time.is_null(True)) - ) - data_list = [ - [ - "id", - "abbrev", - "sname", - "lname", - "gmid", - "gmname", - "wallet", - "ranking", - "gsheet", - "sealed_packs", - "collection_value", - "logo", - "color", - "season", - ], - [ - this_team.id, - this_team.abbrev, - this_team.sname, - this_team.lname, - this_team.gmid, - this_team.gmname, - this_team.wallet, - this_team.ranking, - this_team.gsheet, - team_packs.count(), - this_team.collection_value, - this_team.logo, - this_team.color, - this_team.season, - ], - ] - 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_team) - db.close() - return return_val - - -@app.get("/api/v1/teams/{team_id}/buy/players") -async def v1_team_cards_buy(team_id: int, ids: str, ts: str): - try: - this_team = Team.get_by_id(team_id) - except Exception: - db.close() - 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.", - ) - - last_card = Card.select(Card.id).order_by(-Card.id).limit(1) - lc_id = last_card[0].id - - all_ids = ids.split(",") - conf_message = "" - total_cost = 0 - for player_id in all_ids: - if player_id != "": - try: - this_player = Player.get_by_id(player_id) - except Exception: - db.close() - raise HTTPException( - status_code=404, - detail=f"No player found with id {player_id} /// " - f"{conf_message} purchased", - ) - - # check wallet balance - if this_team.wallet < this_player.cost: - logging.info( - 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 " - f"{this_player} costs {this_player.cost}₼. /// {conf_message} purchased", - ) - - # Create player card and update cost - buy_price = this_player.cost - total_cost += buy_price - this_card = Card( - player_id=this_player.player_id, team_id=this_team.id, value=buy_price - ) - Paperdex.get_or_create(team_id=team_id, player_id=this_player.player_id) - this_card.save() - this_player.change_on_buy() - - # Deduct card cost from team - logging.info(f"{this_team.abbrev} starting wallet: {this_team.wallet}") - this_team.wallet -= buy_price - this_team.save() - logging.info(f"{this_team.abbrev} ending wallet: {this_team.wallet}") - - # Post a notification - if this_player.rarity.value >= 2: - new_notif = Notification( - created=int_timestamp(datetime.now()), - title=f"Price Change", - desc="Modified by buying and selling", - field_name=f"{this_player.description}", - message=f"From {buy_price}₼ 📈 to **{this_player.cost}**₼", - about=f"Player-{this_player.player_id}", - ) - new_notif.save() - - conf_message += ( - f"{buy_price}₼ for {this_player.rarity.name} {this_player.p_name} " - f"({this_player.cardset.name}), " - ) - - # sheets.post_new_cards(SHEETS_AUTH, lc_id) - - raise HTTPException( - status_code=200, - detail=f"{conf_message} purchased. /// Total Cost: {total_cost}₼ /// " - f"Final Wallet: {this_team.wallet}", - ) - - -@app.get("/api/v1/teams/{team_id}/buy/pack/{packtype_id}") -async def v1_team_pack_buy( - team_id: int, packtype_id: int, ts: str, quantity: Optional[int] = 1 -): - try: - this_packtype = PackType.get_by_id(packtype_id) - except Exception: - db.close() - 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() - 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()}" - ) - raise HTTPException( - status_code=401, - detail=f"You are not authorized to buy {this_team.abbrev} packs. This event has been logged.", - ) - - # 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 " - f"{this_packtype} costs {this_packtype.cost}.", - ) - - all_packs = [] - for i in range(quantity): - all_packs.append(Pack(team_id=this_team.id, pack_type_id=this_packtype.id)) - - # Deduct card cost from team - logging.info(f"{this_team.abbrev} starting wallet: {this_team.wallet}") - this_team.wallet -= total_cost - this_team.save() - logging.info(f"{this_team.abbrev} ending wallet: {this_team.wallet}") - - with db.atomic(): - Pack.bulk_create(all_packs, batch_size=15) - db.close() - - raise HTTPException( - status_code=200, - detail=f"Quantity {quantity} {this_packtype.name} pack{'s' if quantity > 1 else ''} have been purchased by " - f"{this_team.lname} for {total_cost} bucks. You may close this window.", - ) - - -@app.get("/api/v1/teams/{team_id}/sell/cards") -async def v1_team_cards_sell(team_id: int, ids: str, ts: str): - try: - this_team = Team.get_by_id(team_id) - except Exception: - db.close() - 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.", - ) - - all_ids = ids.split(",") - del_ids = [] - conf_message = "" - total_cost = 0 - for card_id in all_ids: - if card_id != "": - try: - this_card = Card.get_by_id(card_id) - except Exception: - db.close() - raise HTTPException( - status_code=404, detail=f"No card found with id {card_id}" - ) - - del_ids.append(card_id) - this_player = this_card.player - - if this_card.team != this_team: - raise HTTPException( - status_code=401, - detail=f"Card id {card_id} ({this_player.p_name}) belongs to " - f"{this_card.team.abbrev} and cannot be sold. /// {conf_message} sold", - ) - - orig_price = this_player.cost - sell_price = round(this_player.cost * 0.5) - total_cost += sell_price - - # credit selling team's wallet - if this_team.wallet is None: - this_team.wallet = sell_price - else: - this_team.wallet += sell_price - this_team.save() - - # decrease price of player - this_player.change_on_sell() - this_card.delete_instance() - - # post a notification - if this_player.rarity.value >= 2: - new_notif = Notification( - created=int_timestamp(datetime.now()), - title=f"Price Change", - desc="Modified by buying and selling", - field_name=f"{this_player.description}", - message=f"From {orig_price}₼ 📉 to **{this_player.cost}**₼", - about=f"Player-{this_player.id}", - ) - new_notif.save() - - conf_message += ( - f"{sell_price}₼ for {this_player.rarity.name} {this_player.p_name} " - f"({this_player.cardset.name}), " - ) - - # sheets.post_deletion(SHEETS_AUTH, del_ids) - raise HTTPException( - status_code=200, - detail=f"{conf_message} sold. /// Total Earned: {total_cost}₼ /// " - f"Final Wallet: {this_team.wallet}", - ) - - -@app.get("/api/v1/teams/{team_id}/cards") -async def v1_teams_cards_get(team_id, csv: Optional[bool] = True): - """ - CSV output specifically targeting team roster sheet - - Parameters - ---------- - team_id - csv - """ - try: - this_team = Team.get_by_id(team_id) - except Exception: - db.close() - 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.", - ) - - all_cards = ( - Card.select() - .join(Player) - .join(Rarity) - .where(Card.team == this_team) - .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") - - data_list = [ - [ - "cardset", - "player", - "rarity", - "image", - "image2", - "pos_1", - "pos_2", - "pos_3", - "pos_4", - "pos_5", - "pos_6", - "pos_7", - "pos_8", - "cost", - "mlbclub", - "franchise", - "set_num", - "bbref_id", - "player_id", - "card_id", - ] - ] - for line in all_cards: - data_list.append( - [ - line.player.cardset, - line.player.p_name, - line.player.rarity, - line.player.image, - line.player.image2, - line.player.pos_1, - line.player.pos_2, - line.player.pos_3, - line.player.pos_4, - line.player.pos_5, - line.player.pos_6, - line.player.pos_7, - line.player.pos_8, - line.player.cost, - line.player.mlbclub, - line.player.franchise, - line.player.set_num, - line.player.bbref_id, - line.player.player_id, - line.id, - ] - ) - return_val = DataFrame(data_list).to_csv(header=False, index=False) - - db.close() - return Response(content=return_val, media_type="text/csv") - - -@app.post("/api/v1/teams") -async def v1_teams_post(team: TeamModel, 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 teams. This event has been logged.", - ) - - 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}", - ) - - this_team = Team( - abbrev=team.abbrev, - sname=team.sname, - lname=team.lname, - gmid=team.gmid, - gmname=team.gmname, - wallet=team.wallet, - gsheet=team.gsheet, - team_value=team.team_value, - collection_value=team.collection_value, - logo=team.logo, - color=team.color, - ranking=team.ranking, - season=team.season, - career=team.ps_shiny, - has_guide=team.has_guide, - is_ai=team.is_ai, - ) - - saved = this_team.save() - if saved == 1: - return_team = model_to_dict(this_team) - db.close() - return return_team - else: - raise HTTPException( - status_code=418, - detail="Well slap my ass and call me a teapot; I could not save that team", - ) - - -@app.post("/api/v1/teams/new-season/{new_season}") -async def v1_teams_new_season(new_season: 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 post teams. This event has been logged.", - ) - - r_query = Team.update( - ranking=1000, season=new_season, wallet=Team.wallet + 250 - ).execute() - current = Current.latest() - current.season = new_season - current.save() - db.close() - - return { - "detail": f"Team rankings, season, and wallet updated for season {new_season}" - } - - -@app.post("/api/v1/teams/{team_id}/money/{delta}") -async def v1_teams_money_delta( - team_id: int, delta: 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 adjust wallets. This event has been logged.", - ) - - try: - this_team = Team.get_by_id(team_id) - except Exception: - db.close() - 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( - status_code=418, - detail="Well slap my ass and call me a teapot; I could not save that team", - ) - - -@app.patch("/api/v1/teams/{team_id}") -async def v1_teams_patch( - team_id, - sname: Optional[str] = None, - lname: Optional[str] = None, - gmid: Optional[int] = None, - gmname: Optional[str] = None, - gsheet: Optional[str] = None, - team_value: Optional[int] = None, - collection_value: Optional[int] = None, - logo: Optional[str] = None, - color: Optional[str] = None, - season: Optional[int] = None, - ps_shiny: Optional[int] = None, - wallet_delta: Optional[int] = None, - has_guide: Optional[bool] = None, - is_ai: Optional[bool] = None, - ranking: 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 delete teams. This event has been logged.", - ) - try: - this_team = Team.get_by_id(team_id) - except Exception: - db.close() - raise HTTPException(status_code=404, detail=f"No team found with id {team_id}") - - if sname is not None: - this_team.sname = sname - if lname is not None: - this_team.lname = lname - if gmid is not None: - this_team.gmid = gmid - if gmname is not None: - this_team.gmname = gmname - if gsheet is not None: - this_team.gsheet = gsheet - if team_value is not None: - this_team.team_value = team_value - if collection_value is not None: - this_team.collection_value = collection_value - if logo is not None: - this_team.logo = logo - if color is not None: - this_team.color = color - if season is not None: - this_team.season = season - if ps_shiny is not None: - this_team.career = ps_shiny - if ranking is not None: - this_team.ranking = ranking - if wallet_delta is not None: - this_team.wallet += wallet_delta - if has_guide is not None: - if has_guide: - this_team.has_guide = 1 - else: - this_team.has_guide = 0 - if is_ai is not None: - if is_ai: - this_team.is_ai = 1 - else: - this_team.is_ai = 0 - - if this_team.save() == 1: - return_team = model_to_dict(this_team) - db.close() - return return_team - else: - raise HTTPException( - status_code=418, - detail="Well slap my ass and call me a teapot; I could not save that team", - ) - - -@app.delete("/api/v1/teams/{team_id}") -async def v1_teams_delete(team_id, 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 delete teams. This event has been logged.", - ) - try: - this_team = Team.get_by_id(team_id) - except Exception: - db.close() - 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") - else: - raise HTTPException(status_code=500, detail=f"Team {team_id} was not deleted") - - -""" -RARITY ENDPOINTS -""" - - -class RarityModel(pydantic.BaseModel): - value: int - name: str - color: str - - -@app.get("/api/v1/rarities") -async def v1_rarities_get( - value: Optional[int] = None, - name: Optional[str] = None, - min_value: Optional[int] = None, - max_value: Optional[int] = None, - csv: Optional[bool] = None, -): - all_rarities = Rarity.select() - - 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: - all_rarities = all_rarities.where(Rarity.value == value) - if name is not None: - all_rarities = all_rarities.where(fn.Lower(Rarity.name) == name.lower()) - if min_value is not None: - all_rarities = all_rarities.where(Rarity.value >= min_value) - if max_value is not 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: - data_list = [["id", "value", "name"]] - for line in all_rarities: - data_list.append([line.id, line.value, line.name]) - 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 = {"count": all_rarities.count(), "rarities": []} - for x in all_rarities: - return_val["rarities"].append(model_to_dict(x)) - - db.close() - return return_val - - -@app.get("/api/v1/rarities/{rarity_id}") -async def v1_rarities_get_one(rarity_id, csv: Optional[bool] = False): - try: - this_rarity = Rarity.get_by_id(rarity_id) - except Exception: - db.close() - raise HTTPException( - status_code=404, detail=f"No rarity found with id {rarity_id}" - ) - - if csv: - data_list = [["id", "value", "name"]] - for line in this_rarity: - data_list.append([line.id, line.value, line.name]) - 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 - - -@app.post("/api/v1/rarities") -async def v1_rarities_post(rarity: RarityModel, 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 rarities. This event has been logged.", - ) - - 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(value=rarity.value, name=rarity.name, color=rarity.color) - - saved = this_rarity.save() - if saved == 1: - return_val = model_to_dict(this_rarity) - 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 rarity", - ) - - -@app.patch("/api/v1/rarities/{rarity_id}") -async def v1_rarities_patch( - 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() - 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() - raise HTTPException( - status_code=404, detail=f"No rarity found with id {rarity_id}" - ) - - if value is not None: - this_rarity.value = value - if name is not None: - this_rarity.name = name - if color is not None: - this_rarity.color = color - - if this_rarity.save() == 1: - return_val = model_to_dict(this_rarity) - 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 rarity", - ) - - -@app.delete("/api/v1/rarities/{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() - 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() - 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" - ) - else: - raise HTTPException( - status_code=500, detail=f"Rarity {rarity_id} was not deleted" - ) - - -""" -CARDSET ENDPOINTS -""" - - -class CardsetModel(pydantic.BaseModel): - name: str - description: str - event_id: Optional[int] = None - in_packs: Optional[bool] = True - total_cards: int = 0 - for_purchase: Optional[bool] = True - ranked_legal: Optional[bool] = True - - -@app.get("/api/v1/cardsets") -async def v1_cardsets_get( - name: Optional[str] = None, - in_desc: Optional[str] = None, - event_id: Optional[int] = None, - in_packs: Optional[bool] = None, - ranked_legal: Optional[bool] = None, - csv: Optional[bool] = None, -): - all_cardsets = Cardset.select() - - 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: - all_cardsets = all_cardsets.where(fn.Lower(Cardset.name) == name.lower()) - if in_desc is not None: - all_cardsets = all_cardsets.where( - fn.Lower(Cardset.description).contains(in_desc.lower()) - ) - if event_id is not None: - try: - this_event = Event.get_by_id(event_id) - all_cardsets = all_cardsets.where(Cardset.event == this_event) - except Exception 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: - all_cardsets = all_cardsets.where(Cardset.in_packs == in_packs) - if ranked_legal is not None: - 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: - data_list = [ - [ - "id", - "name", - "description", - "event_id", - "in_packs", - "for_purchase", - "total_cards", - "ranked_legal", - ] - ] - for line in all_cardsets: - data_list.append( - [ - line.id, - line.name, - line.description, - line.event.id if line.event else "", - line.in_packs, - line.for_purchase, - line.total_cards, - line.ranked_legal, - ] - ) - 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 = {"count": all_cardsets.count(), "cardsets": []} - for x in all_cardsets: - return_val["cardsets"].append(model_to_dict(x)) - - db.close() - return return_val - - -@app.get("/api/v1/cardsets/{cardset_id}") -async def v1_cardsets_get_one(cardset_id, csv: Optional[bool] = False): - try: - this_cardset = Cardset.get_by_id(cardset_id) - except Exception: - db.close() - raise HTTPException( - status_code=404, detail=f"No cardset found with id {cardset_id}" - ) - - if csv: - data_list = [ - ["id", "name", "description"], - [this_cardset.id, this_cardset.name, this_cardset.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_cardset) - db.close() - return return_val - - -@app.post("/api/v1/cardsets") -async def v1_cardsets_post(cardset: CardsetModel, 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 cardsets. This event has been logged.", - ) - - 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__) - - saved = this_cardset.save() - if saved == 1: - return_val = model_to_dict(this_cardset) - 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 cardset", - ) - - -@app.patch("/api/v1/cardsets/{cardset_id}") -async def v1_cardsets_patch( - cardset_id, - name: Optional[str] = None, - description: Optional[str] = None, - in_packs: Optional[bool] = None, - 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() - 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() - raise HTTPException( - status_code=404, detail=f"No cardset found with id {cardset_id}" - ) - - if name is not None: - this_cardset.name = name - if description is not None: - this_cardset.description = description - if in_packs is not None: - this_cardset.in_packs = in_packs - if for_purchase is not None: - this_cardset.for_purchase = for_purchase - if total_cards is not None: - this_cardset.total_cards = total_cards - if ranked_legal is not None: - this_cardset.ranked_legal = ranked_legal - - if this_cardset.save() == 1: - return_val = model_to_dict(this_cardset) - 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 rarity", - ) - - -@app.delete("/api/v1/cardsets/{cardset_id}") -async def v1_cardsets_delete(cardset_id, 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 delete cardsets. This event has been logged.", - ) - try: - this_cardset = Cardset.get_by_id(cardset_id) - except Exception: - db.close() - 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" - ) - else: - raise HTTPException( - status_code=500, detail=f"Cardset {cardset_id} was not deleted" - ) - - -""" -PLAYER ENDPOINTS -""" - - -class PlayerPydantic(pydantic.BaseModel): - player_id: int - p_name: str - cost: int - image: str - image2: Optional[str] = None - mlbclub: str - franchise: str - cardset_id: int - set_num: int - rarity_id: int - pos_1: str - pos_2: Optional[str] = None - pos_3: Optional[str] = None - pos_4: Optional[str] = None - pos_5: Optional[str] = None - pos_6: Optional[str] = None - pos_7: Optional[str] = None - pos_8: Optional[str] = None - headshot: Optional[str] = None - vanity_card: Optional[str] = None - strat_code: Optional[str] = None - bbref_id: Optional[str] = None - fangr_id: Optional[str] = None - description: str - quantity: Optional[int] = 999 - - -class PlayerModel(pydantic.BaseModel): - players: List[PlayerPydantic] - - -# NOT A TEMPLATE - BROKE MOLD FOR pos_exclude -@app.get("/api/v1/players") -async def v1_players_get( - name: Optional[str] = None, - value: Optional[int] = None, - min_cost: Optional[int] = None, - max_cost: Optional[int] = None, - has_image2: Optional[bool] = None, - mlbclub: Optional[str] = None, - franchise: Optional[str] = None, - cardset_id: list = Query(default=None), - rarity_id: list = Query(default=None), - pos_include: list = Query(default=None), - pos_exclude: list = Query(default=None), - has_headshot: Optional[bool] = None, - has_vanity_card: Optional[bool] = None, - strat_code: Optional[str] = None, - bbref_id: Optional[str] = None, - fangr_id: Optional[str] = None, - inc_dex: Optional[bool] = True, - in_desc: Optional[str] = None, - flat: Optional[bool] = False, - sort_by: Optional[str] = False, - cardset_id_exclude: list = Query(default=None), - limit: Optional[int] = None, - csv: Optional[bool] = None, -): - 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: - all_players = all_players.where(fn.Lower(Player.p_name) == name.lower()) - if value is not None: - all_players = all_players.where(Player.cost == value) - if min_cost is not None: - all_players = all_players.where(Player.cost >= min_cost) - if max_cost is not None: - all_players = all_players.where(Player.cost <= max_cost) - if has_image2 is not None: - all_players = all_players.where(Player.image2.is_null(not has_image2)) - if mlbclub is not None: - all_players = all_players.where(fn.Lower(Player.mlbclub) == mlbclub.lower()) - if franchise is not None: - all_players = all_players.where(fn.Lower(Player.franchise) == franchise.lower()) - if cardset_id is not None: - all_players = all_players.where(Player.cardset_id << cardset_id) - if cardset_id_exclude is not None: - all_players = all_players.where(Player.cardset_id.not_in(cardset_id_exclude)) - if rarity_id is not None: - all_players = all_players.where(Player.rarity_id << rarity_id) - if pos_include is not None: - p_list = [x.upper() for x in pos_include] - all_players = all_players.where( - (Player.pos_1 << p_list) - | (Player.pos_2 << p_list) - | (Player.pos_3 << p_list) - | (Player.pos_4 << p_list) - | (Player.pos_5 << p_list) - | (Player.pos_6 << p_list) - | (Player.pos_7 << p_list) - | (Player.pos_8 << p_list) - ) - if has_headshot is not None: - all_players = all_players.where(Player.headshot.is_null(not has_headshot)) - if has_vanity_card is not None: - all_players = all_players.where(Player.vanity_card.is_null(not has_vanity_card)) - if strat_code is not None: - all_players = all_players.where(Player.strat_code == strat_code) - if bbref_id is not None: - all_players = all_players.where(Player.bbref_id == bbref_id) - if fangr_id is not None: - all_players = all_players.where(Player.fangr_id == fangr_id) - if in_desc is not None: - all_players = all_players.where( - fn.Lower(Player.description).contains(in_desc.lower()) - ) - - if sort_by is not None: - if sort_by == "cost-desc": - all_players = all_players.order_by(-Player.cost) - elif sort_by == "cost-asc": - all_players = all_players.order_by(Player.cost) - elif sort_by == "name-asc": - all_players = all_players.order_by(Player.p_name) - elif sort_by == "name-desc": - all_players = all_players.order_by(-Player.p_name) - elif sort_by == "rarity-desc": - all_players = all_players.order_by(Player.rarity) - elif sort_by == "rarity-asc": - all_players = all_players.order_by(-Player.rarity) - - final_players = [] - # logging.info(f'pos_exclude: {type(pos_exclude)} - {pos_exclude} - is None: {pos_exclude is None}') - for x in all_players: - if pos_exclude is not None and set( - [x.upper() for x in pos_exclude] - ).intersection(x.get_all_pos()): - pass - else: - final_players.append(x) - - if limit is not None and len(final_players) >= limit: - break - - # if len(final_players) == 0: - # db.close() - # raise HTTPException(status_code=404, detail=f'No players found') - - if csv: - all_players.order_by(-Player.rarity.value, Player.p_name) - data_list = [ - [ - "id", - "name", - "value", - "image", - "image2", - "mlbclub", - "franchise", - "cardset", - "rarity", - "pos_1", - "pos_2", - "pos_3", - "pos_4", - "pos_5", - "pos_6", - "pos_7", - "pos_8", - "headshot", - "vanity_card", - "strat_code", - "bbref_id", - "description", - "for_purchase", - "ranked_legal", - ] - ] - for line in final_players: - data_list.append( - [ - line.player_id, - line.p_name, - line.cost, - line.image, - line.image2, - line.mlbclub, - line.franchise, - line.cardset, - line.rarity, - line.pos_1, - line.pos_2, - line.pos_3, - line.pos_4, - line.pos_5, - line.pos_6, - line.pos_7, - line.pos_8, - line.headshot, - line.vanity_card, - line.strat_code, - line.bbref_id, - line.description, - line.cardset.for_purchase, - line.cardset.ranked_legal, - # line.description, line.cardset.in_packs, line.quantity - ] - ) - 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 = {"count": len(final_players), "players": []} - for x in final_players: - this_record = model_to_dict(x, recurse=not flat) - - if inc_dex: - this_dex = Paperdex.select().where(Paperdex.player == x) - this_record["paperdex"] = {"count": this_dex.count(), "paperdex": []} - for y in this_dex: - this_record["paperdex"]["paperdex"].append( - model_to_dict(y, recurse=False) - ) - - return_val["players"].append(this_record) - - # return_val['players'].append(model_to_dict(x, recurse=not flat)) - - db.close() - return return_val - - -@app.get("/api/v1/players/random") -async def v1_players_get_random( - min_cost: Optional[int] = None, - max_cost: Optional[int] = None, - in_packs: Optional[bool] = None, - min_rarity: Optional[int] = None, - max_rarity: Optional[int] = None, - limit: Optional[int] = None, - pos_include: Optional[str] = None, - pos_exclude: Optional[str] = None, - franchise: Optional[str] = None, - mlbclub: Optional[str] = None, - cardset_id: list = Query(default=None), - pos_inc: list = Query(default=None), - pos_exc: list = Query(default=None), - csv: Optional[bool] = None, -): - all_players = ( - Player.select().join(Cardset).switch(Player).join(Rarity).order_by(fn.Random()) - ) - - if min_cost is not None: - all_players = all_players.where(Player.cost >= min_cost) - if max_cost is not None: - all_players = all_players.where(Player.cost <= max_cost) - if in_packs is not None: - if in_packs: - all_players = all_players.where(Player.cardset.in_packs) - if min_rarity is not None: - all_players = all_players.where(Player.rarity.value >= min_rarity) - if max_rarity is not None: - all_players = all_players.where(Player.rarity.value <= max_rarity) - if pos_include is not None: - all_players = all_players.where( - (fn.lower(Player.pos_1) == pos_include.lower()) - | (fn.lower(Player.pos_2) == pos_include.lower()) - | (fn.lower(Player.pos_3) == pos_include.lower()) - | (fn.lower(Player.pos_4) == pos_include.lower()) - | (fn.lower(Player.pos_5) == pos_include.lower()) - | (fn.lower(Player.pos_6) == pos_include.lower()) - | (fn.lower(Player.pos_7) == pos_include.lower()) - | (fn.lower(Player.pos_8) == pos_include.lower()) - ) - if franchise is not None: - all_players = all_players.where(fn.Lower(Player.franchise) == franchise.lower()) - if mlbclub is not None: - all_players = all_players.where(fn.Lower(Player.mlbclub) == mlbclub.lower()) - if cardset_id is not None: - all_players = all_players.where(Player.cardset_id << cardset_id) - if pos_inc is not None: - p_list = [x.upper() for x in pos_inc] - all_players = all_players.where( - (Player.pos_1 << p_list) - | (Player.pos_2 << p_list) - | (Player.pos_3 << p_list) - | (Player.pos_4 << p_list) - | (Player.pos_5 << p_list) - | (Player.pos_6 << p_list) - | (Player.pos_7 << p_list) - | (Player.pos_8 << p_list) - ) - # if pos_exc is not None: - # p_list = [x.upper() for x in pos_exc] - # logging.info(f'starting query: {all_players}\n\np_list: {p_list}\n\n') - # all_players = all_players.where( - # Player.pos_1.not_in(p_list) & Player.pos_2.not_in(p_list) & Player.pos_3.not_in(p_list) & - # Player.pos_4.not_in(p_list) & Player.pos_5.not_in(p_list) & Player.pos_6.not_in(p_list) & - # Player.pos_7.not_in(p_list) & Player.pos_8.not_in(p_list) - # ) - # logging.info(f'post pos query: {all_players}') - - if pos_exclude is not None and pos_exc is None: - final_players = [x for x in all_players if pos_exclude not in x.get_all_pos()] - elif pos_exc is not None and pos_exclude is None: - final_players = [] - p_list = [x.upper() for x in pos_exc] - for x in all_players: - if limit is not None and len(final_players) >= limit: - break - if not set(p_list).intersection(x.get_all_pos()): - final_players.append(x) - else: - final_players = all_players - - if limit is not None: - final_players = final_players[:limit] - - # if len(final_players) == 0: - # db.close() - # raise HTTPException(status_code=404, detail=f'No players found') - - if csv: - data_list = [ - [ - "id", - "name", - "cost", - "image", - "image2", - "mlbclub", - "franchise", - "cardset", - "rarity", - "pos_1", - "pos_2", - "pos_3", - "pos_4", - "pos_5", - "pos_6", - "pos_7", - "pos_8", - "headshot", - "vanity_card", - "strat_code", - "bbref_id", - "description", - ] - ] - for line in final_players: - data_list.append( - [ - line.id, - line.p_name, - line.cost, - line.image, - line.image2, - line.mlbclub, - line.franchise, - line.cardset.name, - line.rarity.name, - line.pos_1, - line.pos_2, - line.pos_3, - line.pos_4, - line.pos_5, - line.pos_6, - line.pos_7, - line.pos_8, - line.headshot, - line.vanity_card, - line.strat_code, - line.bbref_id, - line.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 = {"count": len(final_players), "players": []} - for x in final_players: - this_record = model_to_dict(x) - - this_dex = Paperdex.select().where(Paperdex.player == x) - this_record["paperdex"] = {"count": this_dex.count(), "paperdex": []} - for y in this_dex: - this_record["paperdex"]["paperdex"].append( - model_to_dict(y, recurse=False) - ) - - return_val["players"].append(this_record) - # return_val['players'].append(model_to_dict(x)) - - db.close() - return return_val - - -@app.get("/api/v1/players/{player_id}") -async def v1_players_get_one(player_id, csv: Optional[bool] = False): - try: - this_player = Player.get_by_id(player_id) - except Exception: - db.close() - raise HTTPException( - status_code=404, detail=f"No player found with id {player_id}" - ) - - if csv: - data_list = [ - [ - "id", - "name", - "cost", - "image", - "image2", - "mlbclub", - "franchise", - "cardset", - "rarity", - "pos_1", - "pos_2", - "pos_3", - "pos_4", - "pos_5", - "pos_6", - "pos_7", - "pos_8", - "headshot", - "vanity_card", - "strat_code", - "bbref_id", - "description", - ] - ] - return_val = DataFrame(data_list).to_csv(header=False, index=False) - data_list.append( - [ - this_player.id, - this_player.p_name, - this_player.cost, - this_player.image, - this_player.image2, - this_player.mlbclub, - this_player.franchise, - this_player.cardset.name, - this_player.rarity.name, - this_player.pos_1, - this_player.pos_2, - this_player.pos_3, - this_player.pos_4, - this_player.pos_5, - this_player.pos_6, - this_player.pos_7, - this_player.pos_8, - this_player.headshot, - this_player.vanity_card, - this_player.strat_code, - this_player.bbref_id, - this_player.description, - ] - ) - - db.close() - return Response(content=return_val, media_type="text/csv") - else: - return_val = model_to_dict(this_player) - this_dex = Paperdex.select().where(Paperdex.player == this_player) - 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 - - -@app.patch("/api/v1/players/{player_id}") -async def v1_players_patch( - player_id, - name: Optional[str] = None, - image: Optional[str] = None, - image2: Optional[str] = None, - mlbclub: Optional[str] = None, - franchise: Optional[str] = None, - cardset_id: Optional[int] = None, - rarity_id: Optional[int] = None, - pos_1: Optional[str] = None, - pos_2: Optional[str] = None, - pos_3: Optional[str] = None, - pos_4: Optional[str] = None, - pos_5: Optional[str] = None, - pos_6: Optional[str] = None, - pos_7: Optional[str] = None, - pos_8: Optional[str] = None, - headshot: Optional[str] = None, - vanity_card: Optional[str] = None, - strat_code: Optional[str] = None, - bbref_id: Optional[str] = None, - description: Optional[str] = None, - cost: Optional[int] = None, - fangr_id: Optional[str] = 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 players. This event has been logged.", - ) - - try: - this_player = Player.get_by_id(player_id) - except Exception: - db.close() - raise HTTPException( - status_code=404, detail=f"No player found with id {player_id}" - ) - - if cost is not None: - this_player.cost = cost - if name is not None: - this_player.p_name = name - if image is not None: - this_player.image = image - if image2 is not None: - if image2.lower() == "false": - this_player.image2 = None - else: - this_player.image2 = image2 - if mlbclub is not None: - this_player.mlbclub = mlbclub - if franchise is not None: - this_player.franchise = franchise - if cardset_id is not None: - try: - this_cardset = Cardset.get_by_id(cardset_id) - except Exception: - db.close() - raise HTTPException( - status_code=404, detail=f"No cardset found with id {cardset_id}" - ) - this_player.cardset = this_cardset - if rarity_id is not None: - try: - this_rarity = Rarity.get_by_id(rarity_id) - except Exception: - db.close() - raise HTTPException( - status_code=404, detail=f"No rarity found with id {rarity_id}" - ) - this_player.rarity = this_rarity - if pos_1 is not None: - if pos_1 == "False": - this_player.pos_1 = None - else: - this_player.pos_1 = pos_1 - if pos_2 is not None: - if pos_2 == "False": - this_player.pos_2 = None - else: - this_player.pos_2 = pos_2 - if pos_3 is not None: - if pos_3 == "False": - this_player.pos_3 = None - else: - this_player.pos_3 = pos_3 - if pos_4 is not None: - if pos_4 == "False": - this_player.pos_4 = None - else: - this_player.pos_4 = pos_4 - if pos_5 is not None: - if pos_5 == "False": - this_player.pos_5 = None - else: - this_player.pos_5 = pos_5 - if pos_6 is not None: - if pos_6 == "False": - this_player.pos_6 = None - else: - this_player.pos_6 = pos_6 - if pos_7 is not None: - if pos_7 == "False": - this_player.pos_7 = None - else: - this_player.pos_7 = pos_7 - if pos_8 is not None: - if pos_8 == "False": - this_player.pos_8 = None - else: - this_player.pos_8 = pos_8 - if headshot is not None: - this_player.headshot = headshot - if vanity_card is not None: - this_player.vanity_card = vanity_card - if strat_code is not None: - this_player.strat_code = strat_code - if bbref_id is not None: - this_player.bbref_id = bbref_id - if fangr_id is not None: - this_player.fangr_id = fangr_id - if description is not None: - this_player.description = description - - if this_player.save() == 1: - return_val = model_to_dict(this_player) - 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 rarity", - ) - - -@app.put("/api/v1/players") -async def v1_players_put(players: PlayerModel, 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 players. This event has been logged.", - ) - - new_players = [] - for x in players.players: - # this_player = Player( - # player_id=x.player_id, - # p_name=x.p_name, - # cost=x.cost, - # image=x.image, - # image2=x.image2, - # mlbclub=x.mlbclub, - # franchise=x.franchise, - # cardset_id=x.cardset_id, - # rarity_id=x.rarity_id, - # set_num=x.set_num, - # pos_1=x.pos_1, - # pos_2=x.pos_2, - # pos_3=x.pos_3, - # pos_4=x.pos_4, - # pos_5=x.pos_5, - # pos_6=x.pos_6, - # pos_7=x.pos_7, - # pos_8=x.pos_8, - # headshot=x.headshot, - # vanity_card=x.vanity_card, - # strat_code=x.strat_code, - # fangr_id=x.fangr_id, - # bbref_id=x.bbref_id, - # description=x.description - # ) - # new_players.append(this_player) - new_players.append( - { - "player_id": x.player_id, - "p_name": x.p_name, - "cost": x.cost, - "image": x.image, - "image2": x.image2, - "mlbclub": x.mlbclub.title(), - "franchise": normalize_franchise(x.franchise), - "cardset_id": x.cardset_id, - "rarity_id": x.rarity_id, - "set_num": x.set_num, - "pos_1": x.pos_1, - "pos_2": x.pos_2, - "pos_3": x.pos_3, - "pos_4": x.pos_4, - "pos_5": x.pos_5, - "pos_6": x.pos_6, - "pos_7": x.pos_7, - "pos_8": x.pos_8, - "headshot": x.headshot, - "vanity_card": x.vanity_card, - "strat_code": x.strat_code, - "fangr_id": x.fangr_id, - "bbref_id": x.bbref_id, - "description": x.description, - } - ) - - logging.info(f"new_players: {new_players}") - - with db.atomic(): - # Use PostgreSQL-compatible upsert helper - upsert_players(new_players, batch_size=15) - db.close() - - # sheets.update_all_players(SHEETS_AUTH) - raise HTTPException( - status_code=200, detail=f"{len(new_players)} players have been added" - ) - - -# @app.put('/api/v1/players') -# async def v1_players_put(players: PlayerModel, 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 players. This event has been logged.' -# ) -# -# new_players = [] -# for x in players.players: -# try: -# this_player = Player.get_by_id(x.player_id) -# except Exception as e: -# new_players.append({ -# 'player_id': x.player_id, -# 'p_name': x.p_name, -# 'cost': x.cost, -# 'image': x.image, -# 'image2': x.image2, -# 'mlbclub': x.mlbclub.title(), -# 'franchise': x.franchise.title(), -# 'cardset_id': x.cardset_id, -# 'rarity_id': x.rarity_id, -# 'set_num': x.set_num, -# 'pos_1': x.pos_1, -# 'pos_2': x.pos_2, -# 'pos_3': x.pos_3, -# 'pos_4': x.pos_4, -# 'pos_5': x.pos_5, -# 'pos_6': x.pos_6, -# 'pos_7': x.pos_7, -# 'pos_8': x.pos_8, -# 'headshot': x.headshot, -# 'vanity_card': x.vanity_card, -# 'strat_code': x.strat_code, -# 'fangr_id': x.fangr_id, -# 'bbref_id': x.bbref_id, -# 'description': x.description -# }) -# finally: -# - - -# @app.patch('/api/v1/players') -# async def v1_players_put(players: PlayerModel, 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 players. This event has been logged.' -# ) -# -# new_players = [] -# for x in players.players: - - -@app.delete("/api/v1/players/{player_id}") -async def v1_players_delete(player_id, 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 delete players. This event has been logged.", - ) - - try: - this_player = Player.get_by_id(player_id) - except Exception: - db.close() - 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( - status_code=200, detail=f"Player {player_id} has been deleted" - ) - else: - raise HTTPException( - status_code=500, detail=f"Player {player_id} was not deleted" - ) - - -""" -PACKTYPE ENDPOINTS -""" - - -class PacktypeModel(pydantic.BaseModel): - name: str - card_count: int - description: str - cost: int - available: Optional[bool] = True - - -@app.get("/api/v1/packtypes") -async def v1_packtypes_get( - name: Optional[str] = None, - card_count: Optional[int] = None, - in_desc: Optional[str] = None, - available: Optional[bool] = None, - csv: Optional[bool] = None, -): - all_packtypes = PackType.select() - - 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: - all_packtypes = all_packtypes.where(fn.Lower(PackType.name) == name.lower()) - if card_count is not None: - all_packtypes = all_packtypes.where(PackType.card_count == card_count) - if in_desc is not None: - all_packtypes = all_packtypes.where( - fn.Lower(PackType.description).contains(in_desc.lower()) - ) - if available is not None: - all_packtypes = all_packtypes.where(PackType.available == available) - - # if all_packtypes.count() == 0: - # db.close() - # raise HTTPException(status_code=404, detail=f'No packtypes found') - - if csv: - data_list = [["id", "name", "card_count", "description"]] - for line in all_packtypes: - data_list.append([line.id, line.name, line.card_count, line.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 = {"count": all_packtypes.count(), "packtypes": []} - for x in all_packtypes: - return_val["packtypes"].append(model_to_dict(x)) - - db.close() - return return_val - - -@app.get("/api/v1/packtypes/{packtype_id}") -async def v1_packtypes_get_one(packtype_id, csv: Optional[bool] = False): - try: - this_packtype = PackType.get_by_id(packtype_id) - except Exception: - db.close() - raise HTTPException( - status_code=404, detail=f"No packtype found with id {packtype_id}" - ) - - if csv: - data_list = [ - ["id", "name", "card_count", "description"], - [ - this_packtype.id, - this_packtype.name, - this_packtype.card_count, - this_packtype.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_packtype) - db.close() - return return_val - - -@app.post("/api/v1/packtypes") -async def v1_packtypes_post( - packtype: PacktypeModel, 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 packtypes. This event has been logged.", - ) - - 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( - name=packtype.name, - card_count=packtype.card_count, - description=packtype.description, - cost=packtype.cost, - available=packtype.available, - ) - - saved = this_packtype.save() - if saved == 1: - return_val = model_to_dict(this_packtype) - 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 cardset", - ) - - -@app.patch("/api/v1/packtypes/{packtype_id}") -async def v1_packtypes_patch( - 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() - 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() - raise HTTPException( - status_code=404, detail=f"No packtype found with id {packtype_id}" - ) - - if name is not None: - this_packtype.name = name - if card_count is not None: - this_packtype.card_count = card_count - if description is not None: - this_packtype.description = description - if cost is not None: - this_packtype.cost = cost - if available is not None: - this_packtype.available = available - - if this_packtype.save() == 1: - return_val = model_to_dict(this_packtype) - 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 rarity", - ) - - -@app.delete("/api/v1/packtypes/{packtype_id}") -async def v1_packtypes_delete(packtype_id, 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 delete packtypes. This event has been logged.", - ) - try: - this_packtype = PackType.get_by_id(packtype_id) - except Exception: - db.close() - 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" - ) - else: - raise HTTPException( - status_code=500, detail=f"Packtype {packtype_id} was not deleted" - ) - - -""" -PACK ENDPOINTS -""" - - -class PackPydantic(pydantic.BaseModel): - team_id: int - pack_type_id: int - pack_team_id: Optional[int] = None - pack_cardset_id: Optional[int] = None - open_time: Optional[str] = None - - -class PackModel(pydantic.BaseModel): - packs: List[PackPydantic] - - -@app.get("/api/v1/packs") -async def v1_packs_get( - team_id: Optional[int] = None, - pack_type_id: Optional[int] = None, - opened: Optional[bool] = None, - limit: Optional[int] = None, - new_to_old: Optional[bool] = None, - pack_team_id: Optional[int] = None, - pack_cardset_id: Optional[int] = None, - exact_match: Optional[bool] = False, - csv: Optional[bool] = None, -): - 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() - 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() - 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() - 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: - all_packs = all_packs.where(Pack.pack_team == None) - - if pack_cardset_id is not None: - try: - this_pack_cardset = Cardset.get_by_id(pack_cardset_id) - except Exception: - db.close() - 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: - all_packs = all_packs.where(Pack.pack_cardset == None) - - if opened is not None: - all_packs = all_packs.where(Pack.open_time.is_null(not opened)) - if limit is not None: - all_packs = all_packs.limit(limit) - if new_to_old is not None: - all_packs = all_packs.order_by(-Pack.id) - - # if all_packs.count() == 0: - # db.close() - # raise HTTPException(status_code=404, detail=f'No packs found') - - if csv: - data_list = [["id", "team", "pack_type", "open_time"]] - for line in all_packs: - data_list.append( - [ - line.id, - line.team.abbrev, - line.pack_type.name, - datetime.fromtimestamp(line.open_time) if line.open_time else 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 = {"count": all_packs.count(), "packs": []} - for x in all_packs: - return_val["packs"].append(model_to_dict(x)) - - db.close() - return return_val - - -@app.get("/api/v1/packs/{pack_id}") -async def v1_packs_get_one(pack_id, csv: Optional[bool] = False): - try: - this_pack = Pack.get_by_id(pack_id) - except Exception: - db.close() - raise HTTPException(status_code=404, detail=f"No pack found with id {pack_id}") - - if csv: - data_list = [ - ["id", "team", "pack_type", "open_time"], - [ - this_pack.id, - this_pack.team.abbrev, - this_pack.pack_type.name, - datetime.fromtimestamp(this_pack.open_time) - if this_pack.open_time - else 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_pack) - db.close() - return return_val - - -@app.post("/api/v1/packs") -async def v1_packs_post(packs: PackModel, 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 packs. This event has been logged.", - ) - - new_packs = [] - for x in packs.packs: - this_player = Pack( - team_id=x.team_id, - pack_type_id=x.pack_type_id, - pack_team_id=x.pack_team_id, - pack_cardset_id=x.pack_cardset_id, - open_time=x.open_time if x.open_time != "" else None, - ) - new_packs.append(this_player) - - 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" - ) - - -@app.post("/api/v1/packs/one") -async def v1_packs_post_one(pack: PackPydantic, 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 packs. This event has been logged.", - ) - - this_pack = Pack( - team_id=pack.team_id, - pack_type_id=pack.pack_type_id, - pack_team_id=pack.pack_team_id, - pack_cardset_id=pack.pack_cardset_id, - open_time=pack.open_time, - ) - - saved = this_pack.save() - if saved == 1: - return_val = model_to_dict(this_pack) - 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 cardset", - ) - - -@app.patch("/api/v1/packs/{pack_id}") -async def v1_packs_patch( - 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() - 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() - raise HTTPException(status_code=404, detail=f"No pack found with id {pack_id}") - - if team_id is not None: - this_pack.team_id = team_id - if pack_type_id is not None: - this_pack.pack_type_id = pack_type_id - if pack_team_id is not None: - this_pack.pack_team_id = pack_team_id - if pack_cardset_id is not None: - this_pack.pack_cardset_id = pack_cardset_id - if open_time is not None: - this_pack.open_time = open_time - - if this_pack.save() == 1: - return_val = model_to_dict(this_pack) - 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 rarity", - ) - - -@app.delete("/api/v1/packs/{pack_id}") -async def v1_packs_delete(pack_id, 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 delete packs. This event has been logged.", - ) - try: - this_pack = Pack.get_by_id(pack_id) - except Exception: - db.close() - 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") - else: - raise HTTPException(status_code=500, detail=f"Pack {pack_id} was not deleted") - - -""" -CARD ENDPOINTS -""" - - -class CardPydantic(pydantic.BaseModel): - player_id: int - team_id: int - pack_id: int - value: Optional[int] = 0 - - -class CardModel(pydantic.BaseModel): - cards: List[CardPydantic] - - -@app.get("/api/v1/cards") -async def v1_cards_get( - player_id: Optional[int] = None, - team_id: Optional[int] = None, - pack_id: Optional[int] = None, - value: Optional[int] = None, - min_value: Optional[int] = None, - max_value: Optional[int] = None, - order_by: Optional[str] = None, - limit: Optional[int] = None, - dupes: Optional[bool] = None, - csv: Optional[bool] = None, -): - all_cards = Card.select() - - # if all_cards.count() == 0: - # db.close() - # raise HTTPException(status_code=404, detail=f'There are no cards to filter') - - if team_id is not None: - try: - this_team = Team.get_by_id(team_id) - except Exception: - db.close() - 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() - 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() - 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: - all_cards = all_cards.where(Card.value == value) - if min_value is not None: - all_cards = all_cards.where(Card.value >= min_value) - if max_value is not None: - all_cards = all_cards.where(Card.value <= max_value) - if order_by is not None: - if order_by.lower() == "new": - all_cards = all_cards.order_by(-Card.id) - if limit is not None: - all_cards = all_cards.limit(limit) - if dupes: - if team_id is None: - raise HTTPException( - status_code=400, detail="Dupe checking must include a team_id" - ) - logging.info(f"dupe check") - p_query = Card.select(Card.player).where(Card.team_id == team_id) - seen = set() - dupes = [] - for x in p_query: - if x.player.player_id in seen: - dupes.append(x.player_id) - else: - seen.add(x.player_id) - all_cards = all_cards.where(Card.player_id << dupes) - - # if all_cards.count() == 0: - # db.close() - # raise HTTPException(status_code=404, detail=f'No cards found') - - if csv: - data_list = [["id", "player", "cardset", "rarity", "team", "pack", "value"]] - for line in all_cards: - data_list.append( - [ - line.id, - line.player.p_name, - line.player.cardset, - line.player.rarity, - line.team.abbrev, - line.pack, - line.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 = {"count": all_cards.count(), "cards": []} - for x in all_cards: - this_record = model_to_dict(x) - logging.debug(f"this_record: {this_record}") - - this_dex = Paperdex.select().where(Paperdex.player == x) - this_record["player"]["paperdex"] = { - "count": this_dex.count(), - "paperdex": [], - } - for y in this_dex: - this_record["player"]["paperdex"]["paperdex"].append( - model_to_dict(y, recurse=False) - ) - - return_val["cards"].append(this_record) - - # return_val['cards'].append(model_to_dict(x)) - - db.close() - return return_val - - -@app.get("/api/v1/cards/{card_id}") -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() - 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, - ], - ] - 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 - - -@app.post("/api/v1/cards") -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() - raise HTTPException( - status_code=401, - detail="You are not authorized to post cards. This event has been logged.", - ) - last_card = Card.select(Card.id).order_by(-Card.id).limit(1) - lc_id = last_card[0].id - - new_cards = [] - player_ids = [] - inc_dex = True - this_team = Team.get_by_id(cards.cards[0].team_id) - if this_team.is_ai or "Gauntlet" in this_team.abbrev: - inc_dex = False - - # new_dex = [] - # now = int(datetime.timestamp(datetime.now()) * 1000) - for x in cards.cards: - this_card = Card( - player_id=x.player_id, team_id=x.team_id, pack_id=x.pack_id, value=x.value - ) - if inc_dex: - Paperdex.get_or_create(team_id=x.team_id, player_id=x.player_id) - player_ids.append(x.player_id) - new_cards.append(this_card) - - with db.atomic(): - Card.bulk_create(new_cards, batch_size=15) - 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" - ) - - -@app.post("/api/v1/cards/ai-update") -async def v1_cards_ai_update(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 update AI cards. This event has been logged.", - ) - - sheets.send_ai_cards(SHEETS_AUTH) - raise HTTPException(status_code=200, detail=f"Just sent AI cards to sheets") - - -@app.post("/api/v1/cards/legal-check/{rarity_name}") -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() - raise HTTPException(status_code=401, detail="Unauthorized") - if rarity_name not in ["ranked"]: - return f"Rarity name {rarity_name} not a valid check" - - bad_cards = [] - all_cards = Card.select().where(Card.id << card_id) - - for x in all_cards: - if x.player.cardset_id not in [3, 4, 9, 10]: - bad_cards.append(x.player.description) - - return {"count": len(bad_cards), "bad_cards": bad_cards} - - -@app.post("/api/v1/cards/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() - 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}", - ) - - -@app.post("/api/v1/cards/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() - raise HTTPException( - status_code=401, - detail="You are not authorized to delete card lists. This event has been logged.", - ) - - logging.info(f"del_ids: {del_ids} / type: {type(del_ids)}") - # sheets.post_deletion(SHEETS_AUTH, del_ids.split(',')) - - -@app.post("/api/v1/cards/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() - raise HTTPException( - status_code=401, - detail="You are not authorized to wipe teams. This event has been logged.", - ) - - try: - this_team = Team.get_by_id(team_id) - except Exception 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" - - -# @app.get('/api/v1/cards/{card_id}/sell') -# async def v1_cards_sell(card_id, ts: int = None): -# try: -# this_card = Card.get_by_id(card_id) -# except Exception: -# db.close() -# raise HTTPException(status_code=404, detail=f'No card found with id {card_id}') -# -# this_team = this_card.team -# 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.' -# ) -# -# this_player = this_card.player -# orig_price = this_player.cost -# sell_price = round(this_player.cost * .5) -# -# # credit selling team's wallet -# if this_team.wallet is None: -# this_team.wallet = sell_price -# else: -# this_team.wallet += sell_price -# this_team.save() -# -# # decrease price of player -# this_player.change_on_sell() -# this_card.delete_instance() -# -# # post a notification -# new_notif = Notification( -# created=int_timestamp(datetime.now()), -# title=f'Price Change', -# desc='Modified by buying and selling', -# field_name=f'{this_player.p_name}', -# message=f'From {orig_price}₼ to **{this_player.cost}**₼', -# about=f'Player-{this_player.id}' -# ) -# new_notif.save() -# -# raise HTTPException(status_code=200, detail=f'Card {card_id} has been sold for {sell_price} bucks') - - -@app.patch("/api/v1/cards/{card_id}") -async def v1_cards_patch( - card_id, - player_id: Optional[int] = None, - team_id: Optional[int] = None, - pack_id: Optional[int] = None, - value: 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() - 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() - raise HTTPException(status_code=404, detail=f"No card found with id {card_id}") - - if player_id is not None: - this_card.player_id = player_id - if team_id is not None: - if team_id == 0: - this_card.team_id = None - else: - this_card.team_id = team_id - if pack_id is not None: - this_card.pack_id = pack_id - if value is not None: - this_card.value = value - if roster1_id is not None: - this_card.roster1_id = roster1_id - if roster2_id is not None: - this_card.roster2_id = roster2_id - if roster3_id is not None: - this_card.roster3_id = roster3_id - - 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", - ) - - -@app.delete("/api/v1/cards/{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() - 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() - 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") - else: - raise HTTPException(status_code=500, detail=f"Card {card_id} was not deleted") - - -""" -EVENTS ENDPOINTS -""" - - -class EventModel(pydantic.BaseModel): - name: str - short_desc: str - long_desc: str - url: Optional[str] = None - thumbnail: Optional[str] = None - active: Optional[bool] = False - - -@app.get("/api/v1/events") -async def v1_events_get( - name: Optional[str] = None, - in_desc: Optional[str] = None, - active: Optional[bool] = None, - csv: Optional[bool] = None, -): - all_events = Event.select() - - if name is not None: - all_events = all_events.where(fn.Lower(Event.name) == name.lower()) - if in_desc is not None: - all_events = all_events.where( - (fn.Lower(Event.short_desc).contains(in_desc.lower())) - | (fn.Lower(Event.long_desc).contains(in_desc.lower())) - ) - if active is not None: - all_events = all_events.where(Event.active == active) - - if csv: - data_list = [ - ["id", "name", "short_desc", "long_desc", "url", "thumbnail", "active"] - ] - for line in all_events: - data_list.append( - [ - line.id, - line.name, - line.short_desc, - line.long_desc, - line.url, - line.thumbnail, - line.active, - ] - ) - 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 = {"count": all_events.count(), "events": []} - for x in all_events: - return_val["events"].append(model_to_dict(x)) - - db.close() - return return_val - - -@app.get("/api/v1/events/{event_id}") -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() - raise HTTPException( - status_code=404, detail=f"No event found with id {event_id}" - ) - - if csv: - data_list = [ - ["id", "name", "short_desc", "long_desc", "url", "thumbnail", "active"], - [ - this_event.id, - this_event.name, - this_event.short_desc, - this_event.long_desc, - this_event.url, - this_event.thumbnail, - this_event.active, - ], - ] - 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 - - -@app.post("/api/v1/events") -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() - raise HTTPException( - status_code=401, - detail="You are not authorized to post events. This event has been logged.", - ) - - dupe_event = Event.get_or_none(Event.name == event.name) - if dupe_event: - db.close() - raise HTTPException( - status_code=400, detail=f"There is already an event using {event.name}" - ) - - this_event = Event( - name=event.name, - short_desc=event.short_desc, - long_desc=event.long_desc, - url=event.url, - thumbnail=event.thumbnail, - active=event.active, - ) - - 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", - ) - - -@app.patch("/api/v1/events/{event_id}") -async def v1_events_patch( - event_id, - name: Optional[str] = None, - short_desc: Optional[str] = None, - long_desc: Optional[str] = None, - url: Optional[str] = None, - thumbnail: Optional[str] = None, - active: Optional[bool] = None, - token: str = Depends(oauth2_scheme), -): - 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 events. This event has been logged.", - ) - try: - this_event = Event.get_by_id(event_id) - except Exception: - db.close() - raise HTTPException( - status_code=404, detail=f"No event found with id {event_id}" - ) - - if name is not None: - this_event.name = name - if short_desc is not None: - this_event.short_desc = short_desc - if long_desc is not None: - this_event.long_desc = long_desc - if url is not None: - this_event.url = url - if thumbnail is not None: - this_event.thumbnail = thumbnail - if active is not None: - this_event.active = active - - 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", - ) - - -@app.delete("/api/v1/events/{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() - 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() - 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" - ) - else: - raise HTTPException(status_code=500, detail=f"Event {event_id} was not deleted") - - -""" -ROSTERS ENDPOINTS -""" - - -class RosterModel(pydantic.BaseModel): - team_id: int - name: Optional[str] = "My Roster" - roster_num: int - card_ids: list - - -@app.get("/api/v1/rosters") -async def v1_rosters_get(team_id: Optional[int] = None, csv: Optional[bool] = None): - all_rosters = Roster.select() - - # if all_rosters.count() == 0: - # db.close() - # raise HTTPException(status_code=404, detail=f'There are no rosters to filter') - - if team_id: - try: - this_team = Team.get_by_id(team_id) - all_rosters = all_rosters.where(Roster.team == this_team) - except Exception: - db.close() - raise HTTPException( - status_code=404, detail=f"No team found with id {team_id}" - ) - - if csv: - data_list = [["id", "roster", "team_id", "team_abbrev"]] - for line in all_rosters: - data_list.append([line.id, line.name, line.team, line.team.abbrev]) - 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 = {"count": all_rosters.count(), "rosters": []} - for x in all_rosters: - return_val["rosters"].append(model_to_dict(x)) - - db.close() - return return_val - - -@app.get("/api/v1/rosters/{roster_id}") -async def v1_rosters_get_one(roster_id, csv: Optional[bool] = None): - try: - this_roster = Roster.get_by_id(roster_id) - except Exception: - db.close() - raise HTTPException( - status_code=404, detail=f"No roster found with id {roster_id}" - ) - - if csv: - data_list = [ - ["id", "roster", "team_id", "team_abbrev"], - [ - this_roster.id, - this_roster.name, - this_roster.team, - this_roster.team.abbrev, - ], - ] - 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_roster) - db.close() - return return_val - - -@app.post("/api/v1/rosters") -async def v1_rosters_post(roster: RosterModel, 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 rosters. This event has been logged.", - ) - - c_query = Card.select().where(Card.id << roster.card_ids) - logging.debug(f"c_query: {c_query}") - for card in c_query: - if card.team_id != roster.team_id: - raise HTTPException( - status_code=401, - detail=f"Card ID {card.id} ({card.player.rarity.name} {card.player.p_name}) belongs to " - f"{card.team.abbrev} and cannot be added to your roster.", - ) - - r_query = Roster.delete().where( - Roster.team_id == roster.team_id, Roster.roster_num == roster.roster_num - ) - logging.debug(f"r_query: {r_query}") - r_query.execute() - - this_roster = Roster( - team_id=roster.team_id, - name=roster.name, - roster_num=roster.roster_num, - card_1_id=roster.card_ids[0], - card_2_id=roster.card_ids[1], - card_3_id=roster.card_ids[2], - card_4_id=roster.card_ids[3], - card_5_id=roster.card_ids[4], - card_6_id=roster.card_ids[5], - card_7_id=roster.card_ids[6], - card_8_id=roster.card_ids[7], - card_9_id=roster.card_ids[8], - card_10_id=roster.card_ids[9], - card_11_id=roster.card_ids[10], - card_12_id=roster.card_ids[11], - card_13_id=roster.card_ids[12], - card_14_id=roster.card_ids[13], - card_15_id=roster.card_ids[14], - card_16_id=roster.card_ids[15], - card_17_id=roster.card_ids[16], - card_18_id=roster.card_ids[17], - card_19_id=roster.card_ids[18], - card_20_id=roster.card_ids[19], - card_21_id=roster.card_ids[20], - card_22_id=roster.card_ids[21], - card_23_id=roster.card_ids[22], - card_24_id=roster.card_ids[23], - card_25_id=roster.card_ids[24], - card_26_id=roster.card_ids[25], - ) - - saved = this_roster.save() - if saved == 1: - return_val = model_to_dict(this_roster, recurse=False) - 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", - ) - - -@app.patch("/api/v1/rosters/{roster_id}") -async def v1_rosters_patch( - roster_id, - team_id: Optional[int] = None, - name: Optional[str] = None, - roster_num: 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 rosters. This event has been logged.", - ) - try: - this_roster = Roster.get_by_id(roster_id) - except Exception: - db.close() - raise HTTPException( - status_code=404, detail=f"No roster found with id {roster_id}" - ) - - if team_id is not None: - this_roster.team_id = team_id - if name is not None: - this_roster.name = name - if roster_num is not None: - this_roster.roster_num = roster_num - - if this_roster.save() == 1: - return_val = model_to_dict(this_roster) - 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", - ) - - -@app.delete("/api/v1/rosters/{roster_id}") -async def v1_rosters_delete(roster_id, 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 delete rosters. This event has been logged.", - ) - try: - this_roster = Roster.get_by_id(roster_id) - except Exception: - db.close() - raise HTTPException( - status_code=404, detail=f"No roster found with id {roster_id}" - ) - - count = this_roster.delete_instance() - db.close() - - if count == 1: - raise HTTPException( - status_code=200, detail=f"Roster {roster_id} has been deleted" - ) - else: - raise HTTPException( - status_code=500, detail=f"Roster {roster_id} was not deleted" - ) - - -""" -RESULTS ENDPOINTS -""" - - -class ResultModel(pydantic.BaseModel): - away_team_id: int - home_team_id: int - away_score: int - home_score: int - away_team_value: Optional[int] = None - home_team_value: Optional[int] = None - away_team_ranking: Optional[int] = None - home_team_ranking: Optional[int] = None - scorecard: str - week: int - season: int - ranked: bool - short_game: bool - game_type: str - - -@app.get("/api/v1/results") -async def v1_results_get( - away_team_id: Optional[int] = None, - home_team_id: Optional[int] = None, - team_one_id: Optional[int] = None, - team_two_id: Optional[int] = None, - away_score_min: Optional[int] = None, - away_score_max: Optional[int] = None, - home_score_min: Optional[int] = None, - home_score_max: Optional[int] = None, - bothscore_min: Optional[int] = None, - bothscore_max: Optional[int] = None, - season: Optional[int] = None, - week: Optional[int] = None, - week_start: Optional[int] = None, - week_end: Optional[int] = None, - ranked: Optional[bool] = None, - short_game: Optional[bool] = None, - game_type: Optional[str] = None, - vs_ai: Optional[bool] = None, - csv: Optional[bool] = None, -): - all_results = Result.select() - - # if all_results.count() == 0: - # db.close() - # raise HTTPException(status_code=404, detail=f'There are no results to filter') - - if away_team_id is not None: - try: - this_team = Team.get_by_id(away_team_id) - all_results = all_results.where(Result.away_team == this_team) - except Exception: - db.close() - 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() - 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() - 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() - raise HTTPException( - status_code=404, detail=f"No team found with id {team_two_id}" - ) - - if away_score_min is not None: - all_results = all_results.where(Result.away_score >= away_score_min) - - if away_score_max is not None: - all_results = all_results.where(Result.away_score <= away_score_max) - - if home_score_min is not None: - all_results = all_results.where(Result.home_score >= home_score_min) - - if home_score_max is not None: - all_results = all_results.where(Result.home_score <= home_score_max) - - if bothscore_min is not None: - all_results = all_results.where( - (Result.home_score >= bothscore_min) & (Result.away_score >= bothscore_min) - ) - - if bothscore_max is not None: - all_results = all_results.where( - (Result.home_score <= bothscore_max) & (Result.away_score <= bothscore_max) - ) - - if season is not None: - all_results = all_results.where(Result.season == season) - - if week is not None: - all_results = all_results.where(Result.week == week) - - if ranked is not None: - all_results = all_results.where(Result.ranked == ranked) - - if short_game is not None: - all_results = all_results.where(Result.short_game == short_game) - - if week_start is not None: - all_results = all_results.where(Result.week >= week_start) - - if week_end is not None: - all_results = all_results.where(Result.week <= week_end) - - if game_type is not None: - all_results = all_results.where(Result.game_type == game_type) - - all_results = all_results.order_by(Result.id) - # Not functional - # if vs_ai is not None: - # AwayTeam = Team.alias() - # all_results = all_results.join( - # Team, on=Result.home_team - # ).switch(Result).join( - # Team, on=(AwayTeam.id == Result.away_team).alias('a_team') - # ) - # - # if vs_ai: - # all_results = all_results.where( - # (Result.home_team.is_ai == 1) | (Result.a_team.is_ai == 1) - # ) - # else: - # all_results = all_results.where( - # (Result.home_team.is_ai == 0) & (Result.a_team.is_ai == 0) - # ) - # logging.info(f'Result Query:\n\n{all_results}') - - if csv: - data_list = [ - [ - "id", - "away_abbrev", - "home_abbrev", - "away_score", - "home_score", - "away_tv", - "home_tv", - "game_type", - "season", - "week", - "short_game", - "ranked", - ] - ] - for line in all_results: - data_list.append( - [ - line.id, - line.away_team.abbrev, - line.home_team.abbrev, - line.away_score, - line.home_score, - line.away_team_value, - line.home_team_value, - line.game_type if line.game_type else "minor-league", - line.season, - line.week, - line.short_game, - line.ranked, - ] - ) - 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 = {"count": all_results.count(), "results": []} - for x in all_results: - return_val["results"].append(model_to_dict(x)) - - db.close() - return return_val - - -@app.get("/api/v1/results/{result_id}") -async def v1_results_get_one(result_id, csv: Optional[bool] = None): - try: - this_result = Result.get_by_id(result_id) - except Exception: - db.close() - raise HTTPException( - status_code=404, detail=f"No result found with id {result_id}" - ) - - if csv: - data_list = [ - [ - "id", - "away_abbrev", - "home_abbrev", - "away_score", - "home_score", - "away_tv", - "home_tv", - "game_type", - "season", - "week", - "game_type", - ], - [ - this_result.id, - this_result.away_team.abbrev, - this_result.away_team.abbrev, - this_result.away_score, - this_result.home_score, - this_result.away_team_value, - this_result.home_team_value, - this_result.game_type if this_result.game_type else "minor-league", - this_result.season, - this_result.week, - this_result.game_type, - ], - ] - 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 - - -@app.get("/api/v1/results/team/{team_id}") -async def v1_results_team_get( - team_id: int, - season: Optional[int] = None, - week: Optional[int] = None, - csv: Optional[bool] = False, -): - all_results = Result.select().where( - (Result.away_team_id == team_id) | (Result.home_team_id == team_id) - ) - try: - this_team = Team.get_by_id(team_id) - except Exception 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") - - if season is not None: - all_results = all_results.where(Result.season == season) - else: - all_results = all_results.where(Result.season == this_team.season) - - if week is not None: - all_results = all_results.where(Result.week == week) - - r_wins, r_loss, c_wins, c_loss = 0, 0, 0, 0 - for x in all_results: - if x.away_team_id == team_id: - if x.away_score > x.home_score: - if x.ranked: - r_wins += 1 - else: - c_wins += 1 - else: - if x.ranked: - r_loss += 1 - else: - c_loss += 1 - elif x.home_team_id == team_id: - if x.away_score > x.home_score: - if x.ranked: - r_loss += 1 - else: - c_loss += 1 - else: - if x.ranked: - r_wins += 1 - else: - c_wins += 1 - - if csv: - data_list = [ - [ - "team_id", - "ranked_wins", - "ranked_losses", - "casual_wins", - "casual_losses", - "team_ranking", - ], - [team_id, r_wins, r_loss, c_wins, c_loss, this_team.ranking], - ] - 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 = { - "team": model_to_dict(this_team), - "ranked_wins": r_wins, - "ranked_losses": r_loss, - "casual_wins": c_wins, - "casual_losses": c_loss, - } - db.close() - return return_val - - -@app.post("/api/v1/results") -async def v1_results_post(result: ResultModel, 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 results. This event has been logged.", - ) - - this_result = Result(**result.__dict__) - saved = this_result.save() - - 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) - - k_value = 20 if result.short_game else 60 - ratio = (result.home_team_ranking - result.away_team_ranking) / 400 - exp_score = 1 / (1 + (10**ratio)) - away_win = True if result.away_score > result.home_score else False - total_delta = k_value * exp_score - high_delta = ( - total_delta * exp_score - if exp_score > 0.5 - else total_delta * (1 - exp_score) - ) - low_delta = total_delta - high_delta - - # exp_score > .5 means away team is favorite - if exp_score > 0.5 and away_win: - final_delta = low_delta - away_delta = low_delta * 3 - home_delta = -low_delta - elif away_win: - final_delta = high_delta - away_delta = high_delta * 3 - home_delta = -high_delta - elif exp_score <= 0.5 and not away_win: - final_delta = low_delta - away_delta = -low_delta - home_delta = low_delta * 3 - elif not away_win: - final_delta = high_delta - away_delta = -high_delta - home_delta = high_delta * 3 - else: - final_delta = 0 - away_delta = 0 - home_delta = 0 - - logging.debug( - f"/results ranking deltas\n\nk_value: {k_value} / ratio: {ratio} / " - f"exp_score: {exp_score} / away_win: {away_win} / total_delta: {total_delta} / " - f"high_delta: {high_delta} / low_delta: {low_delta} / final_delta: {final_delta} / " - ) - - away_team = Team.get_by_id(result.away_team_id) - away_team.ranking += away_delta - away_team.save() - logging.info(f"Just updated {away_team.abbrev} ranking to {away_team.ranking}") - home_team = Team.get_by_id(result.home_team_id) - home_team.ranking += home_delta - home_team.save() - logging.info(f"Just updated {home_team.abbrev} ranking to {home_team.ranking}") - - 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", - ) - - -@app.patch("/api/v1/results/{result_id}") -async def v1_results_patch( - result_id, - away_team_id: Optional[int] = None, - home_team_id: Optional[int] = None, - away_score: Optional[int] = None, - home_score: Optional[int] = None, - away_team_value: Optional[int] = None, - home_team_value: Optional[int] = None, - scorecard: Optional[str] = None, - week: Optional[int] = None, - 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() - 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() - raise HTTPException( - status_code=404, detail=f"No result found with id {result_id}" - ) - - if away_team_id is not None: - this_result.away_team_id = away_team_id - - if home_team_id is not None: - this_result.home_team_id = home_team_id - - if away_score is not None: - this_result.away_score = away_score - - if home_score is not None: - this_result.home_score = home_score - - if away_team_value is not None: - this_result.away_team_value = away_team_value - - if home_team_value is not None: - this_result.home_team_value = home_team_value - - if scorecard is not None: - this_result.scorecard = scorecard - - if week is not None: - this_result.week = week - - if season is not None: - this_result.season = season - - if game_type is not None: - this_result.game_type = game_type - - if short_game is not None: - if not short_game: - this_result.short_game = None - else: - this_result.short_game = short_game - - 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", - ) - - -@app.delete("/api/v1/results/{result_id}") -async def v1_results_delete(result_id, 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 results. This event has been logged.", - ) - try: - this_result = Result.get_by_id(result_id) - except Exception: - db.close() - 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" - ) - else: - raise HTTPException( - status_code=500, detail=f"Result {result_id} was not deleted" - ) - - -""" -AWARDS ENDPOINTS -""" - - -class AwardModel(pydantic.BaseModel): - name: str - season: int - timing: str = "In-Season" - card_id: Optional[int] = None - team_id: Optional[int] = None - image: Optional[str] = None - - -@app.get("/api/v1/awards") -async def v1_awards_get( - name: Optional[str] = None, - season: Optional[int] = None, - timing: Optional[str] = None, - card_id: Optional[int] = None, - team_id: Optional[int] = None, - image: Optional[str] = None, - csv: Optional[bool] = None, -): - all_awards = Award.select() - - 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: - all_awards = all_awards.where(Award.name == name) - if season is not None: - all_awards = all_awards.where(Award.season == season) - if timing is not None: - all_awards = all_awards.where(Award.timing == timing) - if card_id is not None: - all_awards = all_awards.where(Award.card_id == card_id) - if team_id is not None: - all_awards = all_awards.where(Award.team_id == team_id) - if image is not None: - all_awards = all_awards.where(Award.image == image) - - if csv: - data_list = [["id", "name", "season", "timing", "card", "team", "image"]] - for line in all_awards: - data_list.append( - [ - line.id, - line.name, - line.season, - line.timing, - line.card, - line.team, - line.image, - ] - ) - 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 = {"count": all_awards.count(), "awards": []} - for x in all_awards: - return_val["awards"].append(model_to_dict(x)) - - db.close() - return return_val - - -@app.get("/api/v1/awards/{award_id}") -async def v1_awards_get_one(award_id, csv: Optional[bool] = None): - try: - this_award = Award.get_by_id(award_id) - except Exception: - db.close() - raise HTTPException( - status_code=404, detail=f"No award found with id {award_id}" - ) - - if csv: - data_list = [ - ["id", "name", "season", "timing", "card", "team", "image"], - [ - this_award.id, - this_award.name, - this_award.season, - this_award.timing, - this_award.card, - this_award.team, - this_award.image, - ], - ] - 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 - - -@app.post("/api/v1/awards") -async def v1_awards_post(award: AwardModel, 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 awards. This event has been logged.", - ) - - this_award = Award( - name=award.name, - season=award.season, - timing=award.season, - card_id=award.card_id, - team_id=award.team_id, - image=award.image, - ) - - 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", - ) - - -@app.delete("/api/v1/awards/{award_id}") -async def v1_awards_delete(award_id, 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 delete awards. This event has been logged.", - ) - try: - this_award = Award.get_by_id(award_id) - except Exception: - db.close() - 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" - ) - else: - raise HTTPException(status_code=500, detail=f"Award {award_id} was not deleted") - - -""" -REWARD ENDPOINTS -""" - - -class RewardModel(pydantic.BaseModel): - name: str - season: int - week: int - team_id: int - created: Optional[int] = int(datetime.timestamp(datetime.now()) * 1000) - - -@app.get("/api/v1/rewards") -async def v1_rewards_get( - name: Optional[str] = None, - in_name: Optional[str] = None, - team_id: Optional[int] = None, - season: Optional[int] = None, - week: Optional[int] = None, - created_after: Optional[int] = None, - flat: Optional[bool] = False, - csv: Optional[bool] = None, -): - all_rewards = Reward.select() - - 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: - all_rewards = all_rewards.where(fn.Lower(Reward.name) == name.lower()) - if team_id is not None: - all_rewards = all_rewards.where(Reward.team_id == team_id) - if created_after is not None: - all_rewards = all_rewards.where(Reward.created >= created_after) - if in_name is not None: - all_rewards = all_rewards.where(fn.Lower(Reward.name).contains(in_name.lower())) - if season is not None: - all_rewards = all_rewards.where(Reward.season == season) - if week is not None: - 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: - data_list = [["id", "name", "team", "daily", "created"]] - for line in all_rewards: - data_list.append( - [line.id, line.name, line.team.id, line.daily, line.created] - ) - 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 = {"count": all_rewards.count(), "rewards": []} - for x in all_rewards: - return_val["rewards"].append(model_to_dict(x, recurse=not flat)) - - db.close() - return return_val - - -@app.get("/api/v1/rewards/{reward_id}") -async def v1_rewards_get_one(reward_id, csv: Optional[bool] = False): - try: - this_reward = Reward.get_by_id(reward_id) - except Exception: - db.close() - raise HTTPException( - status_code=404, detail=f"No reward found with id {reward_id}" - ) - - if csv: - data_list = [ - ["id", "name", "card_count", "description"], - [ - this_reward.id, - this_reward.name, - this_reward.team.id, - this_reward.daily, - this_reward.created, - ], - ] - 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 - - -@app.post("/api/v1/rewards") -async def v1_rewards_post(reward: RewardModel, 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 rewards. This event has been logged.", - ) - - this_reward = Reward(**reward.dict()) - - saved = this_reward.save() - if saved == 1: - return_val = model_to_dict(this_reward) - 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 cardset", - ) - - -@app.patch("/api/v1/rewards/{reward_id}") -async def v1_rewards_patch( - 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() - 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() - raise HTTPException( - status_code=404, detail=f"No reward found with id {reward_id}" - ) - - if name is not None: - this_reward.name = name - if team_id is not None: - this_reward.team_id = team_id - if created is not None: - this_reward.created = created - - if this_reward.save() == 1: - return_val = model_to_dict(this_reward) - 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 rarity", - ) - - -@app.delete("/api/v1/rewards/{reward_id}") -async def v1_rewards_delete(reward_id, 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 delete rewards. This event has been logged.", - ) - try: - this_reward = Reward.get_by_id(reward_id) - except Exception: - db.close() - 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" - ) - else: - raise HTTPException( - status_code=500, detail=f"Reward {reward_id} was not deleted" - ) - - -""" -BATTING STATS ENDPOINTS -""" - - -class BatStat(pydantic.BaseModel): - card_id: int - team_id: int - roster_num: int - vs_team_id: int - pos: str - pa: Optional[int] = 0 - ab: Optional[int] = 0 - run: Optional[int] = 0 - hit: Optional[int] = 0 - rbi: Optional[int] = 0 - double: Optional[int] = 0 - triple: Optional[int] = 0 - hr: Optional[int] = 0 - bb: Optional[int] = 0 - so: Optional[int] = 0 - hbp: Optional[int] = 0 - sac: Optional[int] = 0 - ibb: Optional[int] = 0 - gidp: Optional[int] = 0 - sb: Optional[int] = 0 - cs: Optional[int] = 0 - bphr: Optional[int] = 0 - bpfo: Optional[int] = 0 - bp1b: Optional[int] = 0 - bplo: Optional[int] = 0 - xch: Optional[int] = 0 - xhit: Optional[int] = 0 - error: Optional[int] = 0 - pb: Optional[int] = 0 - sbc: Optional[int] = 0 - csc: Optional[int] = 0 - week: int - season: int - created: Optional[int] = int(datetime.timestamp(datetime.now()) * 100000) - game_id: int - - -class BattingStatModel(pydantic.BaseModel): - stats: List[BatStat] - - -@app.get("/api/v1/batstats") -async def v1_batstats_get( - card_id: int = None, - player_id: int = None, - team_id: int = None, - vs_team_id: int = None, - week: int = None, - season: int = None, - week_start: int = None, - week_end: int = None, - created: int = None, - csv: bool = None, -): - all_stats = BattingStat.select().join(Card).join(Player) - - if card_id is not None: - all_stats = all_stats.where(BattingStat.card_id == card_id) - if player_id is not None: - all_stats = all_stats.where(BattingStat.card.player.player_id == player_id) - if team_id is not None: - all_stats = all_stats.where(BattingStat.team_id == team_id) - if vs_team_id is not None: - all_stats = all_stats.where(BattingStat.vs_team_id == vs_team_id) - if week is not None: - all_stats = all_stats.where(BattingStat.week == week) - if season is not None: - all_stats = all_stats.where(BattingStat.season == season) - if week_start is not None: - all_stats = all_stats.where(BattingStat.week >= week_start) - if week_end is not None: - all_stats = all_stats.where(BattingStat.week <= week_end) - if created is not None: - all_stats = all_stats.where(BattingStat.created == created) - - # if all_stats.count() == 0: - # db.close() - # raise HTTPException(status_code=404, detail=f'No batting stats found') - - if csv: - data_list = [ - [ - "id", - "card_id", - "player_id", - "cardset", - "team", - "vs_team", - "pos", - "pa", - "ab", - "run", - "hit", - "rbi", - "double", - "triple", - "hr", - "bb", - "so", - "hbp", - "sac", - "ibb", - "gidp", - "sb", - "cs", - "bphr", - "bpfo", - "bp1b", - "bplo", - "xch", - "xhit", - "error", - "pb", - "sbc", - "csc", - "week", - "season", - "created", - "game_id", - "roster_num", - ] - ] - for line in all_stats: - data_list.append( - [ - line.id, - line.card.id, - line.card.player.player_id, - line.card.player.cardset.name, - line.team.abbrev, - line.vs_team.abbrev, - line.pos, - line.pa, - line.ab, - line.run, - line.hit, - line.rbi, - line.double, - line.triple, - line.hr, - line.bb, - line.so, - line.hbp, - line.sac, - line.ibb, - line.gidp, - line.sb, - line.cs, - line.bphr, - line.bpfo, - line.bp1b, - line.bplo, - line.xch, - line.xhit, - line.error, - line.pb, - line.sbc, - line.csc, - line.week, - line.season, - line.created, - line.game_id, - line.roster_num, - ] - ) - 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 = {"count": all_stats.count(), "stats": []} - for x in all_stats: - return_val["stats"].append(model_to_dict(x, recurse=False)) - - db.close() - return return_val - - -@app.get("/api/v1/batstats/player/{player_id}") -async def v1_batstats_get_card( - player_id: int, - team_id: int = None, - vs_team_id: int = None, - week_start: int = None, - week_end: int = None, - csv: bool = None, -): - all_stats = ( - BattingStat.select(fn.COUNT(BattingStat.created).alias("game_count")) - .join(Card) - .group_by(BattingStat.card) - .where(BattingStat.card.player == player_id) - ).scalar() - - if team_id is not None: - all_stats = all_stats.where(BattingStat.team_id == team_id) - if vs_team_id is not None: - all_stats = all_stats.where(BattingStat.vs_team_id == vs_team_id) - if week_start is not None: - all_stats = all_stats.where(BattingStat.week >= week_start) - if week_end is not None: - all_stats = all_stats.where(BattingStat.week <= week_end) - - if csv: - data_list = [ - [ - "pa", - "ab", - "run", - "hit", - "rbi", - "double", - "triple", - "hr", - "bb", - "so", - "hbp", - "sac", - "ibb", - "gidp", - "sb", - "cs", - "bphr", - "bpfo", - "bp1b", - "bplo", - "xch", - "xhit", - "error", - "pb", - "sbc", - "csc", - ], - [ - all_stats.pa_sum, - all_stats.ab_sum, - all_stats.run, - all_stats.hit_sum, - all_stats.rbi_sum, - all_stats.double_sum, - all_stats.triple_sum, - all_stats.hr_sum, - all_stats.bb_sum, - all_stats.so_sum, - all_stats.hbp_sum, - all_stats.sac, - all_stats.ibb_sum, - all_stats.gidp_sum, - all_stats.sb_sum, - all_stats.cs_sum, - all_stats.bphr_sum, - all_stats.bpfo_sum, - all_stats.bp1b_sum, - all_stats.bplo_sum, - all_stats.xch, - all_stats.xhit_sum, - all_stats.error_sum, - all_stats.pb_sum, - all_stats.sbc_sum, - all_stats.csc_sum, - ], - ] - return_val = DataFrame(data_list).to_csv(header=False, index=False) - - db.close() - return Response(content=return_val, media_type="text/csv") - - else: - logging.debug(f"stat pull query: {all_stats}\n") - # logging.debug(f'result 0: {all_stats[0]}\n') - for x in all_stats: - logging.debug(f"this_line: {model_to_dict(x)}") - return_val = model_to_dict(all_stats[0]) - db.close() - return return_val - - -@app.get("/api/v1/plays/batting") -async def get_batting_totals( - player_id: list = Query(default=None), - team_id: list = Query(default=None), - min_pa: Optional[int] = 1, - season: list = Query(default=None), - position: list = Query(default=None), - group_by: Literal[ - "team", "player", "playerteam", "playergame", "teamgame", "league" - ] = "player", - sort: Optional[str] = None, - limit: Optional[int] = None, - short_output: Optional[bool] = False, -): - all_stats = ( - BattingStat.select( - BattingStat.card, - BattingStat.game_id, - BattingStat.team, - BattingStat.vs_team, - BattingStat.pos, - BattingStat.card.player.alias("player"), - fn.SUM(BattingStat.pa).alias("sum_pa"), - fn.SUM(BattingStat.ab).alias("sum_ab"), - fn.SUM(BattingStat.run).alias("sum_run"), - fn.SUM(BattingStat.so).alias("sum_so"), - fn.SUM(BattingStat.hit).alias("sum_hit"), - fn.SUM(BattingStat.rbi).alias("sum_rbi"), - fn.SUM(BattingStat.double).alias("sum_double"), - fn.SUM(BattingStat.triple).alias("sum_triple"), - fn.SUM(BattingStat.hr).alias("sum_hr"), - fn.SUM(BattingStat.bb).alias("sum_bb"), - fn.SUM(BattingStat.hbp).alias("sum_hbp"), - fn.SUM(BattingStat.sac).alias("sum_sac"), - fn.SUM(BattingStat.ibb).alias("sum_ibb"), - fn.SUM(BattingStat.gidp).alias("sum_gidp"), - fn.SUM(BattingStat.sb).alias("sum_sb"), - fn.SUM(BattingStat.cs).alias("sum_cs"), - fn.SUM(BattingStat.bphr).alias("sum_bphr"), - fn.SUM(BattingStat.bpfo).alias("sum_bpfo"), - fn.SUM(BattingStat.bp1b).alias("sum_bp1b"), - fn.SUM(BattingStat.bplo).alias("sum_bplo"), - ) - .having(fn.SUM(BattingStat.pa) >= min_pa) - .join(Card) - ) - - if player_id is not None: - # all_players = Player.select().where(Player.id << player_id) - all_cards = Card.select().where(Card.player_id << player_id) - all_stats = all_stats.where(BattingStat.card << all_cards) - if team_id is not None: - all_teams = Team.select().where(Team.id << team_id) - all_stats = all_stats.where(BattingStat.team << all_teams) - if season is not None: - all_stats = all_stats.where(BattingStat.season << season) - if position is not None: - all_stats = all_stats.where(BattingStat.pos << position) - - if group_by == "player": - all_stats = all_stats.group_by(SQL("player")) - elif group_by == "playerteam": - all_stats = all_stats.group_by(SQL("player"), BattingStat.team) - elif group_by == "playergame": - all_stats = all_stats.group_by(SQL("player"), BattingStat.game_id) - elif group_by == "team": - all_stats = all_stats.group_by(BattingStat.team) - elif group_by == "teamgame": - all_stats = all_stats.group_by(BattingStat.team, BattingStat.game_id) - elif group_by == "league": - all_stats = all_stats.group_by(BattingStat.season) - - if sort == "pa-desc": - all_stats = all_stats.order_by(SQL("sum_pa").desc()) - elif sort == "newest": - all_stats = all_stats.order_by(-BattingStat.game_id) - elif sort == "oldest": - all_stats = all_stats.order_by(BattingStat.game_id) - - if limit is not None: - if limit < 1: - limit = 1 - all_stats = all_stats.limit(limit) - - logging.info(f"bat_plays query: {all_stats}") - - return_stats = { - "count": all_stats.count(), - "stats": [ - { - "player": x.card.player_id - if short_output - else model_to_dict(x.card.player, recurse=False), - "team": x.team_id - if short_output - else model_to_dict(x.team, recurse=False), - "pa": x.sum_pa, - "ab": x.sum_ab, - "run": x.sum_run, - "hit": x.sum_hit, - "rbi": x.sum_rbi, - "double": x.sum_double, - "triple": x.sum_triple, - "hr": x.sum_hr, - "bb": x.sum_bb, - "so": x.sum_so, - "hbp": x.sum_hbp, - "sac": x.sum_sac, - "ibb": x.sum_ibb, - "gidp": x.sum_gidp, - "sb": x.sum_sb, - "cs": x.sum_cs, - "bphr": x.sum_bphr, - "bpfo": x.sum_bpfo, - "bp1b": x.sum_bp1b, - "bplo": x.sum_bplo, - "avg": x.sum_hit / max(x.sum_ab, 1), - "obp": (x.sum_hit + x.sum_bb + x.sum_hbp + x.sum_ibb) - / max(x.sum_pa, 1), - "slg": ( - x.sum_hr * 4 - + x.sum_triple * 3 - + x.sum_double * 2 - + (x.sum_hit - x.sum_double - x.sum_triple - x.sum_hr) - ) - / max(x.sum_ab, 1), - "ops": ( - (x.sum_hit + x.sum_bb + x.sum_hbp + x.sum_ibb) / max(x.sum_pa, 1) - ) - + ( - ( - x.sum_hr * 4 - + x.sum_triple * 3 - + x.sum_double * 2 - + (x.sum_hit - x.sum_double - x.sum_triple - x.sum_hr) - ) - / max(x.sum_ab, 1) - ), - "woba": ( - 0.69 * x.sum_bb - + 0.72 * x.sum_hbp - + 0.89 * (x.sum_hit - x.sum_double - x.sum_triple - x.sum_hr) - + 1.27 * x.sum_double - + 1.62 * x.sum_triple - + 2.1 * x.sum_hr - ) - / max(x.sum_pa - x.sum_ibb, 1), - "game": x.game_id, - } - for x in all_stats - ], - } - - db.close() - return return_stats - - -@app.get("/api/v1/plays/pitching") -async def get_pitching_totals( - player_id: list = Query(default=None), - team_id: list = Query(default=None), - season: list = Query(default=None), - group_by: Literal[ - "team", "player", "playerteam", "playergame", "teamgame", "league" - ] = "player", - min_pa: Optional[int] = 1, - sort: Optional[str] = None, - limit: Optional[int] = None, - short_output: Optional[bool] = False, -): - all_stats = ( - PitchingStat.select( - PitchingStat.card, - PitchingStat.team, - PitchingStat.game_id, - PitchingStat.vs_team, - PitchingStat.card.player.alias("player"), - fn.SUM(PitchingStat.ip).alias("sum_ip"), - fn.SUM(PitchingStat.hit).alias("sum_hit"), - fn.SUM(PitchingStat.run).alias("sum_run"), - fn.SUM(PitchingStat.erun).alias("sum_erun"), - fn.SUM(PitchingStat.so).alias("sum_so"), - fn.SUM(PitchingStat.bb).alias("sum_bb"), - fn.SUM(PitchingStat.hbp).alias("sum_hbp"), - fn.SUM(PitchingStat.wp).alias("sum_wp"), - fn.SUM(PitchingStat.balk).alias("sum_balk"), - fn.SUM(PitchingStat.hr).alias("sum_hr"), - fn.SUM(PitchingStat.ir).alias("sum_ir"), - fn.SUM(PitchingStat.irs).alias("sum_irs"), - fn.SUM(PitchingStat.gs).alias("sum_gs"), - fn.SUM(PitchingStat.win).alias("sum_win"), - fn.SUM(PitchingStat.loss).alias("sum_loss"), - fn.SUM(PitchingStat.hold).alias("sum_hold"), - fn.SUM(PitchingStat.sv).alias("sum_sv"), - fn.SUM(PitchingStat.bsv).alias("sum_bsv"), - fn.COUNT(PitchingStat.game_id).alias("sum_games"), - ) - .having(fn.SUM(PitchingStat.ip) >= max(min_pa / 3, 1)) - .join(Card) - ) - - if player_id is not None: - all_cards = Card.select().where(Card.player_id << player_id) - all_stats = all_stats.where(PitchingStat.card << all_cards) - if team_id is not None: - all_teams = Team.select().where(Team.id << team_id) - all_stats = all_stats.where(PitchingStat.team << all_teams) - if season is not None: - all_stats = all_stats.where(PitchingStat.season << season) - - if group_by == "player": - all_stats = all_stats.group_by(SQL("player")) - elif group_by == "playerteam": - all_stats = all_stats.group_by(SQL("player"), PitchingStat.team) - elif group_by == "playergame": - all_stats = all_stats.group_by(SQL("player"), PitchingStat.game_id) - elif group_by == "team": - all_stats = all_stats.group_by(PitchingStat.team) - elif group_by == "teamgame": - all_stats = all_stats.group_by(PitchingStat.team, PitchingStat.game_id) - elif group_by == "league": - all_stats = all_stats.group_by(PitchingStat.season) - - if sort == "pa-desc": - all_stats = all_stats.order_by(SQL("sum_pa").desc()) - elif sort == "newest": - all_stats = all_stats.order_by(-PitchingStat.game_id) - elif sort == "oldest": - all_stats = all_stats.order_by(PitchingStat.game_id) - - if limit is not None: - if limit < 1: - limit = 1 - all_stats = all_stats.limit(limit) - - logging.info(f"bat_plays query: {all_stats}") - - return_stats = { - "count": all_stats.count(), - "stats": [ - { - "player": x.card.player_id - if short_output - else model_to_dict(x.card.player, recurse=False), - "team": x.team_id - if short_output - else model_to_dict(x.team, recurse=False), - "tbf": None, - "outs": round(x.sum_ip * 3), - "games": x.sum_games, - "gs": x.sum_gs, - "win": x.sum_win, - "loss": x.sum_loss, - "hold": x.sum_hold, - "save": x.sum_sv, - "bsave": x.sum_bsv, - "ir": x.sum_ir, - "ir_sc": x.sum_irs, - "runs": x.sum_run, - "e_runs": x.sum_erun, - "hits": x.sum_hit, - "hr": x.sum_hr, - "bb": x.sum_bb, - "so": x.sum_so, - "hbp": x.sum_hbp, - "wp": x.sum_wp, - "balk": x.sum_balk, - "era": (x.sum_erun * 27) / round(x.sum_ip * 3), - "whip": (x.sum_bb + x.sum_hit) / x.sum_ip, - "avg": None, - "obp": None, - "woba": None, - "k/9": x.sum_so * 9 / x.sum_ip, - "bb/9": x.sum_bb * 9 / x.sum_ip, - "k/bb": x.sum_so / max(x.sum_bb, 0.1), - "game": None, - "lob_2outs": None, - "rbi%": None, - } - for x in all_stats - ], - } - db.close() - return return_stats - - -@app.post("/api/v1/batstats") -async def v1_batstats_post( - stats: BattingStatModel, 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 stats. This event has been logged.", - ) - - new_stats = [] - for x in stats.stats: - this_stat = BattingStat( - card_id=x.card_id, - team_id=x.team_id, - roster_num=x.roster_num, - vs_team_id=x.vs_team_id, - pos=x.pos, - pa=x.pa, - ab=x.ab, - run=x.run, - hit=x.hit, - rbi=x.rbi, - double=x.double, - triple=x.triple, - hr=x.hr, - bb=x.bb, - so=x.so, - hbp=x.hbp, - sac=x.sac, - ibb=x.ibb, - gidp=x.gidp, - sb=x.sb, - cs=x.cs, - bphr=x.bphr, - bpfo=x.bpfo, - bp1b=x.bp1b, - bplo=x.bplo, - xch=x.xch, - xhit=x.xhit, - error=x.error, - pb=x.pb, - sbc=x.sbc, - csc=x.csc, - week=x.week, - season=x.season, - created=x.created, - game_id=x.game_id, - ) - new_stats.append(this_stat) - - 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" - ) - - -@app.delete("/api/v1/batstats/{stat_id}") -async def v1_rewards_delete(stat_id, 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 delete stats. This event has been logged.", - ) - try: - this_reward = Reward.get_by_id(stat_id) - except Exception: - db.close() - raise HTTPException(status_code=404, detail=f"No stat found with id {stat_id}") - - count = this_reward.delete_instance() - db.close() - - if count == 1: - raise HTTPException(status_code=200, detail=f"Stat {stat_id} has been deleted") - else: - raise HTTPException(status_code=500, detail=f"Stat {stat_id} was not deleted") - - -""" -PITCHING STATS ENDPOINTS -""" - - -class PitStat(pydantic.BaseModel): - card_id: int - team_id: int - vs_team_id: int - roster_num: int - ip: float - hit: Optional[int] = 0 - run: Optional[int] = 0 - erun: Optional[int] = 0 - so: Optional[int] = 0 - bb: Optional[int] = 0 - hbp: Optional[int] = 0 - wp: Optional[int] = 0 - balk: Optional[int] = 0 - hr: Optional[int] = 0 - ir: Optional[int] = 0 - irs: Optional[int] = 0 - gs: Optional[int] = 0 - win: Optional[int] = 0 - loss: Optional[int] = 0 - hold: Optional[int] = 0 - sv: Optional[int] = 0 - bsv: Optional[int] = 0 - week: int - season: int - created: Optional[int] = int(datetime.timestamp(datetime.now()) * 100000) - game_id: int - - -class PitchingStatModel(pydantic.BaseModel): - stats: List[PitStat] - - -@app.get("/api/v1/pitstats") -async def v1_pitstats_get( - card_id: int = None, - player_id: int = None, - team_id: int = None, - vs_team_id: int = None, - week: int = None, - season: int = None, - week_start: int = None, - week_end: int = None, - created: int = None, - gs: bool = None, - csv: bool = None, -): - all_stats = PitchingStat.select().join(Card).join(Player) - logging.debug(f"pit query:\n\n{all_stats}") - - if card_id is not None: - all_stats = all_stats.where(PitchingStat.card_id == card_id) - if player_id is not None: - all_stats = all_stats.where(PitchingStat.card.player.player_id == player_id) - if team_id is not None: - all_stats = all_stats.where(PitchingStat.team_id == team_id) - if vs_team_id is not None: - all_stats = all_stats.where(PitchingStat.vs_team_id == vs_team_id) - if week is not None: - all_stats = all_stats.where(PitchingStat.week == week) - if season is not None: - all_stats = all_stats.where(PitchingStat.season == season) - if week_start is not None: - all_stats = all_stats.where(PitchingStat.week >= week_start) - if week_end is not None: - all_stats = all_stats.where(PitchingStat.week <= week_end) - if created is not None: - all_stats = all_stats.where(PitchingStat.created <= created) - if gs is not None: - all_stats = all_stats.where(PitchingStat.gs == 1 if gs else 0) - - # if all_stats.count() == 0: - # db.close() - # raise HTTPException(status_code=404, detail=f'No pitching stats found') - - if csv: - data_list = [ - [ - "id", - "card_id", - "player_id", - "cardset", - "team", - "vs_team", - "ip", - "hit", - "run", - "erun", - "so", - "bb", - "hbp", - "wp", - "balk", - "hr", - "ir", - "irs", - "gs", - "win", - "loss", - "hold", - "sv", - "bsv", - "week", - "season", - "created", - "game_id", - "roster_num", - ] - ] - for line in all_stats: - data_list.append( - [ - line.id, - line.card.id, - line.card.player.player_id, - line.card.player.cardset.name, - line.team.abbrev, - line.vs_team.abbrev, - line.ip, - line.hit, - line.run, - line.erun, - line.so, - line.bb, - line.hbp, - line.wp, - line.balk, - line.hr, - line.ir, - line.irs, - line.gs, - line.win, - line.loss, - line.hold, - line.sv, - line.bsv, - line.week, - line.season, - line.created, - line.game_id, - line.roster_num, - ] - ) - 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 = {"count": all_stats.count(), "stats": []} - for x in all_stats: - return_val["stats"].append(model_to_dict(x, recurse=False)) - - db.close() - return return_val - - -@app.post("/api/v1/pitstats") -async def v1_batstats_post( - stats: PitchingStatModel, 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 stats. This event has been logged.", - ) - - new_stats = [] - for x in stats.stats: - this_stat = PitchingStat( - card_id=x.card_id, - team_id=x.team_id, - vs_team_id=x.vs_team_id, - roster_num=x.roster_num, - ip=x.ip, - hit=x.hit, - run=x.run, - erun=x.erun, - so=x.so, - bb=x.bb, - hbp=x.hbp, - wp=x.wp, - balk=x.balk, - hr=x.hr, - ir=x.ir, - irs=x.irs, - gs=x.gs, - win=x.win, - loss=x.loss, - hold=x.hold, - sv=x.sv, - bsv=x.bsv, - week=x.week, - season=x.season, - created=x.created, - game_id=x.game_id, - ) - new_stats.append(this_stat) - - 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" - ) - - -@app.delete("/api/v1/pitstats/{stat_id}") -async def v1_rewards_delete(stat_id, 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 delete stats. This event has been logged.", - ) - try: - this_reward = Reward.get_by_id(stat_id) - except Exception: - db.close() - raise HTTPException(status_code=404, detail=f"No stat found with id {stat_id}") - - count = this_reward.delete_instance() - db.close() - - if count == 1: - raise HTTPException(status_code=200, detail=f"Stat {stat_id} has been deleted") - else: - raise HTTPException(status_code=500, detail=f"Stat {stat_id} was not deleted") - - -""" -NOTIFICATIONS ENDPOINTS -""" - - -class NotifModel(pydantic.BaseModel): - created: int - title: str - desc: Optional[str] = None - field_name: str - message: str - about: Optional[str] = "blank" - ack: Optional[bool] = False - - -@app.get("/api/v1/notifs") -async def v1_notifs_get( - created_after: Optional[int] = None, - title: Optional[str] = None, - desc: Optional[str] = None, - field_name: Optional[str] = None, - in_desc: Optional[str] = None, - about: Optional[str] = None, - ack: Optional[bool] = None, - csv: Optional[bool] = None, -): - all_notif = Notification.select() - - 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: - all_notif = all_notif.where(Notification.created < created_after) - if title is not None: - all_notif = all_notif.where(Notification.title == title) - if desc is not None: - all_notif = all_notif.where(Notification.desc == desc) - if field_name is not None: - all_notif = all_notif.where(Notification.field_name == field_name) - if in_desc is not None: - all_notif = all_notif.where( - fn.Lower(Notification.desc).contains(in_desc.lower()) - ) - if about is not None: - all_notif = all_notif.where(Notification.about == about) - if ack is not None: - all_notif = all_notif.where(Notification.ack == ack) - - if csv: - data_list = [ - ["id", "created", "title", "desc", "field_name", "message", "about", "ack"] - ] - for line in all_notif: - data_list.append( - [ - line.id, - line.created, - line.title, - line.desc, - line.field_name, - line.message, - line.about, - line.ack, - ] - ) - 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 = {"count": all_notif.count(), "notifs": []} - for x in all_notif: - return_val["notifs"].append(model_to_dict(x)) - - db.close() - return return_val - - -@app.get("/api/v1/notifs/{notif_id}") -async def v1_notifs_get_one(notif_id, csv: Optional[bool] = None): - try: - this_notif = Notification.get_by_id(notif_id) - except Exception: - db.close() - raise HTTPException( - status_code=404, detail=f"No notification found with id {notif_id}" - ) - - if csv: - data_list = [ - ["id", "created", "title", "desc", "field_name", "message", "about", "ack"], - [ - this_notif.id, - this_notif.created, - this_notif.title, - this_notif.desc, - this_notif.field_name, - this_notif.message, - this_notif.about, - this_notif.ack, - ], - ] - 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 - - -@app.post("/api/v1/notifs") -async def v1_notifs_post(notif: NotifModel, 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 notifications. This event has been logged.", - ) - - logging.info(f"new notif: {notif}") - this_notif = Notification( - created=notif.created, - title=notif.title, - desc=notif.desc, - field_name=notif.field_name, - message=notif.message, - about=notif.about, - ) - - 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", - ) - - -@app.patch("/api/v1/notifs/{notif_id}") -async def v1_rewards_patch( - notif_id, - created: Optional[int] = None, - title: Optional[str] = None, - desc: Optional[str] = None, - 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() - 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() - raise HTTPException( - status_code=404, detail=f"No notification found with id {notif_id}" - ) - - if title is not None: - this_notif.title = title - if desc is not None: - this_notif.desc = desc - if field_name is not None: - this_notif.field_name = field_name - if message is not None: - this_notif.message = message - if about is not None: - this_notif.about = about - if ack is not None: - this_notif.ack = ack - if created is not None: - this_notif.created = created - - if this_notif.save() == 1: - return_val = model_to_dict(this_notif) - 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 rarity", - ) - - -@app.delete("/api/v1/notifs/{notif_id}") -async def v1_notifs_delete(notif_id, 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 delete notifications. This event has been logged.", - ) - try: - this_notif = Notification.get_by_id(notif_id) - except Exception: - db.close() - 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" - ) - else: - raise HTTPException( - status_code=500, detail=f"Notification {notif_id} was not deleted" - ) - - -""" -PAPERDEX ENDPOINTS -""" - - -class PaperdexModel(pydantic.BaseModel): - team_id: int - player_id: int - created: Optional[int] = int(datetime.timestamp(datetime.now()) * 1000) - - -@app.get("/api/v1/paperdex") -async def v1_paperdex_get( - team_id: Optional[int] = None, - player_id: Optional[int] = None, - created_after: Optional[int] = None, - cardset_id: Optional[int] = None, - created_before: Optional[int] = None, - flat: Optional[bool] = False, - csv: Optional[bool] = None, -): - all_dex = Paperdex.select().join(Player).join(Cardset) - - 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: - all_dex = all_dex.where(Paperdex.team_id == team_id) - if player_id is not None: - all_dex = all_dex.where(Paperdex.player_id == player_id) - if cardset_id is not None: - all_dex = all_dex.where(Paperdex.player.cardset.id == cardset_id) - if created_after is not None: - all_dex = all_dex.where(Paperdex.created >= created_after) - if created_before is not None: - all_dex = all_dex.where(Paperdex.created <= created_before) - - # if all_dex.count() == 0: - # db.close() - # raise HTTPException(status_code=404, detail=f'No paperdex found') - - if csv: - data_list = [["id", "team_id", "player_id", "created"]] - for line in all_dex: - data_list.append( - [line.id, line.team.id, line.player.player_id, line.created] - ) - 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 = {"count": all_dex.count(), "paperdex": []} - for x in all_dex: - return_val["paperdex"].append(model_to_dict(x, recurse=not flat)) - - db.close() - return return_val - - -@app.get("/api/v1/paperdex/{paperdex_id}") -async def v1_paperdex_get_one(paperdex_id, csv: Optional[bool] = False): - try: - this_dex = Paperdex.get_by_id(paperdex_id) - except Exception: - db.close() - raise HTTPException( - status_code=404, detail=f"No paperdex found with id {paperdex_id}" - ) - - if csv: - data_list = [ - ["id", "team_id", "player_id", "created"], - [this_dex.id, this_dex.team.id, this_dex.player.id, this_dex.created], - ] - 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 - - -@app.post("/api/v1/paperdex") -async def v1_paperdex_post( - paperdex: PaperdexModel, 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 paperdex. This event has been logged.", - ) - - 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( - team_id=paperdex.team_id, player_id=paperdex.player_id, created=paperdex.created - ) - - saved = this_dex.save() - if saved == 1: - return_val = model_to_dict(this_dex) - 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 dex", - ) - - -@app.patch("/api/v1/paperdex/{paperdex_id}") -async def v1_paperdex_patch( - 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() - 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() - raise HTTPException( - status_code=404, detail=f"No paperdex found with id {paperdex_id}" - ) - - if team_id is not None: - this_dex.team_id = team_id - if player_id is not None: - this_dex.player_id = player_id - if created is not None: - this_dex.created = created - - if this_dex.save() == 1: - return_val = model_to_dict(this_dex) - 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 rarity", - ) - - -@app.delete("/api/v1/paperdex/{paperdex_id}") -async def v1_paperdex_delete(paperdex_id, 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 delete rewards. This event has been logged.", - ) - try: - this_dex = Paperdex.get_by_id(paperdex_id) - except Exception: - db.close() - 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" - ) - else: - raise HTTPException( - status_code=500, detail=f"Paperdex {this_dex} was not deleted" - ) - - -@app.post("/api/v1/paperdex/wipe-ai") -async def v1_paperdex_wipeai(token: str = Depends(oauth2_scheme)): - if not valid_token(token): - logging.warning(f"Bad Token: {token}") - db.close() - raise HTTPException(status_code=401, detail="Unauthorized") - - g_teams = Team.select().where(Team.abbrev.contains("Gauntlet")) - count = Paperdex.delete().where(Paperdex.team << g_teams).execute() - return f"Deleted {count} records" - - -""" -GAMEREWARDS ENDPOINTS -""" - - -class GameRewardModel(pydantic.BaseModel): - name: str - pack_type_id: Optional[int] = None - player_id: Optional[int] = None - money: Optional[int] = None - - -@app.get("/api/v1/gamerewards") -async def v1_gamerewards_get( - name: Optional[str] = None, - pack_type_id: Optional[int] = None, - player_id: Optional[int] = None, - money: Optional[int] = None, - csv: Optional[bool] = None, -): - all_rewards = GameRewards.select() - - # if all_rewards.count() == 0: - # db.close() - # raise HTTPException(status_code=404, detail=f'There are no awards to filter') - - if name is not None: - all_rewards = all_rewards.where(GameRewards.name == name) - if pack_type_id is not None: - all_rewards = all_rewards.where(GameRewards.pack_type_id == pack_type_id) - if player_id is not None: - all_rewards = all_rewards.where(GameRewards.player_id == player_id) - if money is not None: - all_rewards = all_rewards.where(GameRewards.money == money) - - if csv: - data_list = [["id", "pack_type_id", "player_id", "money"]] - for line in all_rewards: - data_list.append( - [ - line.id, - line.pack_type_id if line.pack_type else None, - line.player_id if line.player else None, - line.money, - ] - ) - 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 = {"count": all_rewards.count(), "gamerewards": []} - for x in all_rewards: - return_val["gamerewards"].append(model_to_dict(x)) - - db.close() - return return_val - - -@app.get("/api/v1/gamerewards/{gameaward_id}") -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() - raise HTTPException( - status_code=404, detail=f"No game reward found with id {gamereward_id}" - ) - - if csv: - data_list = [ - ["id", "pack_type_id", "player_id", "money"], - [ - this_game_reward.id, - this_game_reward.pack_type_id if this_game_reward.pack_type else None, - this_game_reward.player_id if this_game_reward.player else None, - this_game_reward.money, - ], - ] - 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 - - -@app.post("/api/v1/gamerewards") -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() - raise HTTPException( - status_code=401, - detail="You are not authorized to post game rewards. This event has been logged.", - ) - - this_award = GameRewards( - name=game_reward.name, - pack_type_id=game_reward.pack_type_id, - player_id=game_reward.player_id, - money=game_reward.money, - ) - - 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", - ) - - -@app.patch("/api/v1/gamerewards/{game_reward_id}") -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() - 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() - raise HTTPException( - status_code=404, detail=f"No game reward found with id {game_reward_id}" - ) - - if name is not None: - this_game_reward.name = name - if pack_type_id is not None: - if not pack_type_id: - this_game_reward.pack_type_id = None - else: - this_game_reward.pack_type_id = pack_type_id - if player_id is not None: - if not player_id: - this_game_reward.player_id = None - else: - this_game_reward.player_id = player_id - if money is not None: - if not money: - this_game_reward.money = None - else: - this_game_reward.money = money - - if this_game_reward.save() == 1: - return_val = model_to_dict(this_game_reward) - 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 rarity", - ) - - -@app.delete("/api/v1/gamerewards/{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() - 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() - 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" - ) - else: - raise HTTPException( - status_code=500, detail=f"Game Reward {gamereward_id} was not deleted" - ) - - -""" -GAUNTLET REWARD ENDPOINTS -""" - - -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] - - -@app.get("/api/v1/gauntletrewards") -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 - - -@app.get("/api/v1/gauntletrewards/{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 - - -@app.patch("/api/v1/gauntletrewards/{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}") - - -@app.post("/api/v1/gauntletrewards") -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" - - -@app.delete("/api/v1/gauntletrewards/{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}") - - -""" -GAUNTLET ENDPOINTS -""" - - -class GauntletRunModel(pydantic.BaseModel): - team_id: int - gauntlet_id: int - wins: Optional[int] = 0 - losses: Optional[int] = 0 - gsheet: Optional[str] = None - created: Optional[int] = int(datetime.timestamp(datetime.now()) * 1000) - ended: Optional[int] = 0 - - -@app.get("/api/v1/gauntletruns") -async def v1_gauntletrun_get( - team_id: list = Query(default=None), - wins: Optional[int] = None, - wins_min: Optional[int] = None, - wins_max: Optional[int] = None, - losses: Optional[int] = None, - losses_min: Optional[int] = None, - losses_max: Optional[int] = None, - gsheet: Optional[str] = None, - created_after: Optional[int] = None, - created_before: Optional[int] = None, - ended_after: Optional[int] = None, - ended_before: Optional[int] = None, - is_active: Optional[bool] = None, - gauntlet_id: list = Query(default=None), - season: list = Query(default=None), -): - all_gauntlets = GauntletRun.select() - - if team_id is not None: - all_gauntlets = all_gauntlets.where(GauntletRun.team_id << team_id) - if wins is not None: - all_gauntlets = all_gauntlets.where(GauntletRun.wins == wins) - if wins_min is not None: - all_gauntlets = all_gauntlets.where(GauntletRun.wins >= wins_min) - if wins_max is not None: - all_gauntlets = all_gauntlets.where(GauntletRun.wins <= wins_max) - if losses is not None: - all_gauntlets = all_gauntlets.where(GauntletRun.losses == losses) - if losses_min is not None: - all_gauntlets = all_gauntlets.where(GauntletRun.losses >= losses_min) - if losses_max is not None: - all_gauntlets = all_gauntlets.where(GauntletRun.losses <= losses_max) - if gsheet is not None: - all_gauntlets = all_gauntlets.where(GauntletRun.gsheet == gsheet) - if created_after is not None: - all_gauntlets = all_gauntlets.where(GauntletRun.created >= created_after) - if created_before is not None: - all_gauntlets = all_gauntlets.where(GauntletRun.created <= created_before) - if ended_after is not None: - all_gauntlets = all_gauntlets.where(GauntletRun.ended >= ended_after) - if ended_before is not None: - all_gauntlets = all_gauntlets.where(GauntletRun.ended <= ended_before) - if is_active is not None: - if is_active is True: - all_gauntlets = all_gauntlets.where(GauntletRun.ended == 0) - else: - all_gauntlets = all_gauntlets.where(GauntletRun.ended != 0) - if gauntlet_id is not None: - all_gauntlets = all_gauntlets.where(GauntletRun.gauntlet_id << gauntlet_id) - if season is not None: - all_gauntlets = all_gauntlets.where(GauntletRun.team.season << season) - - return_val = {"count": all_gauntlets.count(), "runs": []} - for x in all_gauntlets: - return_val["runs"].append(model_to_dict(x)) - - db.close() - return return_val - - -@app.get("/api/v1/gauntletruns/{gauntletrun_id}") -async def v1_gauntletrun_get_one(gauntletrun_id): - try: - this_gauntlet = GauntletRun.get_by_id(gauntletrun_id) - except Exception: - db.close() - 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 - - -@app.patch("/api/v1/gauntletruns/{gauntletrun_id}") -async def v1_gauntletrun_patch( - gauntletrun_id, - team_id: Optional[int] = None, - wins: Optional[int] = None, - losses: Optional[int] = None, - 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() - raise HTTPException( - status_code=401, - detail="You are not authorized to patch gauntlet runs. This event has been logged.", - ) - - 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: - this_run.team_id = team_id - if wins is not None: - this_run.wins = wins - if losses is not None: - this_run.losses = losses - if gsheet is not None: - this_run.gsheet = gsheet - if created is not None: - if created is True: - this_run.created = int(datetime.timestamp(datetime.now()) * 1000) - else: - this_run.created = None - if ended is not None: - if ended is True: - this_run.ended = int(datetime.timestamp(datetime.now()) * 1000) - else: - this_run.ended = 0 - - 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}") - - -@app.post("/api/v1/gauntletruns") -async def v1_gauntletrun_post( - gauntletrun: GauntletRunModel, 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.", - ) - - this_run = GauntletRun(**gauntletrun.dict()) - - 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") - - -@app.delete("/api/v1/gauntletruns/{gauntletrun_id}") -async def v1_gauntletrun_delete(gauntletrun_id): - if GauntletRun.delete_by_id(gauntletrun_id) == 1: - return f"Deleted gauntlet run ID {gauntletrun_id}" - - raise DatabaseError(f"Unable to delete gauntlet run {gauntletrun_id}") - - -@app.post("/api/v1/stl-fix") -async def v1_stl_fix(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. This event has been logged.", - ) - - p_query = ( - Player.update(mlbclub="St Louis Cardinals", franchise="Cardinals") - .where(Player.mlbclub == "St. Louis Cardinals") - .execute() - ) - db.close() - - return {"detail": f"Removed the period from St Louis"} - - -@app.get("/api/v1/ratings/batter/{sba_id}") -async def v1_ratings_batter_get( - sba_id: int, - cardset_name: Optional[str] = None, - vs_hand: Literal["l", "r", "L", "R"] = None, -): - this_player = ScoutPlayer.get_or_none(ScoutPlayer.sba_id == sba_id) - logging.info(f"num players: {ScoutPlayer.select().count()}") - if this_player is None: - scout_db.close() - raise HTTPException(status_code=404, detail=f"SBa ID {sba_id} not found") - - r_query = BatterRatings.select().where( - (BatterRatings.player == this_player) & (BatterRatings.is_prep == 0) - ) - - if cardset_name is not None: - this_cardset = ScoutCardset.get_or_none( - fn.Lower(ScoutCardset.set_title) == cardset_name.lower() - ) - r_query = r_query.where(BatterRatings.cardset == this_cardset) - if vs_hand is not None: - r_query = r_query.where( - fn.Lower(BatterRatings.vs_hand) == f"v{vs_hand.lower()}" - ) - - return_val = { - "count": r_query.count(), - "ratings": [model_to_dict(x) for x in r_query], - } - - scout_db.close() - return return_val diff --git a/migrations/2026-01-07_normalize_franchise.sql b/migrations/2026-01-07_normalize_franchise.sql deleted file mode 100644 index 80bcca7..0000000 --- a/migrations/2026-01-07_normalize_franchise.sql +++ /dev/null @@ -1,104 +0,0 @@ --- Migration: Normalize Player.franchise to city-agnostic values --- Date: 2026-01-07 --- Purpose: Enable cross-era player matching for AI rosters by normalizing --- franchise values to match Team.sname (e.g., 'Oakland Athletics' -> 'Athletics') --- --- IMPORTANT: Backup database before running! --- Run on dev first, verify with: SELECT DISTINCT franchise FROM player ORDER BY franchise; --- Then run on prod. --- --- Rollback: See inverse statements at bottom of file - --- ============================================ --- FORWARD MIGRATION: Normalize franchise values --- ============================================ - --- National League West -UPDATE player SET franchise = 'Diamondbacks' WHERE franchise = 'Arizona Diamondbacks'; -UPDATE player SET franchise = 'Rockies' WHERE franchise = 'Colorado Rockies'; -UPDATE player SET franchise = 'Dodgers' WHERE franchise = 'Los Angeles Dodgers'; -UPDATE player SET franchise = 'Padres' WHERE franchise = 'San Diego Padres'; -UPDATE player SET franchise = 'Giants' WHERE franchise = 'San Francisco Giants'; - --- National League Central -UPDATE player SET franchise = 'Cubs' WHERE franchise = 'Chicago Cubs'; -UPDATE player SET franchise = 'Reds' WHERE franchise = 'Cincinnati Reds'; -UPDATE player SET franchise = 'Brewers' WHERE franchise = 'Milwaukee Brewers'; -UPDATE player SET franchise = 'Pirates' WHERE franchise = 'Pittsburgh Pirates'; -UPDATE player SET franchise = 'Cardinals' WHERE franchise IN ('St Louis Cardinals', 'St. Louis Cardinals'); - --- National League East -UPDATE player SET franchise = 'Braves' WHERE franchise = 'Atlanta Braves'; -UPDATE player SET franchise = 'Marlins' WHERE franchise = 'Miami Marlins'; -UPDATE player SET franchise = 'Mets' WHERE franchise = 'New York Mets'; -UPDATE player SET franchise = 'Phillies' WHERE franchise = 'Philadelphia Phillies'; -UPDATE player SET franchise = 'Nationals' WHERE franchise = 'Washington Nationals'; - --- American League West -UPDATE player SET franchise = 'Astros' WHERE franchise = 'Houston Astros'; -UPDATE player SET franchise = 'Angels' WHERE franchise = 'Los Angeles Angels'; -UPDATE player SET franchise = 'Athletics' WHERE franchise = 'Oakland Athletics'; -UPDATE player SET franchise = 'Mariners' WHERE franchise = 'Seattle Mariners'; -UPDATE player SET franchise = 'Rangers' WHERE franchise = 'Texas Rangers'; - --- American League Central -UPDATE player SET franchise = 'White Sox' WHERE franchise = 'Chicago White Sox'; -UPDATE player SET franchise = 'Guardians' WHERE franchise = 'Cleveland Guardians'; -UPDATE player SET franchise = 'Tigers' WHERE franchise = 'Detroit Tigers'; -UPDATE player SET franchise = 'Royals' WHERE franchise = 'Kansas City Royals'; -UPDATE player SET franchise = 'Twins' WHERE franchise = 'Minnesota Twins'; - --- American League East -UPDATE player SET franchise = 'Orioles' WHERE franchise = 'Baltimore Orioles'; -UPDATE player SET franchise = 'Red Sox' WHERE franchise = 'Boston Red Sox'; -UPDATE player SET franchise = 'Yankees' WHERE franchise = 'New York Yankees'; -UPDATE player SET franchise = 'Rays' WHERE franchise = 'Tampa Bay Rays'; -UPDATE player SET franchise = 'Blue Jays' WHERE franchise = 'Toronto Blue Jays'; - --- ============================================ --- VERIFICATION QUERY --- ============================================ --- Run after migration to verify all 30 teams have correct values: --- SELECT DISTINCT franchise FROM player ORDER BY franchise; --- --- Expected output (30 city-agnostic franchise names): --- Angels, Athletics, Blue Jays, Braves, Brewers, Cardinals, Cubs, --- Diamondbacks, Dodgers, Giants, Guardians, Mariners, Marlins, Mets, --- Nationals, Orioles, Padres, Phillies, Pirates, Rangers, Rays, --- Red Sox, Reds, Rockies, Royals, Tigers, Twins, White Sox, Yankees - --- ============================================ --- ROLLBACK MIGRATION (if needed) --- ============================================ --- Use these statements to revert to original values: --- --- UPDATE player SET franchise = 'Arizona Diamondbacks' WHERE franchise = 'Diamondbacks' AND mlbclub LIKE '%Arizona%'; --- UPDATE player SET franchise = 'Atlanta Braves' WHERE franchise = 'Braves' AND mlbclub LIKE '%Atlanta%'; --- UPDATE player SET franchise = 'Baltimore Orioles' WHERE franchise = 'Orioles' AND mlbclub LIKE '%Baltimore%'; --- UPDATE player SET franchise = 'Boston Red Sox' WHERE franchise = 'Red Sox' AND mlbclub LIKE '%Boston%'; --- UPDATE player SET franchise = 'Chicago Cubs' WHERE franchise = 'Cubs' AND mlbclub LIKE '%Chicago%'; --- UPDATE player SET franchise = 'Chicago White Sox' WHERE franchise = 'White Sox' AND mlbclub LIKE '%Chicago%'; --- UPDATE player SET franchise = 'Cincinnati Reds' WHERE franchise = 'Reds' AND mlbclub LIKE '%Cincinnati%'; --- UPDATE player SET franchise = 'Cleveland Guardians' WHERE franchise = 'Guardians' AND mlbclub LIKE '%Cleveland%'; --- UPDATE player SET franchise = 'Colorado Rockies' WHERE franchise = 'Rockies' AND mlbclub LIKE '%Colorado%'; --- UPDATE player SET franchise = 'Detroit Tigers' WHERE franchise = 'Tigers' AND mlbclub LIKE '%Detroit%'; --- UPDATE player SET franchise = 'Houston Astros' WHERE franchise = 'Astros' AND mlbclub LIKE '%Houston%'; --- UPDATE player SET franchise = 'Kansas City Royals' WHERE franchise = 'Royals' AND mlbclub LIKE '%Kansas%'; --- UPDATE player SET franchise = 'Los Angeles Angels' WHERE franchise = 'Angels' AND mlbclub LIKE '%Los Angeles%' AND mlbclub NOT LIKE '%Dodgers%'; --- UPDATE player SET franchise = 'Los Angeles Dodgers' WHERE franchise = 'Dodgers' AND mlbclub LIKE '%Los Angeles%'; --- UPDATE player SET franchise = 'Miami Marlins' WHERE franchise = 'Marlins' AND mlbclub LIKE '%Miami%'; --- UPDATE player SET franchise = 'Milwaukee Brewers' WHERE franchise = 'Brewers' AND mlbclub LIKE '%Milwaukee%'; --- UPDATE player SET franchise = 'Minnesota Twins' WHERE franchise = 'Twins' AND mlbclub LIKE '%Minnesota%'; --- UPDATE player SET franchise = 'New York Mets' WHERE franchise = 'Mets' AND mlbclub LIKE '%New York%'; --- UPDATE player SET franchise = 'New York Yankees' WHERE franchise = 'Yankees' AND mlbclub LIKE '%New York%'; --- UPDATE player SET franchise = 'Oakland Athletics' WHERE franchise = 'Athletics' AND mlbclub LIKE '%Oakland%'; --- UPDATE player SET franchise = 'Philadelphia Phillies' WHERE franchise = 'Phillies' AND mlbclub LIKE '%Philadelphia%'; --- UPDATE player SET franchise = 'Pittsburgh Pirates' WHERE franchise = 'Pirates' AND mlbclub LIKE '%Pittsburgh%'; --- UPDATE player SET franchise = 'San Diego Padres' WHERE franchise = 'Padres' AND mlbclub LIKE '%San Diego%'; --- UPDATE player SET franchise = 'San Francisco Giants' WHERE franchise = 'Giants' AND mlbclub LIKE '%San Francisco%'; --- UPDATE player SET franchise = 'Seattle Mariners' WHERE franchise = 'Mariners' AND mlbclub LIKE '%Seattle%'; --- UPDATE player SET franchise = 'St Louis Cardinals' WHERE franchise = 'Cardinals' AND mlbclub LIKE '%St%Louis%'; --- UPDATE player SET franchise = 'Tampa Bay Rays' WHERE franchise = 'Rays' AND mlbclub LIKE '%Tampa%'; --- UPDATE player SET franchise = 'Texas Rangers' WHERE franchise = 'Rangers' AND mlbclub LIKE '%Texas%'; --- UPDATE player SET franchise = 'Toronto Blue Jays' WHERE franchise = 'Blue Jays' AND mlbclub LIKE '%Toronto%'; --- UPDATE player SET franchise = 'Washington Nationals' WHERE franchise = 'Nationals' AND mlbclub LIKE '%Washington%'; diff --git a/sheets.py b/sheets.py deleted file mode 100644 index b134f26..0000000 --- a/sheets.py +++ /dev/null @@ -1,156 +0,0 @@ -import logging -from db_engine import Player, Card, Team, Cardset - - -PD_SHEET_KEY = '1xqPOrJIjdUWfDSFNnUiecRgo8DDKFKk2Pf1iQ4pTREM' - - -def send_to_sheets(sheets, sheet_key: str, worksheet_title: str, data_list: list, data_range: str): - this_sheet = sheets.open_by_key(sheet_key) - this_ws = this_sheet.worksheet_by_title(worksheet_title) - this_ws.update_values( - crange=data_range, - values=data_list - ) - return this_sheet - - -def update_all_players(sheets): - player_list = Player.select() - logging.debug(f'send to sheets player_list: {player_list}') - player_data = [] - for x in player_list: - player_data.append( - [ - x.player_id, x.p_name, x.cost, x.image, - x.image2, x.mlbclub, x.franchise, - x.cardset.name, x.rarity.name, x.pos_1, x.pos_2, - x.pos_3, x.pos_4, x.pos_5, x.pos_6, - x.pos_7, x.pos_8, x.headshot, x.vanity_card, - x.strat_code, x.bbref_id, - x.description, x.cardset.for_purchase, x.cardset.in_packs - ] - ) - - return send_to_sheets( - sheets, - sheet_key=PD_SHEET_KEY, - worksheet_title='All Players', - data_list=player_data, - data_range='A2' - ) - - -def send_ai_cards(sheets): - card_list = Card.select().join(Team).where(Card.team.is_ai) - logging.debug(f'send to sheets card_list: {card_list}') - card_data = [] - - for x in card_list: - card_data.append([ - x.player.cardset.name, x.player.p_name, x.player.rarity.name, x.player.image, x.player.image2, - x.player.pos_1, - x.player.pos_2, x.player.pos_3, x.player.pos_4, x.player.pos_5, x.player.pos_6, x.player.pos_7, - x.player.pos_8, x.player.cost, x.player.mlbclub, x.player.franchise, x.player.set_num, x.player.bbref_id, - x.player_id, x.id, x.team.id - ]) - - return send_to_sheets( - sheets, - sheet_key=PD_SHEET_KEY, - worksheet_title='AI Cards', - data_list=card_data, - data_range='A2' - ) - - -def post_new_cards(sheets, starting_id: int): - card_list = Card.select().where(Card.id >= starting_id) - logging.debug(f'post_new_cards to sheets card_list: {card_list}') - card_data = [] - - count = starting_id - for x in card_list: - while count < x.id: - card_data.append(['', '', '', '', '']) - count += 1 - - card_data.append([ - x.player.cardset.name, x.player.p_name, x.player.rarity.name, x.player.image, x.player.image2, - x.player.pos_1, - x.player.pos_2, x.player.pos_3, x.player.pos_4, x.player.pos_5, x.player.pos_6, x.player.pos_7, - x.player.pos_8, x.player.cost, x.player.mlbclub, x.player.franchise, x.player.set_num, x.player.bbref_id, - x.player_id, x.id, x.team.id - ]) - count += 1 - - return send_to_sheets( - sheets, - sheet_key=PD_SHEET_KEY, - worksheet_title='All Cards', - data_list=card_data, - data_range=f'A{starting_id + 1}' - ) - - -def post_deletion(sheets, del_ids: list): - del_data = [] - deletion_ids = [int(x) for x in del_ids] - deletion_ids.sort() - logging.info(f'sorted: {deletion_ids}') - - x = deletion_ids[0] - while x <= deletion_ids[-1]: - if x in deletion_ids: - del_data.append(['TRUE']) - else: - del_data.append(['']) - x += 1 - - return send_to_sheets( - sheets, - sheet_key=PD_SHEET_KEY, - worksheet_title='All Cards', - data_list=del_data, - data_range=f'V{deletion_ids[0] + 1}' - ) - - -# def update_one_player(sheets, this_player): -# logging.debug(f'send to sheets this_player: {this_player}') -# player_data = [[ -# this_player['player_id'], this_player['p_name'], this_player['cost'], this_player['image'], -# this_player['image2'], this_player['mlbclub'], this_player['franchise'], -# this_player['cardset']['name'], this_player['rarity']['name'], this_player['pos_1'], this_player['pos_2'], -# this_player['pos_3'], this_player['pos_4'], this_player['pos_5'], this_player['pos_6'], -# this_player['pos_7'], this_player['pos_8'], this_player['headshot'], this_player['vanity_card'], -# this_player['strat_code'], this_player['bbref_id'], -# this_player['description'], this_player['cardset']['for_purchase'], this_player['cardset']['in_packs'] -# ]] -# -# return send_to_sheets( -# sheets, -# sheet_key=PD_SHEET_KEY, -# worksheet_title='All Cards', -# data_list=player_data, -# data_range=f'A{this_player["player_id"] + 1}' -# ) - - -# def update_many_players(sheets, player_list): -# logging.debug(f'send to sheets player_list: {player_list}') -# player_data = [] -# for x in player_list: -# player_data.append( -# [ -# x['player_id'], x['p_name'], x['cost'], x['image'], -# x['image2'], x['mlbclub'], x['franchise'], -# x['cardset']['name'], x['rarity']['name'], x['pos_1'], x['pos_2'], -# x['pos_3'], x['pos_4'], x['pos_5'], x['pos_6'], -# x['pos_7'], x['pos_8'], x['headshot'], x['vanity_card'], -# x['strat_code'], x['bbref_id'], -# x['description'], x['cardset']['for_purchase'], x['cardset']['in_packs'] -# ] -# ) -# -#