"""Leichte In-Place-Migrationen für SQLite (kein Alembic). Wird beim App-Start nach create_all() ausgeführt. Idempotent. """ from __future__ import annotations import logging from sqlalchemy import text from sqlmodel import Session from app.core.db import engine log = logging.getLogger("voice-agent.migrate") def _existing_columns(conn, table: str) -> set[str]: rows = conn.execute(text(f"PRAGMA table_info({table})")).all() return {r[1] for r in rows} # row[1] = column name def run_migrations() -> None: with engine.begin() as conn: # 1) Job.owner_id Spalte nachrüsten, falls die Tabelle aus alter Version kommt. cols = _existing_columns(conn, "job") if "owner_id" not in cols: log.info("Migration: füge job.owner_id hinzu") conn.execute(text("ALTER TABLE job ADD COLUMN owner_id INTEGER")) conn.execute(text("CREATE INDEX IF NOT EXISTS ix_job_owner_id ON job(owner_id)")) # 2) Job.profile Spalte nachrüsten (Profil-System). cols = _existing_columns(conn, "job") if "profile" not in cols: log.info("Migration: füge job.profile hinzu (default 'meeting')") conn.execute(text("ALTER TABLE job ADD COLUMN profile VARCHAR DEFAULT 'meeting'")) # 3) User.default_profile nachrüsten. user_cols = _existing_columns(conn, "user") if user_cols and "default_profile" not in user_cols: log.info("Migration: füge user.default_profile hinzu (default 'meeting')") conn.execute(text("ALTER TABLE user ADD COLUMN default_profile VARCHAR DEFAULT 'meeting'")) # 4) Job.pdf_path nachrüsten (PDF-Export zusätzlich zu DOCX). cols = _existing_columns(conn, "job") if "pdf_path" not in cols: log.info("Migration: füge job.pdf_path hinzu") conn.execute(text("ALTER TABLE job ADD COLUMN pdf_path VARCHAR DEFAULT ''")) # 5) Diagnose-Felder: step_started_at, last_heartbeat_at cols = _existing_columns(conn, "job") if "step_started_at" not in cols: log.info("Migration: füge job.step_started_at hinzu") conn.execute(text("ALTER TABLE job ADD COLUMN step_started_at DATETIME")) if "last_heartbeat_at" not in cols: log.info("Migration: füge job.last_heartbeat_at hinzu") conn.execute(text("ALTER TABLE job ADD COLUMN last_heartbeat_at DATETIME")) # 6) Modell-Auswahl: user.default_model + job.model user_cols = _existing_columns(conn, "user") if user_cols and "default_model" not in user_cols: log.info("Migration: füge user.default_model hinzu (leer = Worker-Default)") conn.execute(text("ALTER TABLE user ADD COLUMN default_model VARCHAR DEFAULT ''")) cols = _existing_columns(conn, "job") if "model" not in cols: log.info("Migration: füge job.model hinzu (leer = Worker-Default)") conn.execute(text("ALTER TABLE job ADD COLUMN model VARCHAR DEFAULT ''")) def assign_orphan_jobs_to(user_id: int) -> int: """Weist alle Jobs ohne owner_id dem angegebenen User zu. Gibt Anzahl zurück.""" with Session(engine) as session: result = session.execute( text("UPDATE job SET owner_id = :uid WHERE owner_id IS NULL"), {"uid": user_id}, ) session.commit() n = result.rowcount or 0 if n: log.info("Migration: %d Bestand-Jobs an user_id=%d zugeordnet", n, user_id) return n