246 lines
7.8 KiB
Python
246 lines
7.8 KiB
Python
#!/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()
|