# /opt/docker/dev/service_finder/backend/migrations/env.py import asyncio from logging.config import fileConfig import os import sys from sqlalchemy import pool, text from sqlalchemy.ext.asyncio import async_engine_from_config from alembic import context # --- ÚTVONAL JAVÍTÁS --- # Biztosítjuk, hogy a Docker konténeren belül az /app könyvtár legyen a gyökér sys.path.insert(0, "/app") try: from app.core.config import settings from app.database import Base # Fontos: Importáljuk a modelleket, hogy a Base.metadata feltöltődjön import app.models as models except ImportError as e: print(f"❌ Kritikus hiba az importálásnál: {e}") raise config = context.config config.set_main_option("sqlalchemy.url", str(settings.SQLALCHEMY_DATABASE_URI)) if config.config_file_name is not None: fileConfig(config.config_file_name) target_metadata = Base.metadata def include_object(object, name, type_, reflected, compare_to): """ 🔥 MB 2.0 BIZTONSÁGI SZŰRŐ ÉS WHITELIST 🔥 Ez a rész felel azért, hogy ne töröljünk véletlenül semmit. """ # 1. PostGIS és Alembic belső táblák védelme excluded_tables = [ "spatial_ref_sys", "alembic_version", "geography_columns", "geometry_columns", "raster_columns", "raster_overviews" ] if type_ == "table" and name in excluded_tables: return False # 2. 🔥 BIZTONSÁGI FÉK (Safety Guard) 🔥 # Ha bent van a DB-ben (reflected), de nincs a kódban (compare_to is None) # -> TILOS TÖRÖLNI! Megvédi a manuálisan létrehozott táblákat. if reflected and compare_to is None: return False # 3. Engedélyezett sémák listája (Whitelist) allowed_schemas = ["identity", "data", "system", "public"] # 4. Séma szintű engedélyezés (pl. séma létrehozásához) if type_ == "schema": return name in allowed_schemas # 5. Objektum séma ellenőrzése obj_schema = getattr(object, "schema", None) if obj_schema is None and hasattr(object, "table"): obj_schema = getattr(object.table, "schema", None) # Ha a séma benne van a whitelistben, engedélyezzük a módosítást if obj_schema: return obj_schema in allowed_schemas # 6. Fallback a public sémára (pl. globális típusok, Enum-ok számára) # Csak akkor engedjük, ha explicit public, vagy ha nincs jelölve, de nem tiltott. if obj_schema is None or obj_schema == "public": return True # 7. 🔥 SZIGORÚ ZÁRÁS 🔥 # Minden mást (pl. idegen sémák) kizárunk a migrációból. return False def do_run_migrations(connection): """ Migrációk futtatása közös konfigurációval. """ context.configure( connection=connection, target_metadata=target_metadata, include_schemas=True, # Lássa a több-sémás felépítést include_object=include_object, # Alkalmazza a fenti biztonsági szűrőt compare_type=True, # Érzékelje az oszloptípus változásait (pl. String -> Text) compare_server_default=True, # Érzékelje az alapértelmezett érték változásait (Dinamikus confighoz kell!) version_table_schema='public', # A verziótábla mindig maradjon a public-ban upgrade_token="upgrade", downgrade_token="downgrade" ) with context.begin_transaction(): context.run_migrations() async def run_migrations_online() -> None: """ Online futtatási mód (ez fut a Dockerben). """ connectable = async_engine_from_config( config.get_section(config.config_ini_section, {}), prefix="sqlalchemy.", poolclass=pool.NullPool, ) async with connectable.connect() as connection: await connection.run_sync(do_run_migrations) await connectable.dispose() if context.is_offline_mode(): # Offline mód kiegészítése a biztonsági paraméterekkel context.configure( url=config.get_main_option("sqlalchemy.url"), target_metadata=target_metadata, literal_binds=True, include_schemas=True, include_object=include_object, compare_type=True, compare_server_default=True ) with context.begin_transaction(): context.run_migrations() else: # Online mód indítása try: loop = asyncio.get_event_loop() except RuntimeError: loop = asyncio.new_event_loop() loop.run_until_complete(run_migrations_online())