combat testing and polishing in the dev console, many bug fixes
This commit is contained in:
245
api/scripts/migrate_combat_data.py
Normal file
245
api/scripts/migrate_combat_data.py
Normal file
@@ -0,0 +1,245 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Combat Data Migration Script.
|
||||
|
||||
This script migrates existing inline combat encounter data from game_sessions
|
||||
to the dedicated combat_encounters table.
|
||||
|
||||
The migration is idempotent - it's safe to run multiple times. Sessions that
|
||||
have already been migrated (have active_combat_encounter_id) are skipped.
|
||||
|
||||
Usage:
|
||||
python scripts/migrate_combat_data.py
|
||||
|
||||
Note:
|
||||
- Run this after deploying the new combat database schema
|
||||
- The application handles automatic migration on-demand, so this is optional
|
||||
- This script is useful for proactively migrating all data at once
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# Add project root to path
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables before importing app modules
|
||||
load_dotenv()
|
||||
|
||||
from app.services.database_service import get_database_service
|
||||
from app.services.combat_repository import get_combat_repository
|
||||
from app.models.session import GameSession
|
||||
from app.models.combat import CombatEncounter
|
||||
from app.utils.logging import get_logger
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
def migrate_inline_combat_encounters() -> dict:
|
||||
"""
|
||||
Migrate all inline combat encounters to the dedicated table.
|
||||
|
||||
Scans all game sessions for inline combat_encounter data and migrates
|
||||
them to the combat_encounters table. Updates sessions to use the new
|
||||
active_combat_encounter_id reference.
|
||||
|
||||
Returns:
|
||||
Dict with migration statistics:
|
||||
- total_sessions: Number of sessions scanned
|
||||
- migrated: Number of sessions with combat data migrated
|
||||
- skipped: Number of sessions already migrated or without combat
|
||||
- errors: Number of sessions that failed to migrate
|
||||
"""
|
||||
db = get_database_service()
|
||||
repo = get_combat_repository()
|
||||
|
||||
stats = {
|
||||
'total_sessions': 0,
|
||||
'migrated': 0,
|
||||
'skipped': 0,
|
||||
'errors': 0,
|
||||
'error_details': []
|
||||
}
|
||||
|
||||
print("Scanning game_sessions for inline combat data...")
|
||||
|
||||
# Query all sessions (paginated)
|
||||
offset = 0
|
||||
limit = 100
|
||||
|
||||
while True:
|
||||
try:
|
||||
rows = db.list_rows(
|
||||
table_id='game_sessions',
|
||||
limit=limit,
|
||||
offset=offset
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("Failed to query sessions", error=str(e))
|
||||
print(f"Error querying sessions: {e}")
|
||||
break
|
||||
|
||||
if not rows:
|
||||
break
|
||||
|
||||
for row in rows:
|
||||
stats['total_sessions'] += 1
|
||||
session_id = row.id
|
||||
|
||||
try:
|
||||
# Parse session data
|
||||
session_json = row.data.get('sessionData', '{}')
|
||||
session_data = json.loads(session_json)
|
||||
|
||||
# Check if already migrated (has reference, no inline data)
|
||||
if (session_data.get('active_combat_encounter_id') and
|
||||
not session_data.get('combat_encounter')):
|
||||
stats['skipped'] += 1
|
||||
continue
|
||||
|
||||
# Check if has inline combat data to migrate
|
||||
combat_data = session_data.get('combat_encounter')
|
||||
if not combat_data:
|
||||
stats['skipped'] += 1
|
||||
continue
|
||||
|
||||
# Parse combat encounter
|
||||
encounter = CombatEncounter.from_dict(combat_data)
|
||||
user_id = session_data.get('user_id', row.data.get('userId', ''))
|
||||
|
||||
logger.info("Migrating inline combat encounter",
|
||||
session_id=session_id,
|
||||
encounter_id=encounter.encounter_id)
|
||||
|
||||
# Check if encounter already exists in repository
|
||||
existing = repo.get_encounter(encounter.encounter_id)
|
||||
if existing:
|
||||
# Already migrated, just update session reference
|
||||
session_data['active_combat_encounter_id'] = encounter.encounter_id
|
||||
session_data['combat_encounter'] = None
|
||||
else:
|
||||
# Save to repository
|
||||
repo.create_encounter(
|
||||
encounter=encounter,
|
||||
session_id=session_id,
|
||||
user_id=user_id
|
||||
)
|
||||
session_data['active_combat_encounter_id'] = encounter.encounter_id
|
||||
session_data['combat_encounter'] = None
|
||||
|
||||
# Update session
|
||||
db.update_row(
|
||||
table_id='game_sessions',
|
||||
row_id=session_id,
|
||||
data={'sessionData': json.dumps(session_data)}
|
||||
)
|
||||
|
||||
stats['migrated'] += 1
|
||||
print(f" Migrated: {session_id} -> {encounter.encounter_id}")
|
||||
|
||||
except Exception as e:
|
||||
stats['errors'] += 1
|
||||
error_msg = f"Session {session_id}: {str(e)}"
|
||||
stats['error_details'].append(error_msg)
|
||||
logger.error("Failed to migrate session",
|
||||
session_id=session_id,
|
||||
error=str(e))
|
||||
print(f" Error: {session_id} - {e}")
|
||||
|
||||
offset += limit
|
||||
|
||||
# Safety check to prevent infinite loop
|
||||
if offset > 10000:
|
||||
print("Warning: Stopped after 10000 sessions (safety limit)")
|
||||
break
|
||||
|
||||
return stats
|
||||
|
||||
|
||||
def main():
|
||||
"""Run the migration."""
|
||||
print("=" * 60)
|
||||
print("Code of Conquest - Combat Data Migration")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# Verify environment variables
|
||||
required_vars = [
|
||||
'APPWRITE_ENDPOINT',
|
||||
'APPWRITE_PROJECT_ID',
|
||||
'APPWRITE_API_KEY',
|
||||
'APPWRITE_DATABASE_ID'
|
||||
]
|
||||
|
||||
missing_vars = [var for var in required_vars if not os.getenv(var)]
|
||||
if missing_vars:
|
||||
print("ERROR: Missing required environment variables:")
|
||||
for var in missing_vars:
|
||||
print(f" - {var}")
|
||||
print()
|
||||
print("Please ensure your .env file is configured correctly.")
|
||||
sys.exit(1)
|
||||
|
||||
print("Environment configuration:")
|
||||
print(f" Endpoint: {os.getenv('APPWRITE_ENDPOINT')}")
|
||||
print(f" Project: {os.getenv('APPWRITE_PROJECT_ID')}")
|
||||
print(f" Database: {os.getenv('APPWRITE_DATABASE_ID')}")
|
||||
print()
|
||||
|
||||
# Confirm before proceeding
|
||||
print("This script will migrate inline combat data to the dedicated")
|
||||
print("combat_encounters table. This operation is safe and idempotent.")
|
||||
print()
|
||||
response = input("Proceed with migration? (y/N): ").strip().lower()
|
||||
if response != 'y':
|
||||
print("Migration cancelled.")
|
||||
sys.exit(0)
|
||||
|
||||
print()
|
||||
print("Starting migration...")
|
||||
print()
|
||||
|
||||
try:
|
||||
stats = migrate_inline_combat_encounters()
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
print("Migration Results")
|
||||
print("=" * 60)
|
||||
print()
|
||||
print(f"Total sessions scanned: {stats['total_sessions']}")
|
||||
print(f"Successfully migrated: {stats['migrated']}")
|
||||
print(f"Skipped (no combat): {stats['skipped']}")
|
||||
print(f"Errors: {stats['errors']}")
|
||||
print()
|
||||
|
||||
if stats['error_details']:
|
||||
print("Error details:")
|
||||
for error in stats['error_details'][:10]: # Show first 10
|
||||
print(f" - {error}")
|
||||
if len(stats['error_details']) > 10:
|
||||
print(f" ... and {len(stats['error_details']) - 10} more")
|
||||
print()
|
||||
|
||||
if stats['errors'] > 0:
|
||||
print("Some sessions failed to migrate. Check logs for details.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("Migration completed successfully!")
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Migration failed", error=str(e))
|
||||
print()
|
||||
print(f"MIGRATION FAILED: {str(e)}")
|
||||
print()
|
||||
print("Check logs for details.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user