#!/usr/bin/env python3 """ Database Migration Script for UrNetwork Stats Dashboard v2.0 Migrates from single-account to multi-account structure """ import sqlite3 import os import sys from getpass import getpass def print_header(text): print("\n" + "=" * 60) print(f" {text}") print("=" * 60) def print_success(text): print(f"✓ {text}") def print_warning(text): print(f"⚠ {text}") def print_error(text): print(f"✗ {text}") def backup_files(): """Create backups of existing files""" print_header("Vytváření záloh") if os.path.exists('.env'): os.system('cp .env .env.backup') print_success("Zazálohován .env → .env.backup") # Check for database in multiple locations db_locations = ['transfer_stats.db', 'instance/transfer_stats.db'] for db_path in db_locations: if os.path.exists(db_path): backup_path = db_path + '.backup' os.system(f'cp {db_path} {backup_path}') print_success(f"Zazálohována databáze → {backup_path}") break def migrate_database(): """Migrate database to new structure""" print_header("Migrace databáze") # Check for database in multiple locations db_path = None possible_paths = [ 'transfer_stats.db', 'instance/transfer_stats.db', '../transfer_stats.db' ] for path in possible_paths: if os.path.exists(path): db_path = path print_success(f"Nalezena databáze: {db_path}") break if not db_path: print_error("Databáze transfer_stats.db nenalezena!") print_warning("Hledáno v: " + ", ".join(possible_paths)) return False try: conn = sqlite3.connect(db_path) cursor = conn.cursor() # 1. Create accounts table cursor.execute(''' CREATE TABLE IF NOT EXISTS accounts ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password TEXT NOT NULL, nickname TEXT, is_active BOOLEAN DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ''') print_success("Vytvořena tabulka accounts") # 2. Add account_id column to stats try: cursor.execute('ALTER TABLE stats ADD COLUMN account_id INTEGER') print_success("Přidán sloupec account_id do tabulky stats") except sqlite3.OperationalError as e: if "duplicate column" in str(e).lower(): print_warning("Sloupec account_id již existuje") else: raise # 3. Migrate existing account from .env if os.path.exists('.env'): username = None password = None with open('.env', 'r') as f: for line in f: if line.startswith('UR_USER='): username = line.split('=', 1)[1].strip() elif line.startswith('UR_PASS='): password = line.split('=', 1)[1].strip() if username and password: cursor.execute('SELECT COUNT(*) FROM accounts WHERE username = ?', (username,)) if cursor.fetchone()[0] == 0: print(f"\nNalezen existující účet: {username}") nickname = input("Zadejte přezdívku pro tento účet (nebo stiskněte Enter pro přeskočení): ").strip() cursor.execute( 'INSERT INTO accounts (username, password, nickname, is_active) VALUES (?, ?, ?, 1)', (username, password, nickname if nickname else None) ) account_id = cursor.lastrowid # Update existing stats cursor.execute('UPDATE stats SET account_id = ? WHERE account_id IS NULL', (account_id,)) updated_count = cursor.rowcount print_success(f"Migrován účet: {username}") if nickname: print_success(f" Přezdívka: {nickname}") print_success(f" Aktualizováno {updated_count} statistických záznamů") else: print_warning(f"Účet {username} již existuje v databázi") else: print_warning("Nenalezeny credentials v .env souboru") else: print_warning(".env soubor nenalezen") conn.commit() conn.close() print_success("Migrace databáze dokončena!") return True except Exception as e: print_error(f"Chyba při migraci databáze: {e}") return False def update_env_file(): """Update .env file with admin password""" print_header("Aktualizace .env souboru") if not os.path.exists('.env'): print_warning(".env soubor nenalezen, bude vytvořen nový") env_lines = [] else: with open('.env', 'r') as f: env_lines = f.readlines() # Check if ADMIN_PASSWORD exists has_admin_pass = any(line.startswith('ADMIN_PASSWORD=') for line in env_lines) if has_admin_pass: print_warning("ADMIN_PASSWORD již existuje v .env") response = input("Chcete nastavit nové heslo? (y/n): ").lower() if response != 'y': return True # Remove old password env_lines = [line for line in env_lines if not line.startswith('ADMIN_PASSWORD=')] print("\nNastavte administrátorské heslo pro přístup do dashboardu.") print("(Toto je oddělené od vašich UrNetwork credentials)") while True: admin_pass = getpass("\nAdministrátorské heslo: ") admin_pass_confirm = getpass("Potvrďte heslo: ") if admin_pass == admin_pass_confirm: if len(admin_pass) < 6: print_error("Heslo musí mít alespoň 6 znaků!") continue break else: print_error("Hesla se neshodují!") # Add admin password if not any(line.strip() == "# Admin Access" for line in env_lines): env_lines.insert(0, "# Admin Access\n") env_lines.insert(1, f"ADMIN_PASSWORD={admin_pass}\n") env_lines.insert(2, "\n") # Write updated .env with open('.env', 'w') as f: f.writelines(env_lines) print_success("Administrátorské heslo nastaveno") return True def verify_migration(): """Verify that migration was successful""" print_header("Ověření migrace") # Find database db_path = None for path in ['transfer_stats.db', 'instance/transfer_stats.db']: if os.path.exists(path): db_path = path break if not db_path: print_error("Databáze nenalezena pro ověření") return False try: conn = sqlite3.connect(db_path) cursor = conn.cursor() # Check accounts table cursor.execute("SELECT COUNT(*) FROM accounts") account_count = cursor.fetchone()[0] print_success(f"Nalezeno {account_count} účtů v databázi") # Check stats with account_id cursor.execute("SELECT COUNT(*) FROM stats WHERE account_id IS NOT NULL") stats_count = cursor.fetchone()[0] print_success(f"Nalezeno {stats_count} statistických záznamů s account_id") # Check orphaned stats cursor.execute("SELECT COUNT(*) FROM stats WHERE account_id IS NULL") orphaned_count = cursor.fetchone()[0] if orphaned_count > 0: print_warning(f"Nalezeno {orphaned_count} statistik bez přiřazeného účtu") conn.close() # Check .env if os.path.exists('.env'): with open('.env', 'r') as f: env_content = f.read() if 'ADMIN_PASSWORD=' in env_content: print_success("ADMIN_PASSWORD nalezeno v .env") else: print_warning("ADMIN_PASSWORD chybí v .env") return True except Exception as e: print_error(f"Chyba při ověření: {e}") return False def print_next_steps(): """Print next steps after migration""" print_header("Další kroky") print(""" 1. Restartujte aplikaci: pkill -f main.py python3 main.py 2. Přihlaste se pomocí nového admin hesla 3. Přejděte do "Správa účtů" pro přidání dalších UrNetwork účtů 4. Pokud něco nefunguje, obnovte zálohy: cp .env.backup .env cp transfer_stats.db.backup transfer_stats.db Dokumentace: README_CZ.md """) def main(): print(""" ╔════════════════════════════════════════════════════════════╗ ║ UrNetwork Stats Dashboard - Migration Script v2.0 ║ ║ Migrace z single-account na multi-account strukturu ║ ╚════════════════════════════════════════════════════════════╝ """) print("\nTento skript provede následující:") print(" • Vytvoří zálohy .env a databáze") print(" • Přidá podporu pro více účtů") print(" • Nastaví administrátorské heslo") print(" • Migruje existující data") response = input("\nPokračovat? (y/n): ").lower() if response != 'y': print("Migrace zrušena.") sys.exit(0) # Run migration steps backup_files() if not migrate_database(): print_error("\nMigrace selhala při aktualizaci databáze!") print("Obnovte zálohy a zkuste to znovu.") sys.exit(1) if not update_env_file(): print_error("\nMigrace selhala při aktualizaci .env!") print("Obnovte zálohy a zkuste to znovu.") sys.exit(1) if not verify_migration(): print_warning("\nOvěření migrace selhalo, ale data by měla být OK") print_next_steps() print_header("Migrace úspěšně dokončena! 🎉") if __name__ == "__main__": try: main() except KeyboardInterrupt: print("\n\nMigrace přerušena uživatelem.") sys.exit(1) except Exception as e: print_error(f"\nNeočekávaná chyba: {e}") print("Obnovte zálohy pomocí:") print(" cp .env.backup .env") print(" cp transfer_stats.db.backup transfer_stats.db") sys.exit(1)