""" i18n JSONB Migration Script (Phase 1) Migrates old name_hu/name_en columns to name_i18n JSONB format. Run INSIDE the sf_api container: docker exec sf_api python3 /app/backend/app/scripts/migrate_i18n_jsonb.py IMPORTANT: Run this AFTER the new JSONB columns have been added by sync_engine. """ import asyncio import json import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent.parent)) from sqlalchemy import text from app.db.session import AsyncSessionLocal TABLES = [ { "schema": "marketplace", "table": "expertise_tags", "name_cols": ["name_hu", "name_en"], "name_translations_col": "name_translations", "desc_cols": ["description"], "target_name_col": "name_i18n", "target_desc_col": "description_i18n", }, { "schema": "vehicle", "table": "dict_body_types", "name_cols": ["name_hu"], "name_translations_col": None, "desc_cols": [], "target_name_col": "name_i18n", "target_desc_col": None, }, { "schema": "system", "table": "service_catalog", "name_cols": ["name"], "name_translations_col": None, "desc_cols": ["description"], "target_name_col": "name_i18n", "target_desc_col": "description_i18n", }, ] async def migrate_table(config: dict): schema = config["schema"] table = config["table"] target_name = config["target_name_col"] target_desc = config["target_desc_col"] name_cols = config["name_cols"] desc_cols = config["desc_cols"] trans_col = config["name_translations_col"] print(f"\n{'='*60}") print(f" Migrating: {schema}.{table}") print(f"{'='*60}") async with AsyncSessionLocal() as db: select_cols = ["id"] + name_cols + desc_cols if trans_col: select_cols.append(trans_col) stmt = text( f"SELECT {', '.join(select_cols)} FROM {schema}.{table} " f"WHERE {target_name} IS NULL OR {target_name} = '{{\"hu\": \"\"}}'::jsonb" ) result = await db.execute(stmt) rows = result.fetchall() if not rows: print(f" ✅ No rows need migration in {schema}.{table}") return updated = 0 for row in rows: row_dict = row._mapping # Build name_i18n name_i18n = {} if len(name_cols) >= 1 and row_dict.get(name_cols[0]): name_i18n["hu"] = row_dict[name_cols[0]] if len(name_cols) >= 2 and row_dict.get(name_cols[1]): name_i18n["en"] = row_dict[name_cols[1]] # Merge with existing name_translations if trans_col and row_dict.get(trans_col): if isinstance(row_dict[trans_col], dict): name_i18n.update(row_dict[trans_col]) # Build description_i18n desc_i18n = None if desc_cols and row_dict.get(desc_cols[0]): desc_i18n = {"hu": row_dict[desc_cols[0]]} # Update update_cols = [f"{target_name} = :name_val"] params = {"name_val": json.dumps(name_i18n), "row_id": row_dict["id"]} if target_desc and desc_i18n: update_cols.append(f"{target_desc} = :desc_val") params["desc_val"] = json.dumps(desc_i18n) update_sql = f"UPDATE {schema}.{table} SET {', '.join(update_cols)} WHERE id = :row_id" await db.execute(text(update_sql), params) updated += 1 await db.commit() print(f" ✅ Migrated {updated} rows in {schema}.{table}") async def main(): print("=" * 70) print(" i18n JSONB Migration Script (Phase 1)") print("=" * 70) for table_config in TABLES: await migrate_table(table_config) print("\n" + "=" * 70) print(" ✅ Migration complete!") print("=" * 70) if __name__ == "__main__": asyncio.run(main())