"""Einmalige Datenmigration zu Multi-Tenancy. Dieses Script: 1. Erstellt die AegisSight-Organisation 2. Erstellt eine permanente Lizenz fuer AegisSight 3. Weist bestehende Nutzer (rac00n, ch33tah) der AegisSight-Org zu 4. Setzt tenant_id auf alle bestehenden Daten 5. Erstellt Portal-Admin-Zugaenge """ import asyncio import os import sys import shutil from datetime import datetime # Pfade fuer Imports sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from config import DB_PATH, TIMEZONE from database import init_db, get_db from auth import hash_password # E-Mail-Adressen fuer bestehende Nutzer USER_EMAILS = { "rac00n": os.environ.get("RACOON_EMAIL", "momohomma@googlemail.com"), "ch33tah": os.environ.get("CHEETAH_EMAIL", "hendrik_gebhardt@gmx.de"), } async def migrate(): """Fuehrt die Multi-Tenancy-Migration durch.""" # 1. Backup erstellen if os.path.exists(DB_PATH): backup_path = DB_PATH + f".backup-{datetime.now().strftime('%Y%m%d_%H%M%S')}" shutil.copy2(DB_PATH, backup_path) print(f"Backup erstellt: {backup_path}") else: print("Keine bestehende Datenbank gefunden. Frische Installation.") # 2. Schema-Migration (init_db erstellt neue Tabellen und fuegt Spalten hinzu) await init_db() print("Schema-Migration abgeschlossen.") db = await get_db() try: # 3. Pruefen ob Migration bereits gelaufen ist cursor = await db.execute("SELECT COUNT(*) as cnt FROM organizations") org_count = (await cursor.fetchone())["cnt"] if org_count > 0: print("Migration wurde bereits durchgefuehrt. Abbruch.") return # 4. AegisSight-Organisation anlegen now = datetime.now(TIMEZONE).isoformat() cursor = await db.execute( """INSERT INTO organizations (name, slug, is_active, created_at, updated_at) VALUES (?, ?, 1, ?, ?)""", ("AegisSight", "aegissight", now, now), ) aegis_org_id = cursor.lastrowid print(f"Organisation AegisSight angelegt (ID: {aegis_org_id})") # 5. Permanente Lizenz fuer AegisSight await db.execute( """INSERT INTO licenses (organization_id, license_type, max_users, valid_from, valid_until, status, notes) VALUES (?, 'permanent', 50, ?, NULL, 'active', 'Interne AegisSight-Lizenz')""", (aegis_org_id, now), ) print("Permanente Lizenz fuer AegisSight erstellt") # 6. Bestehende Nutzer der AegisSight-Org zuweisen cursor = await db.execute("SELECT id, username FROM users") users = await cursor.fetchall() for user in users: username = user["username"] email = USER_EMAILS.get(username, f"{username}@aegis-sight.de") await db.execute( """UPDATE users SET organization_id = ?, role = 'org_admin', is_active = 1, email = ? WHERE id = ?""", (aegis_org_id, email, user["id"]), ) print(f"Nutzer '{username}' -> AegisSight (org_admin, email: {email})") # 7. tenant_id auf alle bestehenden Incidents setzen await db.execute( "UPDATE incidents SET tenant_id = ? WHERE tenant_id IS NULL", (aegis_org_id,), ) cursor = await db.execute("SELECT changes()") changes = (await cursor.fetchone())[0] print(f"{changes} Incidents mit tenant_id versehen") # 8. tenant_id auf alle bestehenden Articles setzen await db.execute( "UPDATE articles SET tenant_id = ? WHERE tenant_id IS NULL", (aegis_org_id,), ) cursor = await db.execute("SELECT changes()") changes = (await cursor.fetchone())[0] print(f"{changes} Articles mit tenant_id versehen") # 9. tenant_id auf alle bestehenden fact_checks setzen await db.execute( "UPDATE fact_checks SET tenant_id = ? WHERE tenant_id IS NULL", (aegis_org_id,), ) # 10. tenant_id auf alle bestehenden refresh_log setzen await db.execute( "UPDATE refresh_log SET tenant_id = ? WHERE tenant_id IS NULL", (aegis_org_id,), ) # 11. tenant_id auf alle bestehenden incident_snapshots setzen await db.execute( "UPDATE incident_snapshots SET tenant_id = ? WHERE tenant_id IS NULL", (aegis_org_id,), ) # 12. tenant_id auf alle bestehenden notifications setzen await db.execute( "UPDATE notifications SET tenant_id = ? WHERE tenant_id IS NULL", (aegis_org_id,), ) # 13. System-Quellen bleiben global (tenant_id=NULL) # Nur nutzer-erstellte Quellen bekommen tenant_id await db.execute( "UPDATE sources SET tenant_id = ? WHERE added_by != 'system' AND tenant_id IS NULL", (aegis_org_id,), ) print("Nutzer-Quellen mit tenant_id versehen (System-Quellen bleiben global)") # 14. Portal-Admin-Zugaenge anlegen print("\n--- Portal-Admin-Zugaenge ---") portal_users = ["rac00n", "ch33tah"] for pu in portal_users: # Pruefen ob schon existiert cursor = await db.execute( "SELECT id FROM portal_admins WHERE username = ?", (pu,) ) if await cursor.fetchone(): print(f"Portal-Admin '{pu}' existiert bereits") continue # Passwort generieren import secrets import string password = ''.join(secrets.choice(string.ascii_letters + string.digits + "!@#$%&*") for _ in range(16)) pw_hash = hash_password(password) await db.execute( "INSERT INTO portal_admins (username, password_hash) VALUES (?, ?)", (pu, pw_hash), ) print(f"Portal-Admin '{pu}' erstellt - Passwort: {password}") await db.commit() print("\nMigration erfolgreich abgeschlossen!") except Exception as e: print(f"\nFEHLER bei Migration: {e}") raise finally: await db.close() if __name__ == "__main__": asyncio.run(migrate())